A while ago I was developing a Kivy App and found abundant resources about how to create a simple GUI and get started using Kvlang, but not many mentioned screen navigation, and what to do when you have a project with multiple screens. A quick Google search later I found 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). 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 on Github, 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.
For context, the app for the project is a game called Cadavre Exquis. And it’s about creating a whiteout poem using the words of the world’s most famous poets. Programatically it works like this: you select several 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 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, the 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, as I’m more comfortable with that approach, and 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:
- Select the poets
- Toggle the words to create the blackout poem
- 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.
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.
The code
Below is a simple Folder 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
.
├── 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
Plain TextIn 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.
#: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()
ShellScriptHere 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.
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:
[...]
PythonWhile 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 all the screens are 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.
#: 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
[...]
ShellScriptAnd in the main.py
file, just before the MainApp
class, we’ll import each screen Class.
[...]
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):
[...]
PythonThis 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.
[...]
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
ShellScriptAt the root is the ScreenManager
widget, and nested inside, each of the screens you have defined and imported using their class names), with the name
and id
properties. It’s important to keep the naming consistent because this is how we’ll be calling the screen when doing the transitions.
But what if you have a splash screen? Well, when I built my application I looked around 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.
class MainApp(App):
[...]
def on_start(self):
self.root.current = "home_screen"
PythonThat sets 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 animationSlideTransition
– slide the screen in/out, from any directionCardTransition
– new screen slides on the previous or the old one slides off the new one depending on the modeSwapTransition
– implementation of the iOS swap transitionFadeTransition
– shader to fade the screen in/outWipeTransition
– shader to wipe the screens from right to leftFallOutTransition
– 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 center while fading from transparent to opaque.
Now, there 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 later). 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.
#: kivy 2.1.0
#: import FallOutTransition kivy.uix.screenmanager.FallOutTransition
#: include app/views/homescreen/homescreen.kv
[...]
ScreenManager:
id: screen_manager
transition: FallOutTransition()
ShellScriptNow, 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, 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.
[...]
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()
KVlangIn 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. The manager is the widget that “manages” the root, which is the ScreenManager
widget on main.kv
. This widget has the property current, which defines what is 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. 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 picked 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 center 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:
- User flow: It is really important to have a clear view of our target user’s interaction with the app
- 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