Fixing a TensorFlow Lite model loading error in a preview build

 In my previous post, I described how I moved K-Pop Celeb Like Me from a WebView implementation to a local React Native app. I also explained why I switched from TensorFlow.js to TensorFlow Lite after finding that image prediction was too slow on Android.

After getting the basic image recognition feature working, I moved on to testing preview builds on real devices. That was when I encountered another problem: the model worked during development, but the app could not load it in the Android preview build.

In this post, I will explain the error, the cause I identified, and the change that allowed the model to load successfully in both Android and iOS preview builds.


The model worked during development but failed in the APK

My app uses image classification models exported from Teachable Machine. I bundled the TensorFlow Lite model files and their corresponding label files inside the project.

The model-loading function initially looked like this:

export async function loadModel(gender: Gender) {
  return loadTensorflowModel(modelFiles[gender], []);
}

The function selected the appropriate model based on the user's gender selection and passed the bundled asset directly to loadTensorflowModel().

This worked in my development environment. However, after building an Android APK for testing, the app displayed an error instead of loading the model.

The first alert said:

Failed to load model

Please check the files in assets/model.

When I tried to continue with the photo-selection flow, another alert appeared:


Preparing Model

The model is not loaded yet.

These messages represented two parts of the same problem. The model initialization had failed, and when the app subsequently tried to use the model, it found that the model was still unavailable.

The model files existed in the project, so I needed to investigate why the release-style build could not load them.


Investigating the difference between development and a preview build

The important clue was that the error appeared in the built APK rather than during ordinary development.

A bundled asset can be represented differently depending on the runtime environment. During development, an asset may be served through the Metro development server. In a built Android application, the same asset may instead resolve to an Android resource URI.

My original code passed the result of require() directly to loadTensorflowModel(). That approach did not account for the difference in how the model asset was made available in the preview build.

Based on the investigation, I identified the asset URI handling as the likely cause. I had not captured a device-side logcat exception confirming every internal step, so I treated this as the cause supported by the investigation and the successful fix, rather than as a fully confirmed low-level diagnosis.

The solution was to prepare the model asset as a local file before passing it to the TensorFlow Lite library.


Preparing the model as a local file

I already used expo-asset to handle bundled label files. I applied the same general approach to the model file.

First, I imported Asset from expo-asset:

import { Asset } from "expo-asset";

Then I changed the model-loading function:

export async function loadModel(gender: Gender) {
  const asset = Asset.fromModule(modelFiles[gender]);

  await asset.downloadAsync();

  if (!asset.localUri) {
    throw new Error(
      `Unable to prepare the ${gender} TensorFlow Lite model.`
    );
  }

  return loadTensorflowModel({ url: asset.localUri }, []);
}

The important change is that I no longer pass the bundled model asset directly to loadTensorflowModel().

Instead, the function follows these steps:

  1. Select the bundled model file for the requested gender.

  2. Use Asset.fromModule() to create an Expo asset reference.

  3. Call downloadAsync() to ensure the asset is available locally.

  4. Check that asset.localUri exists.

  5. Pass the local file URI to loadTensorflowModel().

The local URI gives the TensorFlow Lite library a file location it can read directly.

The if (!asset.localUri) check is also useful because it prevents the function from silently continuing when the local asset could not be prepared.

This change was small, but it addressed the difference between the asset reference available during development and the local file needed by the native model-loading library in my preview build.


Why I used localUri instead of the original asset reference

The key distinction is between a reference to a bundled asset and a local file URI.

In my original implementation, I assumed that the value returned by require() could be passed directly to the TensorFlow Lite loader in every environment. My preview-build test showed that this assumption was not reliable for my setup.

By using Asset.fromModule() and downloadAsync(), I explicitly prepared the asset before loading it.

I also checked the expo-asset documentation while investigating the issue. The documented asset API provides localUri for accessing the locally available asset after it has been downloaded or prepared.

Documentation: Expo Asset

This was a more targeted fix than changing unrelated Android build settings. My investigation pointed to the way the model URI was passed to the loader, so I focused on that path rather than adding configuration that did not address the identified problem.


Testing the fix

After changing loadModel(), I rebuilt the Android app and tested it again.

The model loaded successfully in the Android preview build. I also tested an iOS preview build, where the model-loading flow worked as expected.

My build command for Android was:

npx eas build --platform android --profile preview

The important part of this test was that I was no longer relying only on the development environment. I was testing the packaged application, where the asset-handling behavior had originally caused the problem.

The screenshot shows the updated loadModel() function, including the asset preparation step and the use of asset.localUri.


What I learned

This debugging experience reminded me that a feature working in development does not guarantee that it will work in a packaged mobile app.

When I first saw the error, I suspected that the model files might be missing or incorrectly configured. However, the issue was not solved by simply checking that the files existed in the project. I needed to consider how the native runtime accessed those files after the app had been built.

The practical lesson from this case was to check the complete asset-loading path:

  • Is the model bundled with the application?

  • How is the asset represented in the current environment?

  • Does the native library accept that representation?

  • Can the asset be prepared as a local file before it is loaded?

  • Does the fix work in a built application, not just in development?

For my app, explicitly preparing the TensorFlow Lite model as a local file resolved the issue in both Android and iOS preview builds.

This was a useful step toward making K-Pop Celeb Like Me reliable outside the development environment. It also reinforced why I test preview builds on actual devices before considering a feature ready for release.

Comments

Mostly viewed post

My Mobile App Projects

About this blog

How I prepared my first web app for search engines