npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rn-apk-patcher

v2.1.0

Published

The simplest APK delta-update library for React Native. One function call — that's it.

Readme

rn-apk-patcher

The simplest way to deliver APK updates in React Native.

Instead of making your users download a full APK every time, you ship a tiny patch file (just the bytes that changed). The library downloads it, builds the new APK, and opens the installer — all from a single function call.

old.apk + patch.bsdiff → new.apk → 📲 Install

Android only.


Install

npm install rn-apk-patcher react-native-fs

That's the only install step. The library uses React Native's auto-linking, so no manual Gradle or Java changes are needed.

One manual step: Copy bspatch.c, bspatch.h, and the bzip2/ folder from github.com/mendsley/bsdiff into node_modules/rn-apk-patcher/android/src/main/jniLibs/. This is a one-time thing and is needed because the bspatch source can't be bundled directly due to its license.

Then add two things to your AndroidManifest.xml:

<!-- Inside <manifest> -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

<!-- Inside <application> -->
<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.rnapkpatcher.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/rnapkpatcher_paths" />
</provider>

Create android/app/src/main/res/xml/rnapkpatcher_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="internal" path="." />
    <cache-path name="cache" path="." />
</paths>

That's the entire setup.


Usage

import { updateApp } from 'rn-apk-patcher';

// Somewhere in your app, when an update is available:
const result = await updateApp('https://your-server.com/update.bsdiff');

if (result.success) {
  // The system install dialog is now open
}

With a progress bar

import { updateApp } from 'rn-apk-patcher';

const result = await updateApp(
  'https://your-server.com/update.bsdiff',
  (progress) => {
    console.log(`${progress.percent}% — ${progress.message}`);
    // progress.stage: 'downloading' | 'patching' | 'installing' | 'done' | 'error'
  }
);

With checksum verification (recommended)

const result = await updateApp(
  'https://your-server.com/update.bsdiff',
  (p) => setPercent(p.percent),
  'a1b2c3d4...' // SHA-256 of the new APK, from your server
);

Full example component

import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { updateApp, type UpdateProgress } from 'rn-apk-patcher';

export default function UpdateButton() {
  const [progress, setProgress] = useState<UpdateProgress | null>(null);
  const [loading, setLoading] = useState(false);

  const handleUpdate = async () => {
    setLoading(true);

    const result = await updateApp(
      'https://your-server.com/update.bsdiff',
      setProgress,
      'optional-sha256-checksum'
    );

    setLoading(false);

    if (!result.success) {
      console.error('Update failed:', result.error);
    }
  };

  return (
    <View style={styles.container}>
      {progress && (
        <View style={styles.progressBar}>
          <View style={[styles.fill, { width: `${progress.percent}%` }]} />
        </View>
      )}
      <Text style={styles.status}>
        {progress ? `${progress.percent}% — ${progress.message}` : 'Update available'}
      </Text>
      <TouchableOpacity
        style={styles.button}
        onPress={handleUpdate}
        disabled={loading}
      >
        <Text style={styles.buttonText}>
          {loading ? 'Updating...' : 'Install Update'}
        </Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { padding: 20 },
  progressBar: { height: 6, backgroundColor: '#eee', borderRadius: 3, marginBottom: 10 },
  fill: { height: 6, backgroundColor: '#007AFF', borderRadius: 3 },
  status: { color: '#555', marginBottom: 16 },
  button: { backgroundColor: '#007AFF', padding: 14, borderRadius: 10, alignItems: 'center' },
  buttonText: { color: '#fff', fontWeight: '700' },
});

API

updateApp(patchUrl, onProgress?, checksum?)

The only function you need to call.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | patchUrl | string | ✅ | URL to your .bsdiff patch file | | onProgress | (p: UpdateProgress) => void | ❌ | Called as the update progresses | | checksum | string | ❌ | SHA-256 of the new APK to verify the download |

Returns a Promise<UpdateResult>:

{
  success: boolean,
  error?: string,      // present only on failure
  durationMs?: number  // how long it took in milliseconds
}

UpdateProgress

{
  stage: 'downloading' | 'patching' | 'installing' | 'done' | 'error',
  percent: number,          // 0–100
  message: string,          // e.g. "Downloading update..."
  bytesDownloaded?: number,
  totalBytes?: number,
}

resetBase()

Forces the library to re-save the current APK as the baseline. You shouldn't need this in normal use.

import { resetBase } from 'rn-apk-patcher';
await resetBase();

clearCache()

Deletes all cached APK files to free up disk space.

import { clearCache } from 'rn-apk-patcher';
await clearCache();

Server side: generating patch files

On your build server, install bsdiff and run:

# Install
sudo apt-get install bsdiff   # Linux
brew install bsdiff            # macOS

# Generate the patch
bsdiff old.apk new.apk update.bsdiff

# Generate the checksum (to pass as the 3rd argument on the client)
sha256sum new.apk

A typical update API response might look like:

{
  "hasUpdate": true,
  "patchUrl": "https://cdn.yourapp.com/patches/v2.bsdiff",
  "checksum": "a1b2c3d4e5f6...",
  "version": "2.0.0"
}

How it works

  1. On the first call, the library silently saves your currently installed APK as a baseline (base.apk).
  2. When you call updateApp(), it downloads the patch file from your URL.
  3. It runs bspatch natively (C library, compiled with NDK) to reconstruct the full new APK from base.apk + patch.
  4. It opens the Android system installer with the new APK.
  5. The new APK automatically becomes the baseline for the next update.

The patch file is typically 5–15× smaller than a full APK download, which means faster updates and less data usage for your users.


FAQ

Do I need to call anything on app launch?
No. The first time updateApp() runs, it automatically saves the current APK as the baseline. Nothing to set up.

What if the user cancels the install dialog?
The new APK stays on disk. You can call updateApp() again and it will go straight to the install step — no re-download needed.

What Android versions are supported?
Android 5.0 (API 21) and above. FileProvider is handled automatically on Android 7+.

Does it work on iOS?
No. APK files are Android-only. For iOS, use TestFlight or the App Store.


License

MIT