Using pyinstaller to package kivy and kivyMD desktop apps
Tags: python, kivymd, pyinstaller, appdevelopment
Published: 2021-08-05 22:19:46

Packaging kivy applications can be quite daunting. The documentation can be pretty confusing at times. This guide will walk you through how to package a kivyMD application for __windows__ using pyinstaller through the use of an simple example.

Let's get right into it!🙂

I assume that you already have [kivy](https://kivy.org/doc/stable/gettingstarted/installation.html) and [kivyMD](https://kivymd.readthedocs.io/en/latest/getting-started/) installed in a virtual environment.

The first step is to install pyinstaller with pip. Open up the command prompt terminal, activate your virtual environment and type in the following:

`pip install pyinstaller`

Create two files:
- `main.py` - which is going to contain the main code.
- `main.kv` - which is going to contain the code written in kivy lang

__Note:__ These files should be in the same directory.

Inside `main.py`, enter the following code:

```python
import os, sys
from kivy.resources import resource_add_path, resource_find
from kivymd.app import MDApp

class MainApp(MDApp):
    def build(self):
        self.theme_cls.primary_palette = "DeepPurple"

if __name__ == '__main__':
    try:
        if hasattr(sys, '_MEIPASS'):
            resource_add_path(os.path.join(sys._MEIPASS))
        app = MainApp()
        app.run()
    except Exception as e:
        print(e)
        input("Press enter.")
```

The above code is very crucial when it comes to packaging our application. The try-catch statement helps us identify errors in our program and the `input("Press enter.")` stops the console from closing before we want it to.

Edit `main.kv`:
```
MDRectangleFlatButton:
    text: "Hello World"
    pos_hint: {'center_x': .5, 'center_y': .5}
```
We are just going to display a simple button.

Save the files then open the command prompt terminal, activate your environment and navigate to the folder containing the two files.

Now, before you even start the packaging process, make sure when you run your application you are not getting any console errors.

Type `pyinstaller --onefile main.py` in command prompt and press enter.

When execution is complete, two folders should have been created in the directory containing `main.py`; `dist` and `build` and a file named `main.spec`.

Navigate into the `dist` folder. Inside it, there is an executable file, `main.exe`. If you double click it, a terminal will open and close very quickly.

![Window quickly opens and closes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/intpa0xoe20dib1zk2mc.gif)

To solve this issue, add the following lines to the top of `main.spec`:

```python
from kivy_deps import sdl2, glew
from kivymd import hooks_path as kivymd_hooks_path
```
__Note:__ If you are not packaging a kivyMD application, there is no need for the second import or modifying `hookspath` as shown shortly below.

Add in `Analysis` the lines:
```python
a = Analysis(
    #...
    datas=[('main.kv', '.')],
    hookspath=[kivymd_hooks_path],
   #...
)
```
`datas` is made up of a list of tuples where the first item in the tuple is the file name and the second item is the directory that is going to be used to store the file. If for example, you had a folder named images in the same directory as `main.py`, you would add them as follows:

`datas=[('main.kv', '.'), ('./images/*.png', 'images')],`

The above line is basically saying, 'Get all the *png* images in the images folder and save them to a folder named *images* in the final application.'

Next, edit `EXE`:

```python
exe = EXE(
    #...
    *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],
    #....
)
```

The final *main.spec* file looks like this:

```python
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None
from kivy_deps import sdl2, glew
from kivymd import hooks_path as kivymd_hooks_path

a = Analysis(['main.py'],
             pathex=['C:\\Users\\path\\to\\file'],
             binaries=[],
             datas=[('main.kv', '.')],
             hiddenimports=[],
             hookspath=[kivymd_hooks_path],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],
          name='main',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=True )
```

Now save and run: `pyinstaller main.spec` in the terminal.

After it has finished executing, try executing `main.exe` again.

![It works](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ouogysuz3hszl3oo6qiv.gif)

And that's it! You have successfully packaged your application.

## Errors you may encounter

You may encounter import errors when running pyinstaller. I encountered errors importing pyenchant and cv2. These can easily be fixed by running the following commands in your terminal:

`pip install pyenchant`
`pip install opencv-python`

After the installation is complete, run `pyinstaller main.spec` in your terminal and try executing the `main.exe` again.

## Final thoughts

I hope this guide can prove useful to you. It is by no means a complete guide covering all the aspects of packaging kivy application for windows but I hope you can learn something from it.

For more information, check out:
- [This post](https://stackoverflow.com/a/51921985) on stackoverflow.
- [Kivy documentation](https://kivy.org/doc/stable/guide/packaging-windows.html).
- [Pyinstaller](https://pyinstaller.readthedocs.io/en/stable/usage.html)