DEV Community

Falaq
Falaq

Posted on

I built a native React Native downloader because AI models are huge

I ran into this while building Orb, a private offline AI app for Android.

Orb runs local models on the phone. That is great for privacy, but it creates a very practical mobile problem: the app has to download model files that can be multiple gigabytes. If the network drops, the app backgrounds, or the user force-closes the app, restarting from zero is a terrible experience.

So I pulled the downloader out into a small open-source package:

GitHub: https://github.com/zraisan/react-native-client

NPM: https://www.npmjs.com/package/react-native-client

react-native-client is a native HTTP client for React Native apps that need direct-to-file downloads without moving bytes through the JavaScript bridge. The first stable API is focused on downloads, not general HTTP requests.

What it does

The package exposes a typed downloadFile API through Nitro Modules.

Under the hood:

  • Android uses OkHttp
  • iOS uses URLSession
  • files are streamed directly to disk on the native side
  • progress callbacks report bytes written and total length
  • resumable downloads use HTTP Range requests
  • partial-file resume validates Content-Range before appending
  • Android background mode uses a foreground service
  • iOS uses a background URLSession path

The main use case is large files: AI models, media packs, offline datasets, cache warmups, and anything else where a failed 2GB download should not mean starting over.

Basic usage

import { downloadFile, documentDirectoryPath } from 'react-native-client'

const result = await downloadFile({
  fromUrl: 'https://example.com/model.gguf',
  toFile: `${documentDirectoryPath}/model.gguf`,
})

console.log(result.statusCode, result.bytesWritten)
Enter fullscreen mode Exit fullscreen mode

With progress:

await downloadFile({
  fromUrl: 'https://example.com/large-file.bin',
  toFile: `${documentDirectoryPath}/large-file.bin`,
  begin: (statusCode, contentLength) => {
    console.log('started', statusCode, contentLength)
  },
  onProgress: (bytesWritten, contentLength) => {
    if (contentLength > 0) {
      console.log(`${Math.round((bytesWritten / contentLength) * 100)}%`)
    }
  },
})
Enter fullscreen mode Exit fullscreen mode

Resumable background download:

await downloadFile({
  fromUrl: 'https://example.com/model.gguf',
  toFile: `${documentDirectoryPath}/model.gguf`,
  resumable: true,
  background: true,
  connectionTimeout: 30000,
  readTimeout: 30000,
  onProgress: (bytesWritten, contentLength) => {
    console.log(bytesWritten, contentLength)
  },
})
Enter fullscreen mode Exit fullscreen mode

To resume after app relaunch, call the same request again with the same toFile and resumable: true. If the partial file is still there, the native client requests only the missing range.

const modelPath = `${documentDirectoryPath}/model.gguf`

await downloadFile({
  fromUrl: modelUrl,
  toFile: modelPath,
  resumable: true,
  background: true,
})
Enter fullscreen mode Exit fullscreen mode

Why not just fetch?

For small files, normal app-level download logic is fine.

For Orb, the files are not small. A local AI model download needs to survive boring real-world mobile behavior: bad Wi-Fi, app backgrounding, retries, foreground service expectations on Android, and users reopening the app later.

I wanted the heavy transfer to stay native:

  • no file bytes crossing the JS bridge
  • native networking behavior on each platform
  • progress events for the UI
  • partial-file recovery when the server supports Range
  • safe restart when resume metadata is wrong or unsupported

The package is not trying to be a full download manager yet. It does not currently expose task IDs, cancellation, a persistent queue, checksums, uploads, or a general request/response client. The current goal is narrower: make large direct-to-file downloads reliable enough to build real product flows around them.

Install

npm install react-native-client react-native-nitro-modules
Enter fullscreen mode Exit fullscreen mode
yarn add react-native-client react-native-nitro-modules
Enter fullscreen mode Exit fullscreen mode
bun add react-native-client react-native-nitro-modules
Enter fullscreen mode Exit fullscreen mode

For iOS:

cd ios
pod install
Enter fullscreen mode Exit fullscreen mode

Android should work through normal React Native autolinking.

Current status

The package is early, but usable for the download flow it was built for.

Current version: 0.0.3

Requirements:

  • React Native app
  • react-native-nitro-modules >=0.35.0 <0.36.0
  • Android and/or iOS native builds

The README has a demo showing Android resume behavior: start a native background download, send the app home, return to the transfer, force-stop the app, relaunch, and resume from the partial file.

Why I’m releasing it

Orb needed this because private offline AI has a surprisingly unglamorous bottleneck: getting the model onto the device cleanly. The AI part gets the attention, but the first user experience is often just a large download that must not fail in a stupid way.

If you are building a React Native app that downloads big files, I’d love feedback on the API before it grows too much.

The next useful pieces are probably:

  • task IDs and cancellation
  • persistent background task registry
  • request headers and response metadata
  • checksum verification
  • upload support
  • broader HTTP APIs beyond file download

GitHub: https://github.com/zraisan/react-native-client

NPM: https://www.npmjs.com/package/react-native-client

Orb on Google Play: https://play.google.com/store/apps/details?id=com.falaq.orb

Top comments (0)