Direct answer
Treat LiteRT-LM setup as a chain of separately verifiable steps: choose an artifact that the selected runtime supports, add and resolve the Android library, make the model path readable, initialize the engine away from the main thread, run one representative request, and close every native resource through a clear owner. Google’s Kotlin getting-started guide documents the Android artifact coordinate, EngineConfig, initialization, conversation creation, and explicit close behavior. It is a runtime integration reference, not proof that every model file, phone, accelerator, or product feature works. Record the resolved dependency version in a release build rather than relying on an unrecorded moving version label. The model artifact and the runtime package are separate inputs: changing either can change behavior, so keep their identities together in a test record. Start with CPU as a diagnostic baseline only when the selected package and artifact support that route; do not publish a speed comparison from the baseline. Keep initialization and inference off the main thread, expose loading and failure states, and make cancellation and close behavior explicit. A working Gradle sync proves dependency resolution. A successful APK build proves compilation and packaging. Neither proves the native library loads on a physical device, the artifact opens, a conversation completes, or a user task meets its acceptance criteria. The smallest useful setup test therefore includes an exact artifact, a clean install, engine initialization, one fixed prompt fixture, a complete output capture, cancellation or close, and a second run after cleanup. For an application, the engine should have one documented owner rather than being created from a Composable or each screen recomposition. A repository or process-level coordinator can serialize native access while a ViewModel owns task state and the UI observes that state. The boundary should distinguish artifact unavailable, initialization in progress, ready, inference running, failed, cancelled, and closed. If the runtime call is blocking or emits a stream, bridge it to a lifecycle-aware coroutine without allowing a screen collector to own the native engine lifetime. Preserve the exact error stage; a missing file, unsupported package, native load failure, and empty model response are different diagnostic outcomes. Do not infer NPU or GPU availability from a device name, a dependency, or a configuration flag. Backend setup may require additional libraries, runtime support, and an exact compatible model. A backend request is only a request until runtime evidence confirms what executed. Keep the first integration narrow: one artifact, one runtime revision, one backend, and one task. Add optional backends, updates, or concurrent conversations only after the baseline path has tests for resource release and failure recovery. For Cove specifically, the repository’s pinned model metadata describes a development artifact and does not establish public APK availability or broad device support. Use these four stages as a reviewable setup protocol: 1. Freeze inputs. Record the model repository revision or immutable source, file format, expected path, runtime dependency coordinate and resolved version, Android Gradle configuration, and the build variant. Check that the source documentation actually describes the library you are integrating; do not mix a MediaPipe example with a LiteRT-LM package merely because both are Google AI Edge tools. 2. Prove packaging and access. Build the debug variant, install it cleanly, verify the final model path and file-open result, and record storage or permission decisions that affect that path. A filename extension is not proof of format compatibility. If the artifact is absent, the setup test should stop at that explicit state rather than generate a misleading inference error. 3. Exercise one task. Initialize on a background dispatcher, create one conversation, submit a fixed non-sensitive fixture, collect the complete response or stream, and validate required output properties. Log artifact identity, runtime version, backend request, actual observed backend if exposed, and failure stage. Keep private user content out of diagnostic records. 4. Close and repeat. Cancel or finish work, close conversation and engine according to the library’s ownership contract, return UI state to idle or a visible error, then run the fixture again. Check for duplicate engines after screen recreation, stale collectors, and native resources retained after close. A setup checklist becomes release evidence only when these behaviors are repeatable for the declared configuration. It does not become a device compatibility claim unless there are device-specific records for the exact artifact and workload.
A first Android integration needs a trustworthy smoke test
A team has added the LiteRT-LM Android dependency to a new app module and has a candidate model file. Gradle sync succeeds, but no one has yet verified the file format, native initialization, conversation response, cancellation, or close path on the intended test environment.
Decision
Keep the integration to one documented artifact and one runtime path. Treat build, native load, inference, and product acceptance as separate gates.
Expected outcome
A reviewer receives a reproducible record that says exactly which setup stage passed and where the path remains unverified, without turning source code into a public device-support promise.
Evidence to collect
The claim becomes reviewable only when the following evidence is attached to the same artifact and test run:
- Pinned artifact identity
- Runtime revision
- Load, cancel, close, and fallback results
A runtime comparison is publishable only when artifact identity, build configuration, selected backend, task fixture, and failures are recorded.
Implementation workflow
- Pin the integration inputs. Record the package coordinate and resolved version, source revision for the model artifact, declared file format, build variant, target SDK, and runtime configuration. Keep the documentation link beside the exact coordinate. If the source guide covers another inference API, stop and resolve that mismatch before copying code.
- Separate build from native runtime checks. Compile and install the app, then check file existence, readable path, initialization result, and conversation creation as separate observations. Capture the first failing stage. Do not label a successful APK build as proof of model compatibility or successful on-device inference.
- Run a fixed representative fixture. Use a short, non-sensitive prompt whose expected properties are defined in advance. Run it through the application-owned repository or coordinator, collect the complete response, and validate the output boundary. Record whether the selected backend was merely requested or actually reported by the runtime.
- Close, recreate, and repeat. Cancel an in-progress request where the API permits, close conversation and engine through their documented owners, recreate the screen or task owner, and run the same fixture again. Check that recomposition or activity recreation does not create duplicate native engines or leave stale output collectors.
Keep each transition observable. A failure should identify the stage, artifact, runtime, and recovery action without logging private user content.
Acceptance criteria
- The test record identifies the artifact, runtime dependency version, build variant, and initialization stage without publishing an unsupported compatibility verdict.
- The representative task completes or produces a classified failure, and cancellation returns an explicit state rather than silently leaving work running.
- Conversation and engine close paths are exercised, followed by a second successful or clearly classified run.
Decision table
| Dimension | Choice or evidence | How to interpret it |
|---|---|---|
| Gradle sync or APK build | Dependency and packaging check | Confirms compile-time integration only; it does not open the native runtime or model file. |
| Engine initialization | Native runtime smoke check | Confirms startup for the recorded artifact and configuration, not the entire user task. |
| Representative end-to-end fixture | Product-path acceptance check | Covers the selected task and output rules, but only for the recorded setup and fixture. |
Failure patterns to prevent
- Loading in UI recomposition
- Treating compile success as runtime proof
Also prevent silent fallback, unpinned artifacts, missing cancellation, and conclusions that combine unlike configurations. Store unsuccessful runs alongside successful ones.
Minimum reproducibility record
| Layer | Record |
|---|---|
| Device | Manufacturer, model, chipset, RAM class, operating-system build |
| Software | Application version and git commit |
| Model | Family, variant, revision, format, file length, hash, quantization |
| Runtime | Name, revision, requested backend, observed backend evidence |
| Workload | Fixture revision, input hash, prompt hash, output policy |
| Outcome | Completed, failed, cancelled, fallback, and privacy-safe diagnostics |
Release checklist
- ☐ The primary query is answered without an unsupported number.
- ☐ Every artifact and runtime is pinned.
- ☐ The representative task and failure policy are explicit.
- ☐ Lifecycle, cancellation, cleanup, and fallback are tested.
- ☐ User-content and network boundaries are documented.
- ☐ Result wording applies only to the recorded configuration.
- ☐ The page links to raw method or evidence when results are added.
Sources and related evidence
- LiteRT-LM repository
- Gemma run guide
- Google AI Edge LiteRT-LM Kotlin API getting started
- Google AI for Developers Gemma run guide
- Android Developers processes and threads
Chinese deployment and troubleshooting content is organized in the .奇连 AI 端侧专题.
Frequently asked questions
Does adding the LiteRT-LM dependency mean the model is supported?
No. The dependency exposes a library API; the artifact format, runtime revision, backend, Android packaging, and model-specific support still need to be checked. Preserve a test record for the exact combination rather than generalizing from compilation.
Should engine initialization run on the main thread?
The official Kotlin guide warns that initialization can take significant time and recommends a background thread or coroutine. Keep UI state observable while that work runs, and do not make a Composable own a native engine that may be recreated.
Can I claim GPU or NPU acceleration because I configured that backend?
Not from configuration alone. A request to select a backend is not evidence that the runtime executed there. Record the runtime’s available confirmation and test the exact artifact and device; otherwise describe the backend as requested or unverified.