Run a local LLM on iOS & Android from React Native — a privacy-first AI notes app where every AI feature runs on-device, fully offline.
This is a working demo / reference for shipping on-device generative AI in a React Native app. The model (a Qwen 2.5 GGUF) runs locally through llama.rn (a llama.cpp binding) — your notes never leave the phone, and it works with no network once a model is downloaded.
Not Expo — this is bare React Native (CLI) with the New Architecture (TurboModules/Fabric), because the LLM needs native modules.
- 🧠 On-device LLM inference on both iOS and Android via
llama.rn(summarize, translate, proofread, tone-shift, continue writing, chat). - 🔒 Offline & private — no server, no telemetry; text stays on the device.
- 📱 One codebase, both platforms — built and verified on an Android emulator (incl. real on-device generation) and the iOS simulator.
- 🧩 Clean architecture — a pure, framework-agnostic, unit-tested core
behind a single
LlmEngineseam, so the AI logic is testable without a device. - 📥 OS integration — receive shared text (Android
ACTION_SEND, iOS Share Extension), deep links (ainote://), localization (English / 日本語).
The app's display name is "AI Memo"; this repository is the demo project.
| Notes | AI actions on a note | Settings & model |
|---|---|---|
![]() |
![]() |
![]() |
- Notes — create / edit / search / tag / pin / sort; trash with restore and undo; long-press a card for quick actions.
- AI actions (all on-device): one-line / 3-line / detailed summary, TODO extraction, proofread & rewrite, translate (EN↔JA), tone change (formal / casual), continue writing, title generation.
- Streaming output token-by-token, with a truncation notice when the model hits its token cap; apply a result back to the source note or save as new.
- Chat — a local assistant with on-device history.
- Share-to-app — add a note from another app's share sheet.
- Model manager — download / select / delete GGUF models.
- Settings — model, theme (light / dark / system), language, data wipe.
React Native 0.76 (bare CLI, New Architecture) · llama.rn (llama.cpp) ·
react-native-paper (Material 3) · react-navigation (native-stack + bottom-tabs) ·
react-native-fs · AsyncStorage · TypeScript · Jest + testing-library.
A pure, framework-agnostic core is separated from the React Native and native-module layers, so the business logic is fully unit-testable without a device or an LLM.
src/
core/ # pure TypeScript — unit-tested, no RN imports
llm/ # LlmEngine interface, prompts, output parsing, tasks
notes/ # NoteStore (CRUD/trash/pin), search & sort, formatting
settings/ # SettingsStore
chat/ # ChatStore (persisted thread)
models/ # model catalog + download state machine
storage/ # KeyValueStorage port + in-memory impl
app/ # React Native UI (paper + react-navigation)
screens/ # Notes / Editor / AiResult / Chat / Settings / Trash
components/ # presentational pieces (StreamingResult, ChatBubble)
services/ # context providers wiring stores + engine
navigation/ # root stack + bottom tabs
i18n.ts # tiny en/ja string table + useT() hook
native/ # device-only adapter (llama.rn) — not unit-tested
tests/ # Jest unit tests for the core (+ tests/app for RNTL)
The key seam is the LlmEngine interface (src/core/llm/types.ts): on device
it is backed by LlamaRnEngine (llama.rn), and in tests by MockLlmEngine.
Stores depend on a narrow KeyValueStorage port (AsyncStorage on device,
in-memory in tests). Time and id generation are injected, so all behaviour is
deterministic under test.
The app ships a single default model — download it once from Settings → AI model to enable the AI features:
| Model | Size (Q4_K_M) | Min RAM | License | Commercial use |
|---|---|---|---|---|
| Qwen2.5 1.5B Instruct (default) | ~1.1 GB | 3 GB | Apache-2.0 | ✅ allowed |
It's the default because it is Apache-2.0 (commercial-friendly) and small enough
for most phones. The catalog is just an array in src/core/models/catalog.ts —
add more GGUF models there (e.g. Qwen2.5 3B under the Qwen Research License, or
Llama 3.2 3B under the Llama Community License); mind each model's license before
shipping.
Requires Node.js ≥ 18. Full native setup (Android SDK/NDK, CocoaPods, required
dependency pins, App Group / entitlements) is in
docs/native-setup.md.
npm install
# Core checks (fast, no native build):
make lint # ESLint over core + tests
make test # Jest unit tests (pure core)
make test-app # React Native component tests (testing-library)
make build # type-check core + app/native layers
# Run on a device/simulator (after the native setup in docs/native-setup.md):
npm run android # Android (emulator or device)
npm run ios # iOS (simulator or device)On first launch no model is downloaded yet — open Settings → AI model and download one (Wi-Fi recommended; ~1.1 GB for the default Qwen2.5 1.5B). AI features become available once a model finishes downloading and loading.
Using the engine directly:
import { LlamaRnEngine } from './src/native/LlamaRnEngine';
import { NoteAi } from './src/core';
const engine = await LlamaRnEngine.load(modelFilePath);
const ai = new NoteAi(engine);
const { text } = await ai.summarize(noteBody, 'threeLines');Both platforms are verified building and running:
- Android (emulator, API 35 arm64): full app — notes, search, tags, pin, trash, settings, model download → llama.rn load → on-device generation — all confirmed end-to-end.
- iOS (iPhone 17 Pro simulator): builds (llama.rn C++ compiled), runs, full
UI + bottom tabs + app icon + launch screen. Includes a Share Extension;
shared text reaches the app via the
ainote://deep link and a shared App Group container.
Remaining: on-device QA on physical hardware (large-model memory entitlement for real iOS devices) and a real-device inference pass.
This is a reference/demo, not a store-ready build:
- Android release signing uses the throwaway debug keystore
(
android/app/build.gradle). AreleaseAPK/AAB built from this repo is not distributable — generate your own keystore (and move secrets to a Gradle properties / env file) before shipping. - iOS bundle identifier & App Group still use the React Native template
default (
org.reactjs.native.example.AiNoteOfflineAiMemo). Fine for a demo; change it (and the matching App Group used by the Share Extension) before distributing. npm auditreports some moderate advisories from transitive dependencies of the React Native 0.76 toolchain (CLI / Metro). They are not in the app's runtime path; clearing them requires a major RN/toolchain bump, tracked separately from this demo.- Hexagon NPU backend libraries are copied from
llama.rninto the app's Android assets at build time (android/app/src/main/assets/ggml-hexagon/, git-ignored — not vendored in this repo); seeNOTICE. They are unused on non-Qualcomm SoCs (inference falls back to CPU).
MIT — see LICENSE. Third-party components (llama.rn, llama.cpp,
the Hexagon backend libraries) are listed in NOTICE. Downloaded
models carry their own licenses — see the table above.


