├── .gitignore ├── README.md ├── libs ├── applibs │ ├── constants.py │ └── utils.py └── uix │ ├── baseclass │ ├── auth_screen.py │ ├── home_screen.py │ └── settings_screen.py │ ├── kv │ ├── auth_screen.kv │ ├── home_screen.kv │ └── settings_screen.kv │ └── root.py ├── main.py └── screens.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Directories and files that cannot be commited to git 2 | 3 | # Byte-compiled 4 | __pycache__ 5 | *.py[cod] 6 | *$py.class 7 | *.pyc 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | lib 15 | lib64 16 | parts 17 | sdist 18 | var 19 | wheels 20 | pip-wheel-metadata 21 | share/python-wheels 22 | develop-eggs 23 | eggs 24 | .eggs 25 | *.egg-info 26 | *.egg 27 | MANIFEST 28 | .installed.cfg 29 | downloads 30 | docs/_build 31 | build 32 | dist 33 | bin 34 | .buildozer 35 | 36 | # Logs 37 | *.log 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Editors 42 | .vscode 43 | .ipynb_checkpoints 44 | *.swp 45 | 46 | # PyCharm 47 | .idea/* 48 | !/.idea/*.iml 49 | !/.idea/modules.xml 50 | !/.idea/vcs.xml 51 | !/.idea/runConfigurations 52 | 53 | # Environments 54 | venv 55 | .venv 56 | env 57 | .env 58 | .python-version 59 | 60 | # Temp / Cache 61 | cache 62 | .cache 63 | temp 64 | .temp 65 | .pytest_cache 66 | .coverage 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kivy Lazy Loading - Template 2 | Enhance the performance of your Kivy app 🚀 with lazy loading. 3 | 4 | By implementing this template, you can enhance the performance of your Kivy app through the technique of lazy loading screens. Rather than loading all the screens at startup, this approach ensures that screens are loaded only when they are actively switched to. As a result, the startup time of your app can be significantly reduced. 5 | 6 | This template also features a **screen navigation system** that simplifies handling the back button. 7 | 8 | ### Navigation 9 | The [`Root`](https://github.com/kulothunganug/kivy-lazy-loading-template/blob/main/libs/uix/root.py) is based on [`ScreenManager`](https://kivy.org/doc/stable/api-kivy.uix.screenmanager.html) and additionally provides a few navigation methods: `push(screen_name, side)`, `push_replacement(screen_name, side)` and `pop()`. 10 | 11 | Also `load_screen(screen_name)` method can be used to load the screen and the kv file without setting it as the current screen. 12 | 13 | To incorporate additional screens into your app, follow these steps: 14 | 15 | 1. Create `screen_file.py` in the `libs/uix/baseclass/` directory. 16 | 2. Create `screen_file.kv` in the `libs/uix/kv/` directory. 17 | 3. Add the screen details to `screens.json` as shown below: 18 | ```json 19 | { 20 | ..., 21 | "screen_name": { 22 | "module": "libs.uix.baseclass.screen_file", 23 | "object": "ScreenObjectName", 24 | "kv": "libs/uix/kv/screen_file.kv" 25 | } 26 | } 27 | ``` 28 | This template already contains three screens as example which uses all the navigation methods. 29 | 30 | 31 | ## Buildozer 32 | To use this template for mobile devices, make sure to add **json** to your `buildozer.spec` file, such as 33 | ``` 34 | # (list) Source files to include (let empty to include all the files) 35 | source.include_exts = py,png,jpg,kv,atlas,gif,json 36 | ``` 37 | 38 | ### Further details are documented within the code itself. 39 | -------------------------------------------------------------------------------- /libs/applibs/constants.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | PROJECT_DIR = os.path.abspath(os.path.dirname(sys.argv[0])) 5 | -------------------------------------------------------------------------------- /libs/applibs/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from libs.applibs import constants 4 | 5 | 6 | def abs_path(*path): 7 | return os.path.join(constants.PROJECT_DIR, *path) 8 | -------------------------------------------------------------------------------- /libs/uix/baseclass/auth_screen.py: -------------------------------------------------------------------------------- 1 | from kivy.uix.screenmanager import Screen 2 | 3 | 4 | class AuthScreen(Screen): 5 | # changing screens also can be done in python 6 | # def goto_home_screen(self): 7 | # self.manager.push_replacement("home") 8 | pass 9 | -------------------------------------------------------------------------------- /libs/uix/baseclass/home_screen.py: -------------------------------------------------------------------------------- 1 | from kivy.uix.screenmanager import Screen 2 | 3 | 4 | class HomeScreen(Screen): 5 | # changing screens also can be done in python 6 | # def goto_settings_screen(self): 7 | # self.manager.push("settings") 8 | pass 9 | -------------------------------------------------------------------------------- /libs/uix/baseclass/settings_screen.py: -------------------------------------------------------------------------------- 1 | from kivy.uix.screenmanager import Screen 2 | 3 | 4 | class SettingsScreen(Screen): 5 | # changing screens also can be done in python 6 | # def goto_home_screen(self): 7 | # self.manager.pop() 8 | pass 9 | -------------------------------------------------------------------------------- /libs/uix/kv/auth_screen.kv: -------------------------------------------------------------------------------- 1 | 2 | 3 | BoxLayout: 4 | orientation: "vertical" 5 | 6 | Label: 7 | text: "Current Screen: " + root.name 8 | 9 | Button: 10 | text: "Login (Dummy)" 11 | on_release: root.manager.push_replacement("home") 12 | -------------------------------------------------------------------------------- /libs/uix/kv/home_screen.kv: -------------------------------------------------------------------------------- 1 | 2 | 3 | BoxLayout: 4 | orientation: "vertical" 5 | 6 | Label: 7 | text: "Current Screen: " + root.name 8 | 9 | Button: 10 | text: "GOTO SETTINGS" 11 | on_release: root.manager.push("settings") 12 | -------------------------------------------------------------------------------- /libs/uix/kv/settings_screen.kv: -------------------------------------------------------------------------------- 1 | 2 | 3 | BoxLayout: 4 | orientation: "vertical" 5 | 6 | Label: 7 | text: "Current Screen: " + root.name 8 | 9 | Button: 10 | text: "GOTO HOME" 11 | on_release: root.manager.pop() 12 | -------------------------------------------------------------------------------- /libs/uix/root.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import json 3 | 4 | from kivy.core.window import Window 5 | from kivy.lang import Builder 6 | from kivy.uix.screenmanager import ScreenManager 7 | 8 | from libs.applibs import utils 9 | 10 | 11 | class Root(ScreenManager): 12 | history = [] 13 | 14 | def __init__(self, **kwargs): 15 | super().__init__(**kwargs) 16 | Window.bind(on_keyboard=self._handle_keyboard) 17 | # get screen data from screens.json 18 | with open(utils.abs_path("screens.json")) as f: 19 | self.screens_data = json.load(f) 20 | 21 | def _handle_keyboard(self, instance, key, *args): 22 | if key == 27: 23 | self.pop() 24 | return True 25 | 26 | def load_screen(self, screen_name): 27 | """ 28 | This method creates an instance of the screen object and adds 29 | it to the screen manager without making it the current screen. 30 | 31 | It is useful in situations where certain state needs 32 | to be passed to that screen. 33 | """ 34 | 35 | # checks if the screen is already added to the screen-manager 36 | if not self.has_screen(screen_name): 37 | screen = self.screens_data[screen_name] 38 | # load the kv file (libs/uix/kv/screen_kv_file.kv) 39 | Builder.load_file(utils.abs_path(screen["kv"])) 40 | # grabs the screen module dynamically 41 | screen_mod = importlib.import_module(screen["module"]) 42 | # grabs the screen class from the module 43 | screen_class = getattr(screen_mod, screen["object"]) 44 | # calls the screen class to get the instance of it 45 | screen_object = screen_class() 46 | # set the screen name using screen_name arg 47 | screen_object.name = screen_name 48 | # add the screen to the screen-manager 49 | self.add_widget(screen_object) 50 | 51 | def push(self, screen_name, side="left"): 52 | """ 53 | Appends the screen to the navigation history and 54 | sets `screen_name` it as the current screen. 55 | """ 56 | 57 | if self.current != screen_name: 58 | self.history.append({"name": screen_name, "side": side}) 59 | 60 | self.load_screen(screen_name) 61 | 62 | # set transition direction 63 | self.transition.direction = side 64 | 65 | # set current screen 66 | self.current = screen_name 67 | 68 | def push_replacement(self, screen_name, side="left"): 69 | """ 70 | Clears the navigation history and sets the 71 | current screen to `screen_name`. 72 | """ 73 | 74 | self.history.clear() 75 | self.push(screen_name, side) 76 | 77 | def pop(self): 78 | """ 79 | Removes the current screen from the navigation history and 80 | sets the current screen to the previous one. 81 | 82 | To navigate back to the previous screen, use the this method. 83 | 84 | It is automatically triggered when the user presses the back button on 85 | a mobile device or the ESC button on a desktop. 86 | 87 | Avoid using `scr_mgr_instance.push('prev_screen_name', side='right')` 88 | as it will collapse the navigation history of the screen manager 89 | instead use this method. 90 | """ 91 | 92 | if not len(self.history) > 1: 93 | return 94 | 95 | cur_side = self.history.pop()["side"] 96 | prev_screen = self.history[-1] 97 | 98 | opp_sides = { 99 | "left": "right", 100 | "right": "left", 101 | "up": "down", 102 | "down": "up", 103 | } 104 | 105 | # set transition direction 106 | self.transition.direction = opp_sides[cur_side] 107 | 108 | # set current screen 109 | self.current = prev_screen["name"] 110 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from kivy.app import App 2 | from kivy.core.window import Window 3 | 4 | from libs.uix.root import Root 5 | 6 | 7 | class MainApp(App): 8 | def __init__(self, **kwargs): 9 | super().__init__(**kwargs) 10 | 11 | self.title = "Kivy - Lazy Load" 12 | 13 | Window.keyboard_anim_args = {"d": 0.2, "t": "linear"} 14 | Window.softinput_mode = "below_target" 15 | 16 | def build(self): 17 | # Don't change self.root to self.some_other_name 18 | # refer https://kivy.org/doc/stable/api-kivy.app.html#kivy.app.App.root 19 | self.root = Root() 20 | self.root.push("auth") 21 | 22 | 23 | if __name__ == "__main__": 24 | MainApp().run() 25 | -------------------------------------------------------------------------------- /screens.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth": { 3 | "module": "libs.uix.baseclass.auth_screen", 4 | "object": "AuthScreen", 5 | "kv": "libs/uix/kv/auth_screen.kv" 6 | }, 7 | "home": { 8 | "module": "libs.uix.baseclass.home_screen", 9 | "object": "HomeScreen", 10 | "kv": "libs/uix/kv/home_screen.kv" 11 | }, 12 | "settings": { 13 | "module": "libs.uix.baseclass.settings_screen", 14 | "object": "SettingsScreen", 15 | "kv": "libs/uix/kv/settings_screen.kv" 16 | } 17 | } 18 | --------------------------------------------------------------------------------