How to Implement Screen Navigation in Your Kivy App

Building a Kivy app with multiple screens? then you’ll need to know how to implement screen navigation. This post discusses the process step by step.

By.

min read

screens

A while ago I was developing a Kivy App and I noticed that, while there were abundant resources about how to create a simple GUI and get started using Kvlang, not many mentioned screen navigation, and what to do when you have a project with multiple screens. A quick search in Google taught me about the ScreenManager widget, so I looked for the official documentation and found some usage examples and tips on advanced usage (very cool stuff I didn’t know). And it was great, now I had the code, the transitions and the options. But I had no idea where in my code base this was supposed to go. So I looked around for projects that others have built, and I found some series and videos and started from there.

In this post, I’ll show my take on Screen Navigation step-by-step: from the very beginning, with design and planning, all the way to the implementation. I’ll also add some notes on what actually happened when I was working on the project, what problems I had, what I did, and what I think of my solution. And what I would do differently, and want to change. Finally, I’ll add some tips and tricks you can use in your own projects.

Table of Contents

  1. Planning Your App’s Screen Navigation
  2. Creating and Configuring Screens
    1. Creating the screens
    2. Configuring the screens with ScreenManager
  3. Implementing Screen Transitions
  4. Adding Navigation Controls
  5. Tips for Optimizing Screen Navigation
  6. Key takeaways

On a side note, and just for a bit of context, the app for the project is a game called Cadavre Exquis. It consists in creating a whiteout poem using the words of the world’s most famous poets. Programatically it works like this: you select a number of poets from different eras, and the app takes the most famous poems from said poets, jumbles the words and puts them on the screen. You then can then create the blackout poem by toggling the words, once you are happy with your creation, you can take a screenshot and save it. Would like to see the code? Here is the link to the repository.

Implementing the navigation was a bit of trial and error until I got the code to work and look how I wanted it. And I learned a lot about structuring Kivy apps in the process. That’s when I realised just how important Screen Navigation can be when designing and developing an application. It is what organises all the content of the app, and establishes the user flow. All of this falls within the scope of Architecture Information applied to the field of UX Design. A discipline that focuses on effectively organising information so users can easily find what they are looking for. And, while at the beginning it might seem like it’s just something you use to help the user go from one screen to the next, in the end, it’s what can make or break your app and user experience.

In the case of Kivy Apps, this flow between screens is controlled by the ScreenManager widget, which serves as the base of the tree that holds the application together. Now, implementing screen navigation can be done using both Kvlang and Python or just Python. In my project, I used Kvlang, which is the approach I’m most comfortable with, as I like the separation of concerns it provides and how easy it is to build indented structures that make sense (Flutter, I’m looking at you).

It starts with a plan

The design

Before implementing anything, we need a plan. I started with the flow of the application: what I want the user experience to be like. In a very bare-bones fashion, the minimal possible. For my project, the answer was this:

  1. Select the poets
  2. Toggle the words to create the blackout poem
  3. Finish and save

That gave me a clue as to which screens I was going to need, both the main ones and auxiliaries. So I decided on a design and built the mock-ups.

Design of Pages for Cadaver Exquis app

It’s a simple app with 5 main screens, 3 pop-ups and a splash screen. All screens are accessible through the main menu, and the flow of the application looks like this.

Screen navigation flow chart for Cadaver Exquis application

The code

As for the Folder Structure, below you can find a simple structure for Kivy Applications. Both main.kv and main.py are at the root of the project, while the screens and custom components are stored inside a module called app.

Project’s file structure
├── pyproject.toml               <- Build system requirements
├── LICENSE
├── AUTHORS.md
├── CONTRIBUTING.md
├── Makefile                     <- Makefile with commands for common tasks
├── README.md

├── app                          <- Main application folder
   ├── components               <- Custom components
   ├── views                    <- Screens and logic managers
      ├── aboutscreen          <- Each screen contains a .kv file and a .py file
         ├── aboutscreen.kv
         └── abouscreen.py
      ├── bioscreen
      ├── galleryscreen
      ├── gamescreen
      ├── homescreen
      ├── menuscreen
      ├── startscreen
      └── __init__.py
   └── __init__.py

├── docs                          <- Documentation for the project

├── assets                        <- Folder for data, fonts, images and media used in the app

├── tests                         <- Folder for tests

├── main.py                       <- Entry point for App

├── main.kv                       <- Main file for App Layout

├── setup.cfg                     <- Configuration for tooling
└── tox.ini                       <- tox file with settings for running tox
ShellScript

In this post, I’ll focus on connecting the screens in the views folder to the main app in main.py and main.kv using the ScreenManager widget. And I’ll assume you are familiar with the basics of the Kivy framework such as building simple screens with the provided widgets.

Creating and Configuring Screens

Creating the screens

In a Kivy app, a screen is defined by two files: a .kv file that gives the layout (how the screen looks) and a .py file that stores the logic of how the screen works. Here we have, for example, the design for the Menu Screen for my application.

And below you can see the code from both files.

menuscreen.kv
#:kivy 2.1.0

#: import hex kivy.utils.get_color_from_hex


<MenuScreen>:

    poets_menu: poets_menu


    BoxLayout:
        size: root.width, root.height
        orientation: "vertical"
        spacing: 48

        canvas:
            Color:
                rgba: hex('#FDFDFD')
            Rectangle:
                size: self.size
                pos: self.pos

        Heading:
            heading: "Pick your poison"
            type_size: "50sp"
            pos_hint: {"center_y": 0.5, "center_x": 0.5}
            size_hint: 0.8, 0.15

        # Grid Menu
        ScrollView:
            do_scroll_x: False
            do_scroll_y: True
            dscroll_timeout: 250

            GridLayout:
                id: poets_menu
                size_hint_y: None
                cols: 3
                row_default_height: root.height * 0.1
                height: self.minimum_height
                spacing: 36
                padding: [36, 0]

        # Bottom buttons
        BoxLayout:
            orientation: "horizontal"
            size_hint: 1, 0.1
            padding: [144, 0]
            spacing: 80

            ArrowLeftButton:
                on_release: root.manager.current = "home_screen"

            Button:
                text: "Biographies"
                font_size: "20sp"
                on_release:
                    root.manager.current = "bio_screen"
                    root.manager.ids["bio_screen"].populate_screen()

            ArrowRightButton:
                label_message: "Inicio"
                on_release: root.start_game()
ShellScript

Here you can see the layout defined by three main parts:

  • Heading: This is a custom component with the title
  • Scroll View with a Grid: Where the name of all the poets is
  • Bottom Box: This contains the navigation buttons to go forward or backwards.
menuscreen.py
import ast
import pandas as pd
import random

from typing import List
from kivy.uix.screenmanager import Screen

from app.components.poettoggle.poettogle import PoetToggle


class MenuScreen(Screen):
    def __init__(self, **kwargs):
        super(Screen, self).__init__(**kwargs)
        [...]

    def populate_screen(self):
        [...]

    def get_selected_poets(self) -> List:
        [...]

    def start_game(self) -> List:
        [...]
Python

While the main.py file contains the main class for the menu screen and three custom methods:

  • populate_screen: populates the screen with the names of the poets from a database
  • get_selected_poets: reads the names of the poets selected by the user
  • start_game: retrieves poems from selected poets, mixes the words and creates a bag of words. The result serves as input for the populate_screen method for the Game Screen (this is done through the Screen Manager and will be explained later)

Registering the screens with ScreenManager

Once we have all the screens ready, we can “plug them in” to the main app. To do that, we import them into both of the main files. So, the top of the main.kv file will be like this.

main.kv
#: kivy 2.1.0

#: include app/views/homescreen/homescreen.kv
#: include app/views/menuscreen/menuscreen.kv
#: include app/views/gamescreen/gamescreen.kv
#: include app/views/aboutscreen/aboutscreen.kv
#: include app/views/demoscreen/demoscreen.kv
#: include app/views/bioscreen/bioscreen.kv
#: include app/views/galleryscreen/galleryscreen.kv

[...]
ShellScript

And in the main.py file, just before the MainApp class, we’ll import each screen Class.

main.py
[...]

from kivy.app import App

from app.views.homescreen.homescreen import HomeScreen
from app.views.menuscreen.menuscreen import MenuScreen
from app.views.gamescreen.gamescreen import GameScreen
from app.views.aboutscreen.aboutscreen import AboutScreen
from app.views.demoscreen.demoscreen import DemoScreen
from app.views.bioscreen.bioscreen import BioScreen
from app.views.galleryscreen.galleryscreen import GalleryScreen


class MainApp(App):
    [...]
Python

This makes the app “aware” of the existence of the screens and you can start linking them using the ScreenManager widget. There are multiple ways of doing this, in the official documentation the examples show that, in your main.py file, you can create an instance of the ScreenManager class inside the build method of the app, and add each screen widget to it. And this works well when you have a small application which can be stored inside one file. But as your codebase grows I think it’s better to have some separation of concerns and keep the UI and the Business logic separated. Also, at this point of the project, you already have an idea of how big your starting, bare minimum app is going to be, you will only grow from there.

So I decided to take a different approach, which is, to store the application tree in the main.kv file. Also, since I’m not going to hold any logic in the ScreenManager itself, I didn’t create a class in the main.py file. Although, in the beginning, I wasn’t sure it was going to work without the Python class but it did ????.

With that, we can then proceed to create the tree, and it’s actually pretty simple, in the main.kv file (using Kvlang), below the imports, you declare the manager and its children. Note that you don’t enclose the declaration of the widget inside <>. So, in the case of my project, the structure looked like this.

main.kv
[...]

ScreenManager:
    id: screen_manager

    HomeScreen:
        name: "home_screen"
        id: home_screen
    MenuScreen:
        name: "menu_screen"
        id: menu_screen
    GameScreen:
        name: "game_screen"
        id: game_screen
    AboutScreen:
        name: "about_screen"
        id: about_screen
    DemoScreen:
        name: "demo_screen"
        id: demo_screen
    BioScreen:
        name: "bio_screen"
        id: bio_screen
    GalleryScreen:
        name: "gallery_screen"
        id: gallery_screen
ShellScript

At the root is the ScreenManager widget, and nested inside, each of the screens you have defined and imported using their class names), with a name and id properties. And it’s important to keep the naming consistent because this is how we’ll be calling the screen when doing the transitions.

And if you have a splash screen? Well, when I built my application I looked around a bit and didn’t found anything specific to splash screens so I used the standard way of switching screens inside the on_start method from the MainApp class in main.py and added the following code.

main.py
class MainApp(App):
    [...]
    def on_start(self):
        self.root.current = "home_screen"
Python

That set’s the current screen to home_screen, which is the name we gave the screen on the ScreenManager widget.

Implementing screen transitions for beautiful Screen Navigation

Now that the screens are connected and we can define how we go from once screen to the next. And while the effects are really pretty, it actually helps with the flow of the app, as it gives the user the sense of movement. And according to the Kivy documentation for ScreenManager, there are eight types of transitions one can implement.

  • NoTransition – switches screens instantly with no animation
  • SlideTransition – slide the screen in/out, from any direction
  • CardTransition – new screen slides on the previous or the old one slides off the new one depending on the mode
  • SwapTransition – implementation of the iOS swap transition
  • FadeTransition – shader to fade the screen in/out
  • WipeTransition – shader to wipe the screens from right to left
  • FallOutTransition – shader where the old screen ‘falls’ and becomes transparent, revealing the new one behind it.
  • RiseInTransition – shader where the new screen rises from the screen centre while fading from transparent to opaque.

Now, they are two ways to implement transitions: on one side, you can declare the transition directly on the screen while setting up navigation (which I’ll discuss further along). Or, you can just set a default on the ScreenManager widget, which is what I ended up doing. To do that, in your main.kv file, you import the transition you want to set (between the first declaration and your Screens imports), add the transition property and set it to your preferred style. The transition is declared as a function call.

main.kv
#: kivy 2.1.0

#: import FallOutTransition kivy.uix.screenmanager.FallOutTransition

#: include app/views/homescreen/homescreen.kv

[...]

ScreenManager:
    id: screen_manager
    transition: FallOutTransition()
ShellScript

Now, in this project, I set the transition to a unique value in the ScreenManager, because I chose Fall Out, which I think suited the theme of the app. But, if I wanted to use swaps, for example, I would have set the navigation directly on the screens. This is because you can declare things such as transition direction, which will depend on where are you in the app, and gives the user that sense of going forward or backwards.

Adding Navigation Controls

Now for the fun part, which gave me nightmares, but was oh-so-satisfying when it finally worked, adding the controls. This is done through buttons, menus or swipes, and in the case of our example, we have three buttons at the bottom of the screen. To add interactivity to those buttons I used the on-release function on the menuscreen.kv file. Below is the code corresponding to the bottom Box Layout containing the navigation menu.

Screen navigation controls used in the app
menuscreen.kv
[...]

        BoxLayout:
            orientation: "horizontal"
            size_hint: 1, 0.1
            padding: [144, 0]
            spacing: 80

            ArrowLeftButton:
                on_release: root.manager.current = "home_screen"

            Button:
                text: "Bibliography"
                font_size: "20sp"
                on_release:
                    root.manager.current = "bio_screen"
                    root.manager.ids["bio_screen"].populate_screen()

            ArrowRightButton:
                label_message: "Next"
                on_release: root.start_game()
ShellScript

In the three cases, you can see that the screen navigation is set by the root.manager.current property. But what actually is root.manager? Well, root refers to the root widget of the document, which in this case is a Screen Widget. And manager is the widget that “manages” the root, which is the ScreenManager widget on main.kv. So, the manager that has the property current, which defines what it’s shown to the user at that moment. We then can set the property to the name of the screen where we want our button to direct to. See the importance of good naming here? Yes, it is good practice and all, but for me, it’s a way to keep myself sane when developing and to know what I was doing when I pick up the project after some time.

And that’s not all that you can do with the ScreenManager, I mean, in terms of screen navigation it is. But, if you read the code above, on the centre button there is another call made below root.manager.current, which calls to root.manager.ids["bio_screen"].populate_screen(). That’s something you can do, in case there is something else you want to happen when you press the button. Here, as the name of the function says, the function is in charge of it populating the destination screen. So, the function calls the manager of the root widget (the ScreenManager) to check the ids registered and select the one with the id bio_screen. This selects the Screen object with that id and calls on its method named populate_screen (not to be confused with the populate_screen method on menuscreen.py, I just name the methods consistently across screens).

Tips for Optimizing Screen Navigation

When working on Screen Navigation for an app it’s important to have consistency and order for it to be successful. This makes things easier both for the developer and the user. In the case of Kivy Apps, the Screen Manager widget makes the job pretty easy, but there are a few key things to be aware of. Not doing so could break your app, or make it unnecessarily hard to navigate.

Appropriate naming of screens

A screen is a Class, so you need to follow Python’s convention for naming classes (like Nike… Just do it!). Screen names are like this: <Name>Screen

Keep naming simple:

You are going to be referencing these things a lot, so keep names and ids simple, and in snake_case (because Python ????)

Have a clear flow

Continuing on the theme of simplicity. It’s important for the user to know at all times, where he is in the app and how to go back or get to the main screen. I’m thinking about myself now, having to go back endlessly when using some apps in order to find myself. Also, not doing this can also backfire on you as a developer because when you want to change something, there are a million calls going from one screen to the next. Then you move something and suddenly a whole tree has moved.

Finally, keep in mind that this it’s mostly my opinion, both as a developer and App user, it’s how I like to interact with applications. There are many ways one can go about structuring and application. This is why I encourage you to test your application with lots of people. You can then see how they orient themselves in the app and what problems they may have.

Key takeaways

Implementing screen navigation in Kivy Apps is not difficult using the ScreenManager widget. But before you can do anything you need two things:

  1. User flow: It is really important to have a clear view of our target user’s interaction with the app
  2. Structure: A clear design and architecture structure makes user navigation, code development and refactoring easier

For Kivy apps, the code is separated into two files: a .kv file, which handles how the app looks and a .py file where the logic is stored. So, a way of structuring files can be, to have a main app folder, and inside create a folder for each screen containing both those files. Then, in your main.kv file, you can import the screens and modules to create a tree structure using the ScreenManager widget. Also, don’t forget to import the screen classes to your main.py folder.

Navigation between screens is implemented through methods built into the widgets that make up each Screen, like buttons and menus. You can also add more actions to those methods to implement other functionality when changing screens.

And that’s it, I hope this post was helpful and gave you a clearer idea of how to implement navigation on your own Kivy apps. If there is something you’d like to ask or have a suggestion, don’t hesitate to leave a comment or send a message. If you want some further reading, you can check Erick Sandberg’s Youtube series on how to build a Kivy app from start to finish or my resources page.

May Kivy be nice to you in your next project,

Andrea