Mobile App Lifecycle Basics
Understand mobile app lifecycle basics in this Mobile App Development tutorial. Learn core concepts, hands-on steps, and troubleshooting for your Python apps.
Focus: understand mobile app lifecycle basics
You've built a Python web app that runs forever in a terminal, but the moment you try to run it on a phone, you realize your assumptions are wrong. Mobile operating systems kill apps when they're in the background, interrupt them with phone calls, and rotate the screen underneath them. That's why mobile app lifecycle basics are the first thing you need to master after writing your first lines of Python mobile code. Ignore the lifecycle, and your app will crash, lose user data, or drain the battery without you understanding why.
The problem this lesson solves
Unlike a desktop or server process, a mobile app doesn't have a stable, continuous existence. On iOS and Android, the operating system (OS) controls your app's execution and can pause, resume, recolor, or kill it at any moment. Here's what happens when you don't handle lifecycle events:
- Data loss: Your app is in the background, the OS kills it, and the user's form input or game state is gone.
- Battery drain: Your app keeps running timers or GPS updates while in the background, and the user's battery plummets.
- Crashes: Your app tries to access the GPU or camera right after the OS has suspended it.
- Bad UX: The app freezes when a phone call comes in, or resets its UI when the screen rotates.
This lesson gives you the mental toolkit to handle these transitions gracefully. You'll learn the mobile app lifecycle concept, map it to Python frameworks, and write code that reacts to every state change.
Core concept / mental model
Think of your app as an actor on a stage, and the OS as the stage manager. The manager can say:
- "You're on!" (foreground, active)
- "Step backstage, but stay ready." (background, might be killed)
- "Go home and rest." (terminated)
The app lifecycle is the series of states an app moves through between launch and termination. The two most important states are foreground (visible and interactive) and background (hidden but still alive). Between them, there are transition events that your code can hook into.
Here's a word-diagram of the general lifecycle:
Launch
│
▼
Active (foreground)
│ ┌──────────────────────┐
├── Phone call ───────► Paused / Background │
│ └──────────────────────┘
│ │
└── Resume ─────────────────┘
│
▼
Terminated (by OS or user)
In Python, two popular mobile frameworks give you built-in hooks: Kivy and BeeWare (Toga). Kivy has an App class with on_start(), on_stop(), and on_resume(). BeeWare's Toga has similar on_start, on_stop, and on_resume callbacks.
How it works step by step
Let's break down the typical lifecycle sequence for a Python mobile app:
- Launch — The user taps the app icon. The OS creates a process and instantiates your app's main class.
- Start — Your app's
on_start()method runs. This is where you initialize global state, set up databases, and load configuration. - Active/Resume — The app is now visible and interactive. For Kivy, this is
on_resume(); for BeeWare, alsoon_resume(). You might start animations or refresh data from a server. - Pause — Something interrupts the app: a phone call, the user presses the home button, or a system dialog appears. The OS calls
on_pause()(Kivy returns abool, Toga useson_pause). You should save user progress, stop heavy operations, and release exclusive resources like the camera. - Resume — The interruption ends. The OS calls
on_resume()again, and you can restore the UI and continue. - Stop — The app goes fully into the background (e.g., user switches to another app). In Toga, this is
on_stop; in Kivy, you only haveon_stop()when the app is about to close. - Destroy/Terminate — The OS kills the app (user swipes it away, or the system reclaims memory). There's no reliable callback for this in most frameworks — you must rely on
on_pause()andon_stop()to save state.
The key takeaway: The OS can kill your app at any time after it goes to the background. You must save critical state in on_pause(), not in on_stop().
Hands-on walkthrough
Let's see this in action. We'll build a simple Kivy app that tracks its lifecycle events and prints them.
First, install Kivy if you haven't:
pip install kivy
Create lifecycle_demo.py:
from kivy.app import App
from kivy.uix.label import Label
from kivy.clock import Clock
class LifecycleApp(App):
def build(self):
self.event_log = []
self.label = Label(text="Lifecycle Demo")
return self.label
def on_start(self):
print("on_start called")
self.event_log.append("start")
# Start a dummy timer to simulate background work
self.timer = Clock.schedule_interval(self.tick, 1.0)
def on_pause(self):
print("on_pause called")
self.save_state(self.event_log)
# Returning True allows the app to continue in the background
return True
def on_resume(self):
print("on_resume called")
self.event_log.append("resume")
self.restore_state()
def on_stop(self):
print("on_stop called")
Clock.unschedule(self.timer)
self.save_state(self.event_log)
def tick(self, dt):
print("tick", dt)
def save_state(self, state):
# In a real app, save to a file or database
with open("state.txt", "w") as f:
for item in state:
f.write(item + "\n")
print("State saved")
def restore_state(self):
try:
with open("state.txt") as f:
self.event_log = [line.strip() for line in f]
print("State restored:", self.event_log)
except FileNotFoundError:
self.event_log = []
if __name__ == "__main__":
LifecycleApp().run()
If you run this on a desktop with Kivy, you'll see the window, and on closing it you'll see:
on_start called
on_stop called
State saved
But on a mobile device, when you press the home button, you'll see on_pause and possibly on_resume when you return. This demonstrates the core flow.
For BeeWare, the pattern is similar. Download the BeeWare template (briefcase new), then edit the main app file:
import toga
from toga.style import Pack
from toga.style.pack import COLUMN, CENTER
class LifecycleApp(toga.App):
def startup(self):
self.main_box = toga.Box(style=Pack(direction=COLUMN, alignment=CENTER))
self.label = toga.Label("Lifecycle Demo", style=Pack(padding=10))
self.main_box.add(self.label)
self.main_window = toga.MainWindow(title=self.formal_name)
self.main_window.content = self.main_box
self.main_window.show()
def on_start(self):
print("toga on_start")
def on_resume(self):
print("toga on_resume")
def on_pause(self):
print("toga on_pause")
# Save state
return True
def on_stop(self):
print("toga on_stop")
def main():
return LifecycleApp("Lifecycle", "org.example.lifecycle")
Run with briefcase run android and watch the console when you switch apps or rotate the screen.
Compare options / when to choose what
You have two main Python mobile frameworks, and each handles the lifecycle differently. Here's a comparison:
| Framework | Key lifecycle methods | Platform approach | Best for |
|---|---|---|---|
| Kivy | on_start, on_pause (must return bool), on_resume, on_stop |
Cross-platform (Android, iOS, Windows, Linux) with a single codebase | Apps that need custom UI, games, or rapid prototyping |
| BeeWare (Toga) | on_start, on_pause, on_resume, on_stop |
Uses native widgets on each platform | Apps that need a native look and feel, or want to target iOS/Android with native widgets |
| OS | Typical states | Notes |
|---|---|---|
| Android | onCreate, onStart, onResume, onPause, onStop, onDestroy |
OS may kill background apps at any time; onSaveInstanceState is critical |
| iOS | applicationDidFinishLaunching, applicationWillResignActive, applicationDidEnterBackground, applicationWillEnterForeground, applicationWillTerminate |
Similar concepts; background execution is limited |
Pro tip: Use Kivy when you want to reuse your Python knowledge and build game-like UIs; use BeeWare when you need platform-native controls or when you're targeting iOS more seriously.
There's also the option of using PyBridge or pyjnius to hook directly into platform lifecycle callbacks — but that's an advanced path that locks you into a single OS.
Troubleshooting & edge cases
- App doesn't resume properly. If
on_pausereturnsFalse(Kivy), the app doesn't go to background and keeps running. That can drain battery. Always returnTrueunless you have a reason. - State not saved when OS kills the app. The OS may not call
on_stop; it just kills the process. So save everything inon_pause, noton_stop. - Crash on resume after screen rotation. Rotation destroys and recreates the activity. In Kivy, you must handle
on_resumeand reconstruct your UI state. Store state inon_resume-triggered data. - Timers keep running in background. Use
Clock.unschedule()insideon_pauseto stop timers. - File access in background. On iOS, you may have limited access to files when the app is backgrounded. Instead, use platform-specific storage APIs.
- Debugging with print statements. Print statements don't always appear in the system log on Android. Use
adb logcatwithSystem.outor Python'sloggingmodule to see output.
What you learned & what's next
You now understand the mobile app lifecycle basics: what states your app goes through, which events to hook into, and how to save and restore state. You've seen it in Python with Kivy and BeeWare, and you know the critical rule — save in on_pause, not on_stop. This foundation is essential for building reliable mobile apps.
Next in the track, you'll learn how to handle user input and touch events, which builds directly on the lifecycle concept — you'll know when to apply listeners and when to release them.
Practice recap
Take the simple Kivy lifecycle demo from this lesson and run it on Android using buildozer (or on desktop). Add a counter that increments on your screen, then press the home button and wait a few seconds. Return to the app and verify the counter resumes from its saved value. Modify the code to stop a timer in on_pause and restart it in on_resume — note the difference in battery usage.
Common mistakes
- Saving state only in
on_stop()and losing data when the OS kills the app without calling that callback. - Returning
Falsefrom Kivy'son_pause()causing the app to keep running in the background and drain the battery. - Not stopping timers or network operations in
on_pause(), leading to crashes or battery drain when the app is backgrounded. - Assuming the app is always the foreground process and ignoring that the OS can suspend it at any moment.
Variations
- Use platform-specific lifecycle hooks (e.g.,
on_createin Android viapyjnius) for fine-grained control, but that ties you to one OS. - Use a state management library like
redupyfor more complex apps, where you centralize state and rehydrate inon_resume. - Employ a background service (Kivy's
Clockor OS services) only when you truly need it; otherwise, keep the app lightweight.
Real-world use cases
- A note-taking app that auto-saves the draft in
on_pauseso users never lose typed content when they switch apps. - A fitness tracker that stops GPS updates in
on_pauseto save battery, then resumes them when the user returns. - A game that pauses the timer in
on_pauseand restores the exact game state onon_resumeafter a phone call.
Key takeaways
- The mobile OS controls your app's lifecycle; you must handle state transitions to avoid crashes and data loss.
- Save critical state in
on_pause()because the OS may kill your app without callingon_stop(). - Kivy and BeeWare both provide intuitive callbacks (
on_start,on_pause,on_resume,on_stop) to manage the lifecycle. - Return
Truefrom Kivy'son_pause()if you want the app to continue in the background;Falsestops it. - Stop timers and heavy operations in
on_pause()to conserve battery and prevent issues. - The next step is handling user input and touch events, with timing based on the lifecycle methods you just learned.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.