We write a calculator in Python using Flet
Contents
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.
-
We install Flet, for example using pip. Open the terminal or command line and enter
pip install flet
-
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 bgcolor
without 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 ElevatedButton
which 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 width
but 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 👈