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

Sponsored

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:

  1. Launch — The user taps the app icon. The OS creates a process and instantiates your app's main class.
  2. Start — Your app's on_start() method runs. This is where you initialize global state, set up databases, and load configuration.
  3. Active/Resume — The app is now visible and interactive. For Kivy, this is on_resume(); for BeeWare, also on_resume(). You might start animations or refresh data from a server.
  4. 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 a bool, Toga uses on_pause). You should save user progress, stop heavy operations, and release exclusive resources like the camera.
  5. Resume — The interruption ends. The OS calls on_resume() again, and you can restore the UI and continue.
  6. 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 have on_stop() when the app is about to close.
  7. 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() and on_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_pause returns False (Kivy), the app doesn't go to background and keeps running. That can drain battery. Always return True unless 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 in on_pause, not on_stop.
  • Crash on resume after screen rotation. Rotation destroys and recreates the activity. In Kivy, you must handle on_resume and reconstruct your UI state. Store state in on_resume-triggered data.
  • Timers keep running in background. Use Clock.unschedule() inside on_pause to 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 logcat with System.out or Python's logging module 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 False from Kivy's on_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

  1. Use platform-specific lifecycle hooks (e.g., on_create in Android via pyjnius) for fine-grained control, but that ties you to one OS.
  2. Use a state management library like redupy for more complex apps, where you centralize state and rehydrate in on_resume.
  3. Employ a background service (Kivy's Clock or 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_pause so users never lose typed content when they switch apps.
  • A fitness tracker that stops GPS updates in on_pause to save battery, then resumes them when the user returns.
  • A game that pauses the timer in on_pause and restores the exact game state on on_resume after 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 calling on_stop().
  • Kivy and BeeWare both provide intuitive callbacks (on_start, on_pause, on_resume, on_stop) to manage the lifecycle.
  • Return True from Kivy's on_pause() if you want the app to continue in the background; False stops 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.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.