A Flutter app for low-end Android phones and slow networks usually fails on slow screens, confusing steps and bad releases, and a million users multiply each one. What worked for me was to protect the core flow from every new feature, ship through staged rollouts with automated checks, and wire crash and performance monitoring before launch.
I learned this leading the mobile side of Baideshik Rojgar, a Flutter app with over 1,000,000 installs on Google Play at a 4.4 rating (as of 17 Oct 2026). Some of what follows I got right. The monitoring part I got wrong, and I say so below.
Who the users are, and why it is the hardest audience
Baideshik Rojgar, also known as Sajha Jobs, is the app Nepali migrant workers use to find jobs abroad. Hundreds of thousands of Nepalis leave each year to work in the Gulf, Malaysia and beyond, and the information they need was scattered and often wrong. I led mobile development from Oct 2023 to Jun 2024 as a senior mobile developer at E-Signature in Kathmandu. One Flutter codebase, iOS and Android.
The users are on entry-level Android phones and patchy mobile data, they have little patience for anything confusing, and a mistake can cost them their savings. A developer's phone on office wifi tells you almost nothing about how the app feels to them.
We tested on cheap Android phones of the kind our users carried, on mobile data, before each release.
Flutter app performance on low-end Android: what changes at a million installs
At a million installs every rough edge is a thousand support messages, and every release is a risk. A screen that takes a beat too long on a cheap phone is a reason for someone to give up on a job search.
On cheap hardware the usual Flutter costs show up first: large images decoded at full size, long lists built all at once, heavy work during startup, and network calls that assume a good connection. These are the general pressure points, not ones we measured. What we did about each one:
- Images: served resized for a phone screen, decoded at the size they display, and cached on the device so a second visit did not download them again.
- Lists: the job list loaded one page at a time from the API as the user scrolled, and a lazy list builder built only the rows on screen.
- Startup: only what the first screen needed ran before it drew. Ads, the radio player and anything else waited until after.
- Slow or dropped connections: the last listings fetched stayed on the device, so a dropped connection still showed jobs, with a note that they might be out of date. Failed requests retried with a backoff, and if they kept failing the user got a clear retry button instead of an endless spinner.
Adding payments, ads and radio without breaking the core flow
Over that period we added three things to an app that already worked: online payments, ads and a live radio stream. I treated the job-search flow as the product and everything else as a guest in it. A new feature had to prove it did not slow down or confuse that flow before it shipped.
Payments went through eSewa and Khalti, the two rails Nepali users actually have, so offering anything else would have been pointless. On patchy data, the dangerous moment is a payment that succeeds at the provider while the phone loses the connection. The app never marked anything paid on the phone's word. The server checked the transaction with eSewa or Khalti before anything unlocked, and until then the user saw the payment as pending, with a way to check again once the connection came back.
Ads pay for a free app, and they also cost data, memory and attention on exactly the phones this audience uses. Ads stayed out of job details and the application steps entirely and lived in the browsing screens around them. They loaded after the content on the screen, so a slow ad never held up a listing.
Streaming live radio on a weak connection tests the patience of the user and the phone. The stream buffered before it started, kept playing in the background with a notification to control it, and when the connection dropped it stopped and offered to reconnect.
I cannot give you a before-and-after performance number for any of these three. We were not measuring it closely enough at the time, which is the regret further down.
The release discipline that started here
I set up automated releases so a fix could reach users within hours. At this scale a bug fix needs to arrive fast, and a bad build needs to reach as few people as possible before anyone notices.
Three habits came out of this project, and I still use all three.
With staged rollouts, a new build goes to a slice of users first and widens only if nothing breaks.
Automated release checks run in the pipeline before a human decides to ship the build. They were the boring ones: the app builds for both platforms, the analyzer and the tests pass, and the build is signed for release.
A verification script runs before every upload, every time, so the release does not depend on someone remembering a checklist at the end of a long day. It checked that the version and build numbers had gone up, that the build was signed with the release key, and that it pointed at the production backend.
What I would change: monitoring before reviews
Add crash and performance monitoring earlier. We learned about slow screens from reviews before we learned from data, and reviews are a poor source: they arrive late, describe a feeling instead of a screen, and come from the few people who bother to write one. The people who gave up and uninstalled never write anything.
On the products I build now, analytics and crash reporting are wired from day one, with the numbers the team will steer by.
A short checklist for a Flutter app on cheap Android phones
- Test on the cheapest phone your users actually own, on mobile data, not on yours.
- Name the core flow in writing and make every new feature prove it does not slow it down.
- Wire crash and performance monitoring before the first public release.
- Ship through staged rollouts, automated release checks and a verification script before every upload.
- Set up a release path that gets a fix to users within hours.
If you are building one
If you are shipping a Flutter app to a mass-market audience and want this discipline in place from the first release, this is what I build.