Create a Native App with Toga

Build a simple native mobile app with Python using the Toga framework. This hands-on lesson walks you through creating your first Toga app, connecting UI widgets to events, and understanding key concepts for cross-platform development.

Focus: create a simple native app with toga

Sponsored

You've built web apps, scripts, and maybe even desktop tools with Python — but the moment you try to ship something on a phone, the walls go up. Flutter, React Native, Kotlin, Swift: each demands a new language, a new ecosystem, a new mental model. For a Python developer who wants a simple native app, the barrier feels immense. But there's a way to use the Python you already know to write an app that runs natively on Android, iOS, macOS, Windows, and Linux — with real widgets, real events, real deployment. That way is Toga, part of the BeeWare project, and this lesson shows you exactly how to create a simple native app with Toga, from a blank file to a working, interactive UI.

The problem this lesson solves

Mobile development today is fragmented. Android wants Kotlin; iOS wants Swift; even cross-platform tools like React Native force JavaScript. If your entire background is Python, starting native mobile development means climbing a wall of new syntax, build systems, and platform APIs before you even render your first screen.

Worse, many Python-based mobile solutions are not truly native. They render a web view inside a shell, so your app feels and behaves like a website, not a citizen of the operating system. Users notice: scrolling is less smooth, accessibility is worse, and the platform integration (camera, notifications, themes) is manual and brittle.

The pain point this lesson solves is simple: you want to build a native-feeling app using the language you already love. Toga lets you write Python code that the operating system translates into native widgets — a real button on Android is a real Android button. No web view, no DOM, no JavaScript. The goal here is to get you from zero to a working 'Hello, world' style app with a button that actually does something when pressed — using Toga.

Core concept / mental model

Toga is a cross-platform GUI toolkit from the BeeWare project. The idea: you write your app's logic and UI layout once in Python, and at runtime, Toga uses a native backend for each platform. On Windows, your widgets are WinForms controls; on macOS, they're Cocoa views; on Android, they're AWT/Swing widgets. The Python code stays identical — only the underlying toolkit changes.

Think of Toga as a universal translator. You speak Python (the 'UI language'), and Toga listens, then repeats your request in the native tongue of the operating system. You ask for a Button, and on Android Toga tells the OS, "create an Android Button"; on macOS, "create an NSButton". Your app never stops being a Python process — Toga runs a Python interpreter embedded in the native app — but the visible widgets are 100% native.

A key mental shift: your app is a tree of widgets. A Box contains other widgets. A Button is a child of that box. Labels display text, TextInput accepts user input. Events connect user actions (like a click) to Python callbacks. This is the same mental model you'd use with Flask's routes or Django's views — you're mapping user actions to code execution.

Finally, Toga apps start with a single class that defines the app's build method. Toga provides an App class, you subclass it, and you override startup() to create the UI. This is your entry point — no if __name__ == "__main__": messiness.

How it works step by step

Here's the logical flow of creating any Toga app, broken into steps.

  1. Choose your target. Toga runs on desktop first (Windows, macOS, Linux) and mobile (Android, iOS) with varying maturity. For development, you'll usually run it as a desktop app — same code runs on mobile later.

  2. Install Toga. The toga pip package includes the base library. For each platform you target, you may need platform-specific packages (e.g., toga-cocoa for iOS, toga-gtk for Linux). Running pip install toga usually brings what you need for your current OS.

  3. Define your app class. Subclass toga.App. Give it a name (used in the app title) and an app_id (a reverse-domain identifier like org.example.myapp). This is the identity of your app.

  4. Design the UI in startup(). Override startup(self) and build your widget tree. Usually you'll create a toga.Box, add widgets, then set self.main_window with that box as content.

  5. Add event handlers. Connect widget events like on_press (for buttons) to Python methods. These methods receive the widget and often a **kwargs object.

  6. Run the app. The main() method of your app class creates the app and starts the event loop. Toga's main loop is similar to a game loop — it waits for user events and dispatches them to your handlers.

  7. Build a distributable (optional). Use BeeWare's briefcase (a separate tool) to package your app for Android, iOS, etc. That's beyond this lesson, but it's the path to a real installable app.

Hands-on walkthrough

Let's build a simple but complete Toga app. First, install Toga in a fresh environment:

# Create a virtual environment to keep things clean
python -m venv .venv
source .venv/bin/activate   # on Windows: .venv\Scripts\activate

# Install Toga (base package includes your platform backend)
pip install toga

Now create a file myapp.py with this code:

import toga
from toga.style import Pack
from toga.style.pack import COLUMN, CENTER

class MyApp(toga.App):
    def startup(self):
        # Create the main box (vertical layout)
        box = toga.Box(style=Pack(direction=COLUMN, alignment=CENTER))

        # Add a label
        label = toga.Label("Hello, Toga!", style=Pack(padding=10))

        # Add a button with an event handler
        button = toga.Button("Click me", on_press=self.on_button, style=Pack(padding=10))

        # Add widgets to the box
        box.add(label)
        box.add(button)

        # Create the main window
        self.main_window = toga.MainWindow(title=self.name, size=(300, 200))
        self.main_window.content = box
        self.main_window.show()

    def on_button(self, widget, **kwargs):
        # Update the label when the button is pressed
        print("Button pressed!")

def main():
    return MyApp("My First App", "org.example.myapp")

if __name__ == "__main__":
    app = main()
    app.main_loop()

Run it:

python myapp.py

You should see a window with a label and a button. Press the button — check your terminal for the printed message. This confirms the event loop and your handler are working.

Now let's add an interactive feature: a text input and a button that updates a label with the input's content. This makes the app genuinely usable.

import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW, CENTER

class GreetingApp(toga.App):
    def startup(self):
        # Create a vertical box for the main layout
        main_box = toga.Box(style=Pack(direction=COLUMN, padding=20))

        # Horizontal box for input + button
        input_row = toga.Box(style=Pack(direction=ROW, padding_bottom=10))
        self.name_input = toga.TextInput(placeholder="Enter your name", style=Pack(flex=1))
        greet_button = toga.Button("Greet", on_press=self.on_greet, style=Pack(padding_left=10))

        input_row.add(self.name_input)
        input_row.add(greet_button)

        # Label to show the result
        self.result_label = toga.Label("", style=Pack(padding_top=10))

        main_box.add(input_row)
        main_box.add(self.result_label)

        self.main_window = toga.MainWindow(title=self.name, size=(400, 150))
        self.main_window.content = main_box
        self.main_window.show()

    def on_greet(self, widget, **kwargs):
        name = self.name_input.value
        self.result_label.text = f"Hello, {name}!" if name else "Please enter a name."

def main():
    return GreetingApp("Greeter", "org.example.greeter")

if __name__ == "__main__":
    app = main()
    app.main_loop()

When you run this, type a name and press Greet — the label updates instantly. This is the core interaction pattern for any Toga app: user input → event handler → UI update.

Pro tip: Toga's widgets are stateful — the text property of a Label is not just read-only; assigning to it updates the native widget immediately. That's how you build dynamic UIs without manual repaints.

Compare options / when to choose what

Toga isn't the only Python-based GUI solution. Here's how it stacks up against common alternatives:

Framework Native widgets? Target platforms Maturity Best for
Toga Yes (each OS's own controls) Android, iOS, Windows, macOS, Linux Growing Developers who want one Python codebase for desktop + mobile with native look/feel
Kivy No (custom canvas, drawn) Android, iOS, Windows, macOS, Linux Mature Complex custom UIs, games, multitouch apps
PyQt/PySide Yes on desktop Windows, macOS, Linux Very mature Desktop-only apps with power user features
Flutter (from Python) No (paints its own widgets) All major Mature but not Python-true Teams needing high performance and modern design

When to choose Toga: - You want to write your UI in pure Python, not a separate language. - You plan to ship to mobile and desktop with minimal code changes. - You value platform-native look and behavior (e.g., a button looks and feels like the OS's button). - You prefer a set of widgets that map 1:1 to platform controls, rather than a custom-drawn canvas.

When to choose something else: - If you need cutting-edge UI effects (animations, custom-drawn charts), Kivy's canvas gives you more control. - If you only need a desktop app for internal use, PyQt's mature ecosystem and docs might serve you better. - If you're comfortable with JavaScript/Flutter, those ecosystems may have more community examples.

The rule of thumb: Toga is the 'batteries-included native' choice for a Python-first mobile strategy.

Troubleshooting & edge cases

Here's what often trips people up when first getting Toga running — and how to fix it.

Error: ModuleNotFoundError: No module named 'toga' You haven't installed Toga or you're in the wrong virtual environment. Double-check which python and pip list. Run pip install toga again.

Error: ImportError for a backend (e.g., toga-cocoa) on macOS The base package may not pull the native backend for your OS. On macOS, run pip install toga-cocoa; on Linux, pip install toga-gtk; on Windows, pip install toga-winforms (in older versions). Current Toga versions auto-detect, but if you see this, make sure your OS is supported (be wary of WSL — it often lacks a display server).

The window opens but nothing is inside (blank screen) Did you forget to show() the window? A very common mistake. Always call self.main_window.show() after setting content.

The button's on_press never fires Check the handler signature: it must accept (self, widget, **kwargs). If you wrote def on_button(self):, Python will call it with the widget argument and raise a TypeError — the event will silently fail. Always include widget and **kwargs.

Layout is squished or widgets overlap Toga uses a constraint-based layout engine. Missing style attributes can cause unpredictable placement. Make sure to give your Box a direction (COLUMN or ROW) and add padding to child widgets. Use flex=1 on the main input so it expands to fill available space.

TextInput doesn't show typed text on mobile On iOS/Android, the keyboard may need special handling in the future — for now ensure you're testing on desktop where it works reliably. For mobile, you'll later use briefcase to create a proper project; a bare .py file won't package directly for mobile without scaffolding.

App crashes with a KeyError on startup for app_id Your app_id must be a unique reverse-domain string. If you reuse a generic one, it can clash with other installed Toga apps. Use something like org.yourname.yourapp.

Pro tip: If you're using an old version of Toga (pre-0.3), the API differs significantly (e.g., toga.App was constructed differently). Always check your installed version with pip show toga. Most tutorials online use the modern (0.3+) API shown here.

What you learned & what's next

By now, you should be able to explain the core idea behind Toga: a Python-to-native widget bridge that lets you build one app for multiple platforms. You've also completed a practical exercise — you installed Toga, built a window with a label, button, and text input, and wired up events to handle user interaction. That's the foundation of every Toga app you'll ever write.

You now know: - How to structure a app class with startup(). - How to create boxes and widgets and style them with Pack. - How to bind events like on_press and update widgets dynamically. - Where Toga fits relative to Kivy, PyQt, and other frameworks. - Common pitfalls and their fixes, from import errors to blank windows.

Your next step in the Mobile App Development track is to learn handling user input and events in greater depth — things like keyboard shortcuts, multi-touch gestures, and responsive layouts. You'll take these same Toga skills and make your apps richer and more interactive. If you're aiming for a real mobile build, start exploring BeeWare's briefcase tool, which will package this app into an installable Android or iOS app. But first, solidify what you've built here — try adding a second button that resets the label, or a Slider to change the label's font size. Each new widget you master brings you closer to shipping your own native Python app.

Now go ahead: open your editor, add that reset button, and make your Toga app truly yours.

Practice recap

Open your GreetingApp and add a second button labeled 'Reset' that clears the text input and resets the label to empty. Then try changing the layout to center the widgets vertically by wrapping them in an extra Box with alignment=CENTER. Run it to confirm both buttons work — this cements the event-handler pattern you'll use in every Toga app.

Common mistakes

  • Forgetting to call self.main_window.show() — the window opens blank or not at all.
  • Defining on_press handler without the (self, widget, **kwargs) signature, causing a silent TypeError.
  • Skipping a virtual environment or installing Toga in the wrong Python interpreter, leading to ModuleNotFoundError.
  • Not specifying flex=1 on a TextInput, making it refuse to expand and leaving the layout squished.

Variations

  1. Use toga.Slider or toga.Switch instead of a Button to explore different event patterns.
  2. Switch to a toga.ScrollContainer to handle smaller mobile screens with long content.
  3. For mobile packaging, use BeeWare's briefcase create android to turn this app into an installable APK.

Real-world use cases

  • A company internal tool for logging maintenance checks, deployed on technicians' Android tablets with native forms and database sync.
  • A cross-platform 'flashcard accelerometer' study app for iOS and Android that captures swipe gestures to flip cards.
  • A field survey app for data collection on both desktop and phone, using the same Python codebase for offline entry and sync.

Key takeaways

  • Toga translates Python widget calls into native OS controls, giving you a native look and feel without writing Kotlin or Swift.
  • A Toga app is a subclass of toga.App; you build its UI inside startup() and wire events with callbacks.
  • The widget tree (Box, Button, Label, TextInput) plus event handlers is the universal pattern for any Toga UI.
  • Event handlers must accept (self, widget, **kwargs); forgetting this causes silent failures.
  • Toga's Pack style system gives you flexible column/row layouts — use flex and padding to control the arrangement.
  • For real mobile deployment, you'll pair Toga with BeeWare's briefcase, but the code you write here transfers directly.

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.