“Boss, the package didn’t arrive on time — even though the driver swore he delivered it 30 minutes ago.”
That’s how my Sunday started. A customer complaint, a confused driver, and a baffled team. We were building a logistics app Express Logistics designed to track real-time delivery status. We thought everything was perfect.
Until this day.
I opened the dashboard. The driver’s last location was 4 km away from the drop point. No location update for the last 20 minutes.
But the driver? “I was there the whole time. I even saw the map stuck.”
Wait… the map was stuck?
Piecing the Puzzle Together
I opened the logs. The location update function was being called regularly as long as the app was open. But the moment the app went to the background, it stopped updating entirely.
We were using Timer.periodic()
to send location data every 30 seconds. And yes, we had tested it. But only while the app was open.
We never tested what happens when:
- The driver locks their phone
- Gets a call and switches apps
- Minimizes the app while waiting in traffic
That’s when I realized we weren’t fighting a bug. We were ignoring something fundamental: Flutter’s App Lifecycle.
Flutter Apps Aren’t Always “Alive”
Flutter apps have distinct states:
-
resumed
: App is visible and interactive -
inactive
: App is transitioning (like during a call) -
paused
: App is running in background -
detached
: App is running but no longer has a UI (rare)
Our timer ran during resumed
, but once it hit paused
, things went silent.
No logs. No errors. Just silence.
That’s because in many cases, Flutter doesn’t guarantee that Timer
, Stream
, or even Bloc
logic will keep working in the background. Especially on Android, where the OS aggressively kills background tasks to save battery.
Real Problem, Real Consequences
Imagine you’re building a ride-sharing app like Texi2Cardiff:
- The passenger books a ride.
- The driver accepts.
- But the driver then opens Google Maps or Spotify.
Your app moves to paused
. You think everything’s running. But:
- The driver’s location doesn’t update.
- The ETA never refreshes.
- The passenger sees the driver stuck.
Now both the rider and driver blame your app.
Or picture this: A delivery driver for Express Logistics is using a barcode scanner app to confirm delivery. They switch to the scanner. Your app pauses. The location update halts. And the delivery time is now inaccurate by 12 minutes.
That’s not a small bug. That’s a broken business promise.
Understanding Lifecycle Events
To handle these situations, you must observe lifecycle changes. Flutter provides an easy way:
class LifecycleAwareWidget extends StatefulWidget {
@override
_LifecycleAwareWidgetState createState() => _LifecycleAwareWidgetState();
}
class _LifecycleAwareWidgetState extends State<LifecycleAwareWidget>
with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
print("App state changed to: $state");
if (state == AppLifecycleState.paused) {
// Save state, pause timers, release camera, etc.
} else if (state == AppLifecycleState.resumed) {
// Restart tracking, resume streams, etc.
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text("Lifecycle Tracker")),
);
}
}
Fixing It the Right Way
We needed a background service that worked even when the app was paused.
Enter flutter_background_service
:
void onStart(ServiceInstance service) {
service.on('setData').listen((event) {
userId = event!['userId'];
token = event['token'];
});
Timer.periodic(Duration(seconds: 30), (_) async {
final location = await getCurrentLocation();
await sendToServer(location, userId, token);
});
}
With a proper foreground notification on Android, the OS lets the service live longer. Now even if the driver switches to Spotify or locks the phone, location updates continue.
Another Missed Detail: App Restart
Sometimes, the OS might kill your app completely due to memory pressure.
So, we started saving flags in SharedPreferences
like isTracking: true
and lastLocationTime
. That way, when the app restarted, it could ask:
“Was I tracking before? Should I resume it?”
This helped avoid silent failures when the app restarted without user interaction.
Don’t Assume, Test Real Scenarios
We began testing all real-world cases:
- Minimize app during location tracking
- Lock screen and wait 10 mins
- Switch to camera app mid-delivery
- Simulate calls and app interruptions
And in each case, we observed how our app behaved.
Lifecycle state changes became the backbone of every core feature.
What You Should Take Away
- Flutter apps don’t keep running unless you handle it intentionally.
- Observing lifecycle state is not a bonus its survival.
- Background services must be registered correctly, with foreground notifications.
- Save and restore state carefully. OS can kill you anytime.
- Never assume logic runs unless you’ve tested it while minimized, locked, or interrupted.
Final Thoughts
If your app stops updating when the screen is off, it’s not a bug it’s a blind spot.
Understanding Flutter’s app lifecycle helped us fix broken deliveries, restore driver trust, and build more resilient features.
It’s not just a topic to study. It’s a behavior to design around.
So next time you hit a strange pause in your app maybe it’s not your logic. Maybe it’s just your app trying to tell you: “I’m paused, help me resume.”
Hey everyone! We’re launching free tokens for all verified Dev.to authors. Claim your rewards here to see if you qualify (limited supply — act fast). – Dev.to Team