WebView to local app: testing on-device image recognition

 When I started developing K-Pop Celeb Like Me, the app was originally built as a WebView app. The main feature was simple: users could select or take a photo, and an image classification model trained with Teachable Machine would predict which K-pop celebrity looked most similar to them.

The web version already worked, so I initially considered keeping it as a WebView app. However, before developing more features, I wanted to check whether I could run the same model directly inside a React Native app.

There were two main reasons for this experiment. First, I wanted to reduce the app's dependence on a separately hosted website. Second, I wanted to see whether running image recognition locally would provide a better experience, especially on Android devices.

This post describes my initial traffic investigation, my first attempt using TensorFlow.js, and the performance improvement I observed after switching to TensorFlow Lite.


Why I considered moving away from WebView

My website was deployed through Netlify:

https://anchubbysite.netlify.app/

While investigating the hosting costs, I checked Netlify's free-plan information. At the time of my investigation, the free plan listed a monthly bandwidth allowance of 100 GB. I also checked my website's usage during December 2025.

The reported traffic was approximately 11.4 MB for the month. However, around 3 MB came from a separate webpage, which contained several images. Therefore, the total figure did not represent traffic generated by K-Pop Celeb Like Me alone.

During the same month, the number of app users increased from 37 on December 1 to 110 on December 30. That was an increase of 73 users.


These figures suggested that my website traffic was very small at that stage. I also expected browser caching to reduce repeated downloads of static resources such as HTML, JavaScript, CSS, and images.

However, I did not want to assume that every subsequent visit would generate zero traffic. Actual data transfer can vary depending on caching, resource changes, and whether a resource needs to be downloaded again. My monthly traffic figure also included another webpage, so it was not a reliable basis for predicting how many future users the app could support.

In other words, exceeding the free bandwidth allowance was not an immediate problem. The more important question was whether I needed to maintain a separate website for an app whose main feature could run locally.

The app did not need to upload users' photos to a server for image recognition. In my original web implementation, the selected image was read locally using JavaScript's FileReader. The image data was then used by the browser for prediction rather than being uploaded to my website.

This meant that the core image-recognition feature did not require a server to process each user's photo.

I decided to test a local implementation before investing more time in the WebView version. If the model could run efficiently on the device, I could simplify the app's architecture and reduce its dependence on the hosted website.


My first local implementation with TensorFlow.js

For the first experiment, I used TensorFlow.js with its React Native integration.

My test environment was based on Expo and React Native. I used the following packages for the initial implementation:

  • @tensorflow/tfjs

  • @tensorflow/tfjs-react-native

  • expo-image-picker

  • expo-file-system

  • expo-camera

  • @react-native-async-storage/async-storage

I also needed additional configuration to let Metro recognize the model's .bin weight file as an asset. During package installation, I encountered peer-dependency compatibility issues and used --legacy-peer-deps for some packages.

These setup details were part of my initial experiment, but my main concern was not whether the model could run. It was how long the prediction took.

I downloaded the TensorFlow.js model exported from Teachable Machine and bundled its model and weight files with the app. When a user selected a photo, the app read the image, converted it into a tensor, resized it to 224 × 224 pixels, normalized the pixel values, and passed it to the model.

The model then returned scores for the available celebrity classes. I displayed the class with the highest score as the predicted match.

The basic feature worked: I could select a photo, see its preview, and display the predicted celebrity. However, the performance was disappointing.


The prediction time was too long on Android

I tested the first implementation on both simulators and physical devices. The results were very different depending on the device and environment.

Test environment Prediction time
Android emulator Around 15 seconds or longer
Physical Android device Around 15 seconds on average, sometimes longer
iOS simulator 22 seconds initially, then 17, 13, and 9 seconds
Physical iPhone Usually within 3 seconds
Android WebView app Within 2 seconds
Website in Android browser Around 5 seconds initially, then within 1 second
Website in iPhone browser Around 15 seconds initially, then within 1 second
Website on MacBook Within 1 second

Note: These are approximate results from my own tests, not controlled benchmark measurements. Prediction time varied by device and test conditions.


These were observations from my own tests, not controlled benchmark results. The image, device state, model initialization, caching, and runtime environment could all affect the timing.

Even with that limitation, the result was clear enough for my project: the TensorFlow.js implementation was not consistently fast enough on Android. A prediction that takes around 15 seconds makes a simple photo-matching app feel slow.

The web version was much faster after its initial run in several of my tests. I could not conclude from these measurements alone exactly which component caused the difference, but I knew that the first local implementation needed further investigation.

I decided to try another inference engine instead of continuing to optimize the TensorFlow.js version.


Switching to TensorFlow Lite

My second experiment used TensorFlow Lite (TFLite), the model format available from Teachable Machine's export options.

The important change was not simply changing the model file extension. I also changed the runtime used to execute the model.

In the first implementation, I used TensorFlow.js and @tensorflow/tfjs-react-native. In the second, I used react-native-fast-tflite to load and execute a .tflite model through a native React Native module.

The main packages for this version were:

  • react-native-fast-tflite

  • react-native-nitro-modules

  • expo-image-picker

  • expo-file-system

  • expo-image-manipulator

  • expo-asset

  • jpeg-js

  • base64-js

I exported the model from Teachable Machine in TensorFlow Lite format and included the model and label files in the app's assets. I also configured Metro to recognize the .tflite and .txt file extensions.

The image-processing flow changed as well. Before prediction, the selected image was resized to 224 × 224 pixels. I then decoded the JPEG data, converted the RGB pixel values into the input format expected by the model, and passed the resulting buffer to the TFLite runtime.

This version used a native inference module instead of the TensorFlow.js execution path from my first experiment. I wanted to see whether that change would improve prediction time on both platforms.

One practical detail was that I could not test this implementation in Expo Go. The native module required a native app build, so I tested it using commands such as npx expo run:android and npx expo run:ios.

This was an important distinction in my development process: installing a package in an Expo project does not necessarily mean its native functionality can run inside Expo Go.


The result after switching engines

The difference was substantial in my tests.

Test environment Prediction time
Android emulator Within 2 seconds
iOS simulator Within 2 seconds
Physical Android device Within 1–2 seconds
Physical iPhone Within 1–2 seconds

Note: These are approximate results observed during my tests. Actual performance may vary depending on the device, model, and runtime configuration.


The TensorFlow Lite version completed prediction within approximately two seconds on both simulators and physical devices in my tests.

The screenshot shows the basic result screen from my local implementation. The app displays the selected photo on the iOS side and a predicted celebrity match on the Android side. At this stage, I was checking that the main flow worked across platforms before continuing with the rest of the app.

The export screen shows the TensorFlow Lite option I used for the second experiment.

These results do not prove that TensorFlow Lite will always be faster for every model or device. I did not run a controlled benchmark in which every variable was held constant. However, the improvement was large enough to make TensorFlow Lite the practical choice for this app.


What I learned from this test

This test changed two decisions about K-Pop Celeb Like Me.

First, I confirmed that the app's main feature could run locally without sending the selected photo to my website for prediction. That made a local app a reasonable architecture for my use case.

Second, I learned that a model's performance depends not only on the model itself but also on the runtime used to execute it. My first implementation worked, but its prediction time on Android was too long. Switching to TensorFlow Lite gave me a much more responsive result in my tests.

The initial traffic investigation also helped me clarify my motivation. My website was not close to its reported bandwidth allowance, and I could not justify the migration simply by claiming that hosting costs were already too high. Instead, I wanted to avoid maintaining a separate web deployment when the app's main functionality could run on the device.

The migration was therefore both an architectural decision and a performance experiment.

With the basic image-selection and prediction flow working on Android and iOS, I could move on to developing the actual app experience, including the results screen and other features.

Comments

Mostly viewed post

My Mobile App Projects

About this blog

How I prepared my first web app for search engines