We write a calculator in Python using Flet

We write a calculator in Python using Flet

Introduction

In the world, cross-platform has become an integral part of application development. However, with such a variety of frameworks, choosing the right tool to achieve this goal can be difficult.

Creating an attractive mobile app that will work perfectly on Android and iOS usually requires significant refinement of existing tools such as Kivy or Tkinter. That’s where Flet comes in, a framework that makes it easy to build web, desktop, and mobile apps using Flutter, Google’s popular front-end tool, but in Python.

Let’s take a look at how to build a basic calculator app using Flet and see how simple and powerful this framework can be.

Who is Flet?

Flet is a framework that allows you to create user interfaces directly using the Flutter toolkit.

What are its advantages?

  • It combines the simplicity of Python with Flutter’s rich front-end capabilities, allowing you to rapidly develop cross-platform applications without requiring extensive front-end experience.

  • This tool does not require an SDK, and its functionality can be easily extended using the Flutter SDK.

❕ Pay attention: To successfully master this topic, it will be helpful to have a general understanding of some key front-end concepts, such as the block model, Flexbox and Grid layouts, and how to position elements. Although you can proceed without a deep understanding of these topics, it is still highly recommended that you familiarize yourself with them at least a little.

Let’s finally build our calculator!

We adjust the environment

Before you start writing code, make sure you have Python installed on your machine. Then follow these steps to set up your Flet environment.

  1. We install Flet, for example using pip. Open the terminal or command line and enter pip install flet

  2. Open your favorite code editor (for example, VSCode, Pycharm, etc.) and create a new executable Python file.

Let’s first check if everything works with the most favorite phrase in the development community “Hello World!”.
In our Python file, enter and write the code:

import flet as ft

def main(page: ft.Page):
    page.add(ft.Text(value="Hello, World!"))

ft.app(target=main)

Let’s start and check if everything works. If we see the inscription “Hello, World!” on the screen, then we are ready to proceed to the creation of our calculator.

We create a layout

First, let’s deal with the structure of the calculator. We use a widget to create columns that will act as a display and buttons. The display will show the current input and the buttons will allow click on them interact with the user.

Let’s write the following code:

from flet import (
    app, Page, Container, Column, Row,
    TextField, colors, border_radius, ElevatedButton, TextAlign, TextStyle
)


def main(page: Page):
    page.title = "Calculator"
    result = TextField(
        hint_text="0", text_size=20,
        color="white", text_align=TextAlign.RIGHT,
        hint_style=TextStyle(
            color=colors.WHITE, size=20
        ),
        read_only=True
    )

    def button_click(e):
        pass

    button_row0 = Row(
        [
            ElevatedButton(text="C",  on_click=button_click),
            ElevatedButton(text="^",  on_click=button_click),
            ElevatedButton(text="%",  on_click=button_click),
            ElevatedButton(text="/",  on_click=button_click),
        ]
    )
    button_row1 = Row(
        [
            ElevatedButton(text="7",  on_click=button_click),
            ElevatedButton(text="8",  on_click=button_click),
            ElevatedButton(text="9",  on_click=button_click),
            ElevatedButton(text="*",  on_click=button_click),
        ]
    )
    button_row2 = Row(
        [
            ElevatedButton(text="4",  on_click=button_click),
            ElevatedButton(text="5",  on_click=button_click),
            ElevatedButton(text="6",  on_click=button_click),
            ElevatedButton(text="-",  on_click=button_click),
        ]
    )
    button_row3 = Row(
        [
            ElevatedButton(text="1",  on_click=button_click),
            ElevatedButton(text="2",  on_click=button_click),
            ElevatedButton(text="3",  on_click=button_click),
            ElevatedButton(text="+",  on_click=button_click),
        ]
    )
    button_row4 = Row(
        [
            ElevatedButton(text="0",  on_click=button_click),
            ElevatedButton(text=".",  on_click=button_click),
            ElevatedButton(text="=",  on_click=button_click),
        ]
    )
    container = Container(
        width=350, padding=20,
        bgcolor=colors. BLACK,
        content=Column(
            [
                result,
                button_row0, button_row1, button_row2,
                button_row3, button_row4
            ]
        )
    )
    page.add(container)


if __name__ == '__main__':
    app(target=main)

After executing this code, we will see the calculator layout, which will not look very good yet, but that’s okay! We’ll improve it by adding some spacing, container rounding, and a theme to make our calculator look neater.

Explanation of the code

Here we have created a layout, but some of you did not understand, as we did it Let’s understand each other!

In the first line of our code, we import the necessary controls. These elements, called Flet widgets, play a key role in creating the user interface of our application. In this case, we imported important components such as: app, Page, Container, Column, Row, TextField, colors, border_radius, ElevatedButton, TextAlign, TextStyle.

Some of them are not full widgets, for example app, colors, border_radius, TextAlign and TextStyle. These are classes and methods that add additional functionality to our program.

For example, app allows us to run our application offline, targeting the main instance. colors gives the ability to style our controls that support attributes. color and bgcolorwithout having to identify their names. AND border_radius makes it possible to round the corners of our containers.

In line 7, we define the main instance of our program as Page. A page is a container that houses controls View. We will not delve into the details of View, but you can read more about it on the official website.

Now we will set the title of our page using the attribute page.title. This title will be displayed in the title bar of our application.

There is a block in rows 9 to 16 result. It has various properties, but we will use only some of them in this project. We added the text “0”, set its size to 20, selected the color white and aligned it to the right. Also, we’ve made this text non-editable. This means that users cannot change it using the keyboard.

In line 18, we defined the event handler button_click. In this function, we will apply logic to our application, turning it into a real calculator. But for now it just stands there pass as a plug

In lines 21 through 59, we defined our lines using a widget Row. Widget Row is a control that displays its children horizontally, from left to right. Similar to linear layout in Android development or linear elements in CSS, the control Row works the same way, aligning the controls along the horizontal axis.

Then we added ElevatedButtonwhich will represent the buttons in the calculator user interface. Notice that we have given it attributes text and _onclick. Attribute text defines the data that will be displayed on the results when clicked, and the attribute onclick calls a function button_click for event processing

We still have a container. It helps make the control look good. You can choose the background color, spacing, borders and their radius. You can also position the control using padding, margin and alignment.

A container follows the concept of a box model, similar to that used in CSS, as shown in the figure below:

Control element Column works as a control element Row. It arranges its child elements, as if lining them up in a row from top to bottom. So we can conveniently arrange the buttons in the right order.

Once we have defined the UI elements, we need to display them in our application and then call it. For this we use the method page.add()which allows us to add and logically organize UI elements.

Next follows the call of our program in offline mode, which was implemented in lines 74-75.

Adding functionality

Update the button click function to match the code below:

def button_click(e):
        if e.control.text == "=":
            try:
                result.value = str(eval(result.value))
            except Exception:
                result.value = "Error"
        elif e.control.text == "C":
            result.value = ""
        # elif e.control.text == "^":
        # logic for powers
        # pass
        else:
            result.value += e.control.text
        result.update()

Explanation of the code

Let’s take a look at what’s going on in this code. Function button_click designed to handle various button click events in our calculator app.

Here is a brief description of what happens in it:

Getting the button text: When the user clicks the button, the function gets the button text (eg ‘1’, ‘2’, ‘+’, ‘-‘, ‘C’, ‘=’) via e.control.text. This allows you to determine which button the user interacted with.

Cleaning the display: Pressing ‘C’ clears the calculator input. The result is zeroed, and 0 is set on the display. The calculator will be ready for new calculations.

Calculation of expressions: If the user presses the = button, the calculator should calculate the current mathematical expression. For this we use functions str() and eval()and the first converts the result into a string, and the second calculates it and displays it. If the expression is invalid, an exception will be thrown and an “Error” message will be displayed instead.

Other buttons: For other buttons, such as numbers and operators, the function adds the button’s text to the display (which is initially ‘0’ or is cleared when ‘C’ is pressed). It replaces “0” with the value of the button if possible, or appends it to the end of the display/

After the button click is processed, the page is refreshed using the method page.update()to display a new input or result on the calculator display. Whenever you press a button and see a value on the display or a result, it is this method page.update() works

💀Note: Function eval() can be dangerous because it executes any Python code that can be malicious. In a more reliable application, it is better to use another safer method.

User interface improvements

So far, our interface doesn’t look that nice, so let’s update it and make the buttons more attractive using the following code:

button_row0 = Row(
    [
        ElevatedButton(text="C", expand=1, on_click=button_click,
                       bgcolor=colors.RED_ACCENT, color=colors.WHITE),
        ElevatedButton(text="^", expand=1, on_click=button_click,
                       bgcolor=colors.BLUE_ACCENT_100,
                       color=colors.RED_900
                       ),
        ElevatedButton(text="%", expand=1, on_click=button_click,
                       bgcolor=colors.BLUE_ACCENT_100,
                       color=colors.RED_900
                       ),
        ElevatedButton(text="/", expand=1, on_click=button_click,
                       bgcolor=colors.BLUE_ACCENT_100,
                       color=colors.RED_900
                       ),
    ]
)
button_row1 = Row(
    [
        ElevatedButton(text="7", expand=1, on_click=button_click),
        ElevatedButton(text="8", expand=1, on_click=button_click),
        ElevatedButton(text="9", expand=1, on_click=button_click),
        ElevatedButton(text="*", expand=1, on_click=button_click,
                       bgcolor=colors.BLUE_ACCENT_100,
                       color=colors.RED_900
                       ),
    ]
)
button_row2 = Row(
    [
        ElevatedButton(text="4", expand=1, on_click=button_click),
        ElevatedButton(text="5", expand=1, on_click=button_click),
        ElevatedButton(text="6", expand=1, on_click=button_click),
        ElevatedButton(text="-", expand=1, on_click=button_click, 
                       bgcolor=colors.BLUE_ACCENT_100
                       ),
    ]
)
button_row3 = Row(
    [
        ElevatedButton(text="1", expand=1, on_click=button_click),
        ElevatedButton(text="2", expand=1, on_click=button_click),
        ElevatedButton(text="3", expand=1, on_click=button_click),
        ElevatedButton(text="+", expand=1, on_click=button_click, 
                       bgcolor=colors.BLUE_ACCENT_100,
                       color=colors.RED_900),
    ]
)
button_row4 = Row(
    [
        ElevatedButton(text="0", expand=1, on_click=button_click),
        ElevatedButton(text=".", expand=1, on_click=button_click),
        ElevatedButton(
            text="=", expand=2, on_click=button_click,
            bgcolor=colors.GREEN_ACCENT, color=colors.AMBER
        ),
    ]
)

What exactly did we change?

For buttons, we could use an attribute widthbut this would not produce the desired result and could break the user interface. You can see for yourself if you try.

However, we have an alternative option – an attribute expand. It allows you to set values ​​for only two data types: Boolean and int.

For common buttons like operators, numbers, and the clear input button, we’ve increased the value expand by 1, and for the same button – by 2.

Now for what the attribute does expand. This attribute allows the control to fill free space in the specified container. Thus, the buttons with expand 1 will have the same width, and the “equals” button will expand by 2, which means that it will be the same size as the width of the two buttons.

Note that we’ve added colors and background colors to some of our buttons to make them stand out against the number buttons.

And let’s add rounding to the container immediately in the field of the padding attribute like this:border_radius=border_radius.all(20),

Now you have a full-featured calculator built with Flet! Try to customize it to your taste or add additional functions. It can also be packaged as a standalone APK, AAB for download on Google Play Store or Apple App Store.

Here is the complete code:

from flet import (
    app, Page, Container, Column, Row,
    TextField, colors, border_radius, ElevatedButton, TextAlign, TextStyle
)
from flet_core import ThemeMode


def main(page: Page):
    page.title = "Calculator"
    page.theme_mode = ThemeMode.DARK
    page.horizontal_alignment = page.vertical_alignment="center"
    result = TextField(
        hint_text="0", text_size=20,
        color="white", text_align=TextAlign.RIGHT,
        hint_style=TextStyle(
            color=colors.WHITE, size=20
        ),
        read_only=True
    )

    def button_click(e):
        if e.control.text == "=":
            try:
                result.value = str(eval(result.value))
            except Exception:
                result.value = "Error"
        elif e.control.text == "C":
            result.value = ""
        # elif e.control.text == "^":
        # logic for powers
        # pass
        else:
            result.value += e.control.text
        result.update()

    button_row0 = Row(
        [
            ElevatedButton(text="C", expand=1, on_click=button_click,
                           bgcolor=colors.RED_ACCENT, color=colors.WHITE),
            ElevatedButton(text="^", expand=1, on_click=button_click,
                           bgcolor=colors.BLUE_ACCENT_100,
                           color=colors.RED_900
                           ),
            ElevatedButton(text="%", expand=1, on_click=button_click,
                           bgcolor=colors.BLUE_ACCENT_100,
                           color=colors.RED_900
                           ),
            ElevatedButton(text="/", expand=1, on_click=button_click,
                           bgcolor=colors.BLUE_ACCENT_100,
                           color=colors.RED_900
                           ),
        ]
    )
    button_row1 = Row(
        [
            ElevatedButton(text="7", expand=1, on_click=button_click),
            ElevatedButton(text="8", expand=1, on_click=button_click),
            ElevatedButton(text="9", expand=1, on_click=button_click),
            ElevatedButton(text="*", expand=1, on_click=button_click,
                           bgcolor=colors.BLUE_ACCENT_100,
                           color=colors.RED_900
                           ),
        ]
    )
    button_row2 = Row(
        [
            ElevatedButton(text="4", expand=1, on_click=button_click),
            ElevatedButton(text="5", expand=1, on_click=button_click),
            ElevatedButton(text="6", expand=1, on_click=button_click),
            ElevatedButton(text="-", expand=1, on_click=button_click, 
                           bgcolor=colors.BLUE_ACCENT_100
                           ),
        ]
    )
    button_row3 = Row(
        [
            ElevatedButton(text="1", expand=1, on_click=button_click),
            ElevatedButton(text="2", expand=1, on_click=button_click),
            ElevatedButton(text="3", expand=1, on_click=button_click),
            ElevatedButton(text="+", expand=1, on_click=button_click, 
                           bgcolor=colors.BLUE_ACCENT_100,
                           color=colors.RED_900),
        ]
    )
    button_row4 = Row(
        [
            ElevatedButton(text="0", expand=1, on_click=button_click),
            ElevatedButton(text=".", expand=1, on_click=button_click),
            ElevatedButton(
                text="=", expand=2, on_click=button_click,
                bgcolor=colors.GREEN_ACCENT, color=colors.AMBER
            ),
        ]
    )
    container = Container(
        width=350, padding=20,
        bgcolor=colors.BLACK, border_radius=border_radius.all(20),
        content=Column(
            [
                result,
                button_row0, button_row1, button_row2,
                button_row3, button_row4
            ]
        )
    )
    page.add(container)


if __name__ == '__main__':
    app(target=main)

👉 And if you are interested in other useful materials about Python and IT, you can subscribe to my channel in tg: PythonTalk 👈

Related posts