DEV Community

Using pyinstaller to package kivy and kivyMD desktop apps

Ngonidzashe Nzenze on August 05, 2021

Packaging kivy applications can be quite daunting. The documentation can be pretty confusing at times. This guide will walk you through how to pack...
Collapse
 
nikthegiant profile image
Nikthegiant

i get an error if i do console= false

Error:

Traceback (most recent call last):
File "logging_init_.py", line 1103, in emit
AttributeError: 'NoneType' object has no attribute 'write

Collapse
 
ngonidzashe profile image
Ngonidzashe Nzenze

Hi,

If you are using pyinstaller 5.7.0, try building your app with version 5.6.2

Collapse
 
nikthegiant profile image
Nikthegiant

Doesn't work... wich Versions should i Take from kivymd or kivy?

Thread Thread
 
ngonidzashe profile image
Ngonidzashe Nzenze

I'm using kivy==2.1.0 and kivymd==1.1.1.

Did you try running it with --noconsole?
That is, pyinstaller --onefile --noconsole main.py

Thread Thread
 
nikthegiant profile image
Nikthegiant

Yes, that is the problem. With console it worked, but without console It doesn't.

Thread Thread
 
ngonidzashe profile image
Ngonidzashe Nzenze

Please share your spec file

Thread Thread
 
nikthegiant profile image
Nikthegiant

The Standard Hook from the KIVYMD webside,
i think i must Import hidden modules, but i don't know how i do it correctly...

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

import sys
import os

from kivy_deps import sdl2, glew

from kivymd import hooks_path as kivymd_hooks_path

path = os.path.abspath("E:\Programmieren\Python\KivyMD\CreateApp")

a = Analysis(
["main.py"],
pathex=[path],
hookspath=[kivymd_hooks_path],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=None,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=None)

exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
*[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],
debug=False,
strip=False,
upx=True,
name="Main",
console=False,
)`

Thread Thread
 
ngonidzashe profile image
Ngonidzashe Nzenze

I used your spec file and it built the app successfully. I faced the same issue before and solved it by simply changing the version of pyinstaller I was using to version 5.6.2.

Are getting that error from building the app in this tutorial or your own application?

Thread Thread
 
nikthegiant profile image
Nikthegiant

i did it with pyinstaller==5.6.2 and it worked!! thank you so much!!!!

Thread Thread
 
ngonidzashe profile image
Ngonidzashe Nzenze

You're welcome. Glad it worked

Collapse
 
islamimtiaz profile image
IslamImtiaz • Edited

Greetings, I am currently attempting to transform my python code into an APK, however, after converting it and installing the file, it unfortunately crashes right after the kivy logo appears. I am using google collab to convert my code into APK.
Spec file
Image description

main.py

from kivymd.uix.screen import MDScreen
from kivymd.app import MDApp
from kivy.uix.image import Image
from kivymd.uix.button import MDFillRoundFlatButton
from kivymd.uix.textfield import MDTextField
from kivymd.uix.label import MDLabel
from kivymd.uix.toolbar import MDTopAppBar
from kivymd.uix.dialog import MDDialog

class ConverterApp(MDApp):

    def flip(self):
        if self.state == 0:
            self.state = 1
            self.toolbar.title = 'CGPA calculator'
            self.GP.hint_text = 'Enter your current GPA'
            self.CH.hint_text = 'Enter your previous GPA'
        else:
            self.state = 0
            self.toolbar.title = 'GPA calculator'
            self.GP.hint_text = 'Enter your GP'
            self.CH.hint_text = 'Enter subject CH'

    def gpa(self, obj):
        gp_text = self.GP.text.strip()
        ch_text = self.CH.text.strip()
        # Check if GP and CH fields are not empty
        if not gp_text or not ch_text:
            # Show an error when GP and CH fields are empty
            dialog = MDDialog(title='Error',text='Both GP and CH fields are required',size_hint=(0.7, 1))
            dialog.open()
            return
        # Check if entered data is valid
        try:
            gp_values = [float(gp) for gp in gp_text.split(',')]
            ch_values = [float(ch) for ch in ch_text.split(',')]
        except ValueError:
            # Show an error when non-numeric value is entered
            dialog = MDDialog(title='Error',text='Invalid input! Please enter comma-separated numbers only',size_hint=(0.7, 1))
            dialog.open()
            return
        # Calculate GPA or CGPA
        if self.state == 0:
            x = sum(gp_values)
            y = sum(ch_values)
            if y == 0:
                # Show an error
                dialog = MDDialog(title='Error',text='Zero division error',size_hint=(0.7, 1))
                dialog.open()
            else:    
                c = x / y
                self.label1.text = str(c)
                self.label.text = "Your GPA is: "
        else:
            x = sum(gp_values)
            len1 = len(gp_values)
            y = sum(ch_values)
            len2 = len(ch_values)
            b = len1 + len2
            z = (x + y) / b
            self.label1.text = str(z)
            self.label.text = 'Your CGPA is: '

    def build(self):
        self.state = 0
        screen = MDScreen()
        # Top toolbar
        self.toolbar = MDTopAppBar(title="GPA Calculator")
        self.toolbar.pos_hint = {'top':1}
        self.toolbar.right_action_items = [['rotate-3d-variant', lambda x: self.flip()]]
        screen.add_widget(self.toolbar)
        # Logo
        screen.add_widget(Image(source ='gpr.png',size_hint=(0.8,1),pos_hint ={'center_x':0.5,'center_y':0.7}))
        # Collect user input
        self.GP = MDTextField(hint_text='Enter your GP',halign='center',size_hint=(0.8,1),pos_hint ={'center_x':0.5,'center_y':0.48},font_size=22)
        screen.add_widget(self.GP)
        self.CH = MDTextField(hint_text='Enter your CH',halign='center',size_hint=(0.8,1),pos_hint ={'center_x':0.5,'center_y':0.4},font_size=22)
        screen.add_widget(self.CH)
        # Secondary + Primary Label
        self.label = MDLabel(halign='center',pos_hint ={'center_x':0.5,'center_y':0.32},theme_text_color='Secondary')
        self.label1 = MDLabel(halign='center',pos_hint ={'center_x':0.5,'center_y':0.28},theme_text_color='Primary',font_style='H5')
        screen.add_widget(self.label)
        screen.add_widget(self.label1)
        # Convert Button
        self.button= MDFillRoundFlatButton(text='Result',font_size='17',pos_hint ={'center_x':0.5,'center_y':0.15})
        self.button.bind(on_press = self.gpa)
        screen.add_widget(self.button)

        return screen

ConverterApp().run()
Enter fullscreen mode Exit fullscreen mode
Collapse
 
panxproject profile image
Waleed Sadek • Edited

The problem you're experiencing might be due to a number of reasons, including missing dependencies, issues with your buildozer.spec file, or problems with the build process itself.

Here are a few steps you can follow to help debug and solve the problem:

  1. Check the Logs: After running the application and it crashes, check the logs for any error messages. On an Android device, you can view the logs by connecting the device to your computer and running adb logcat in a terminal. This should provide some information about why the application is crashing.

  2. Check the Buildozer.spec File: Ensure that all necessary requirements are mentioned in the buildozer.spec file. You can use the requirements field in the spec file to specify all Python modules that your app depends on.

  3. Permissions: Some functionalities might require certain permissions to be set in the buildozer.spec file. If your app is trying to access a feature it does not have permission for, it could cause the app to crash.

  4. Resource Files: If your application uses any resources such as images, audio files, etc., make sure that these files are being included in the APK. In your buildozer.spec file, the source.include_exts option can be used to specify the file types to include in the package.

  5. Python Version: Make sure you're using a Python version that's compatible with KivyMD.

Share the log with the errors showing, it will be helpful to debug!

Collapse
 
mohsenmrds profile image
mohsenmrds

That's completely nice and worked for me. thank you very much.
The question is why the above solution is not in pyinstaller by default?

Collapse
 
ngonidzashe profile image
Ngonidzashe Nzenze

You're welcome. Glad it worked for you!

Collapse
 
drizzyovo profile image
Gaurav Malpedi

Hi, your code did help me. Thank you. But can you please show me how to include a text file or a JSON file as a database. I wanna make a kivy desktop application in onefile. It's giving me error. Please help me out.

Collapse
 
ngonidzashe profile image
Ngonidzashe Nzenze

Hi,

So the best I can do is give you an example using json. You'll need two file, main.py and main.kv. I'm going to be using code from this article to demonstrate.

main.kv remains the same as in the article but in main.py I am going to add code to save and extract data from a json file so in the end it will look like this:

import os, sys, json
from kivy.resources import resource_add_path, resource_find
from kivymd.app import MDApp
from kivymd.uix.list import OneLineIconListItem, IconLeftWidget

def save_data_to_json(filename, data):
    with open(filename, "w") as file:
        json.dump(data, file)


# Extract data from a JSON file
def extract_data_from_json(filename):
    with open(filename, "r") as file:
        return json.load(file)

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

    def add_item(self, text):
        new_list_item = OneLineIconListItem(text=text)
        new_list_item.add_widget(IconLeftWidget(icon="language-python"))
        self.root.ids.listcontainer.add_widget(new_list_item)
        self.root.ids.listinput.text = ""

        # Check if the JSON file exists
        if os.path.exists("data.json"):
            # Extract existing data from the JSON file
            data = extract_data_from_json("data.json")

        else:
            data = []

        # Add the new item to the data
        data.append(text)

        # Save the updated data to the JSON file
        save_data_to_json("data.json", data)

    def on_start(self):
        """Load items from the JSON file on app start"""

        # Check if the JSON file exists
        if os.path.exists("data.json"):
            # Extract data from the JSON file
            data = extract_data_from_json("data.json")

            # Iterate over the data and add items to the list container
            for item in data:
                self.add_item(item)


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.")

Enter fullscreen mode Exit fullscreen mode

When you add an item, it will be added to the listbox and saved to the json file as well. When you start the app, data is loaded from the json file and added to the listbox.

Hope that helps.

Collapse
 
james_hanenian_3a5fa77ddb profile image
James Hanenian

Hi, when I run pyinstaller --onefile main.py with your example, I get an empty dist folder with no executable file in it. I have python==3.10.8, kivy==2.1.0, kivymd==1.1.1.
I even tried changing pyinstaller version to 5.6.2 and the hello world program runs normally.
I am not sure how to proceed from here.

Collapse
 
spirich profile image
413K54HDR

Thanks, it worked!

Collapse
 
samueljevix profile image
samueljevix

thanks bro it worked for me

Collapse
 
murphenheim profile image
samad

for linux:
just skip the kivy_deps import .. and *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)], steps