Skip to main content

6 posts tagged with "release"

View All Tags

ยท 3 min read
Feodor Fitsner

User authentication in Flet is here! ๐ŸŽ‰

Now you can implement user authentication ("Login with X" buttons) in your Flet app using 3rd-party identity providers such as GitHub, Google, Azure, Auth0, LinkedIn and others:

Traditionally, this release is not just about authentication, but it adds a ton of accompanying functionality and small improvements:

Authenticationโ€‹

Flet authentication features:

  • Works with Flet desktop, web and mobile apps.
  • Using multiple authentication providers in one app.
  • Built-in OAuth providers with automatic user details fetching:
    • GitHub
    • Azure
    • Google
    • Auth0
  • Optional groups fetching.
  • Automatic token refresh.
  • Login with a saved token ("Remember me").
  • Custom OAuth providers.

A simple example on how to add "Login with GitHub" button to your Flet app:

import os

import flet
from flet import ElevatedButton, Page
from flet.auth.providers.github_oauth_provider import GitHubOAuthProvider

def main(page: Page):

provider = GitHubOAuthProvider(
client_id=os.getenv("GITHUB_CLIENT_ID"),
client_secret=os.getenv("GITHUB_CLIENT_SECRET"),
redirect_url="http://localhost:8550/api/oauth/redirect",
)

def login_click(e):
page.login(provider)

def on_login(e):
print("Access token:", page.auth.token.access_token)
print("User ID:", page.auth.user.id)

page.on_login = on_login
page.add(ElevatedButton("Login with GitHub", on_click=login_click))

flet.app(target=main, port=8550, view=flet.WEB_BROWSER)
note

Before running the app set the secret environment variables in a command line:

$ export GITHUB_CLIENT_ID="<client_id>"
$ export GITHUB_CLIENT_SECRET="<client_secret>"

Read Authentication guide for more information and examples.

Client storageโ€‹

Flet's client storage API that allows storing key-value data on a client side in a persistent storage. Flet implementation uses shared_preferences Flutter package.

Writing data to the storage:

page.client_storage.set("key", "value")

Reading data:

value = page.client_storage.get("key")

Read Client storage guide for more information and examples.

Session storageโ€‹

Flet introduces an API for storing key-value data in user's session on a server side.

Writing data to the session:

page.session.set("key", "value")

Reading data:

value = page.session.get("key")

Read Session storage guide for more information and examples

Encryption APIโ€‹

In this release Flet introduces utility methods to encrypt and decrypt sensitive text data using symmetric algorithm (where the same key is used for encryption and decryption). It uses Fernet implementation from cryptography package, which is AES 128 with some additional hardening, plus PBKDF2 to derive encryption key from a user passphrase.

Encrypting data:

from flet.security import encrypt, decrypt
secret_key = "S3CreT!"
plain_text = "This is a secret message!"
encrypted_data = encrypt(plain_text, secret_key)

Decrypting data:

from flet.security import encrypt, decrypt
secret_key = "S3CreT!"
plain_text = decrypt(encrypted_data, secret_key)
print(plain_text)

Continue reading for more information and examples.

Other improvementsโ€‹

import flet
from flet import ElevatedButton, Page, colors
def main(page: Page):
page.window_bgcolor = colors.TRANSPARENT
page.bgcolor = colors.TRANSPARENT
page.window_title_bar_hidden = True
page.window_frameless = True
page.window_left = 400
page.window_top = 400
page.add(ElevatedButton("I'm a floating button!"))
flet.app(target=main)

Upgrade Flet module to the latest version (pip install flet --upgrade), integrate auth in your app and let us know what you think!

Enjoy!

ยท 4 min read
Feodor Fitsner

Finally, File picker with uploads has arrived! ๐ŸŽ‰

File picker control opens a native OS dialog for selecting files and directories. It's based on a fantastic file_picker Flutter package.

It works on all platforms: Web, macOS, Window, Linux, iOS and Android.

Check out source code of the demo above.

File picker allows opening three dialogs:

  • Pick files - one or multiple, any files or only specific types.
  • Save file - choose directory and file name.
  • Get directory - select directory.

When running Flet app in a browser only "Pick files" option is available and it's used for uploads only as it, obviously, doesn't return a full path to a selected file.

Where file picker really shines is a desktop! All three dialogs return full paths to selected files and directories - great assistance to your users!

Using file picker in your appโ€‹

It is recommended to add file picker to page.overlay.controls collection, so it doesn't affect the layout of your app. Despite file picker has 0x0 size it is still considered as a control when put into Row or Column.

import flet
from flet import FilePicker

file_picker = FilePicker()
page.overlay.append(file_picker)
page.update()

To open file picker dialog call one of the three methods:

  • pick_files()
  • save_file()
  • get_directory_path()

Lambda works pretty nice for that:

ElevatedButton("Choose files...",
on_click=lambda _: file_picker.pick_files(allow_multiple=True))

When dialog is closed FilePicker.on_result event handler is called which event object has one of the following properties set:

  • files - "Pick files" dialog only, a list of selected files or None if dialog was cancelled.
  • path - "Save file" and "Get directory" dialogs, a full path to a file or directory or None if dialog was cancelled.
import flet
from flet import FilePicker, FilePickerResultEvent

def on_dialog_result(e: FilePickerResultEvent):
print("Selected files:", e.files)
print("Selected file or directory:", e.path)

file_picker = FilePicker(on_result=on_dialog_result)

The last result is always available in FilePicker.result property.

Check File picker control docs for all available dialog methods and their parameters.

Uploading filesโ€‹

File picker has built-in upload capabilities that work on all platforms and the web.

To upload one or more files you should call FilePicker.pick_files() first. When the files are selected by the user they are not automatically uploaded anywhere, but instead their references are kept in the file picker state.

To perform an actual upload you should call FilePicker.upload() method and pass the list of files that need to be uploaded along with their upload URLs and upload method (PUT or POST):

import flet
from flet import FilePicker, FilePickerResultEvent, FilePickerUploadFile

def upload_files(e):
upload_list = []
if file_picker.result != None and file_picker.result.files != None:
for f in file_picker.result.files:
upload_list.append(
FilePickerUploadFile(
f.name,
upload_url=page.get_upload_url(f.name, 600),
)
)
file_picker.upload(upload_list)

ElevatedButton("Upload", on_click=upload_files)
note

If you need to separate uploads for each user you can specify a filename prepended with any number of directories in page.get_upload_url() call, for example:

upload_url = page.get_upload_url(f"/{username}/pictures/{f.name}", 600)

/{username}/pictures directories will be automatically created inside upload_dir if not exist.

Upload storageโ€‹

Notice the usage of page.get_upload_url() method - it generates a presigned upload URL for Flet's internal upload storage.

Use any storage for file uploads

You can generate presigned upload URL for AWS S3 storage using boto3 library.

The same technique should work for Wasabi, Backblaze, MinIO and any other storage providers with S3-compatible API.

To enable Flet saving uploaded files to a directory provide full or relative path to that directory in flet.app() call:

flet.app(target=main, upload_dir="uploads")

You can even put uploads inside "assets" directory, so uploaded files, e.g. pictures, docs or other media, can be accessed from a Flet client right away:

flet.app(target=main, assets_dir="assets", upload_dir="assets/uploads")

and somewhere in your app you can display uploaded picture with:

page.add(Image(src="/uploads/<some-uploaded-picture.png>"))

Upload progressโ€‹

Once FilePicker.upload() method is called Flet client asynchronously starts uploading selected files one-by-one and reports the progress via FilePicker.on_upload callback.

Event object of on_upload event is an instance of FilePickerUploadEvent class with the following fields:

  • file_name
  • progress - a value from 0.0 to 1.0.
  • error

The callback is called at least twice for every uploaded file: with 0 progress before upload begins and with 1.0 progress when upload is finished. For files larger than 1 MB a progress is additionally reported for every 10% uploaded.

Check that example demonstrating multiple file uploads:

See File picker control docs for all its properties and examples.

Upgrade Flet module to the latest version (pip install flet --upgrade), give File Picker a try and let us know what you think!

Enjoy!

ยท 2 min read
Feodor Fitsner

Despite Flet release debuting animations support was released some time ago, we've just finished documenting its new features! We all know if the feature is not documented it just doesn't exist! ๐Ÿ˜‰

Flutter offers multiple approaches for creating animations such "implicit", "explicit", "tween", "stagered", "pre-canned" animations as well as displaying animation scenes prepared in Rive and Lottie editors.

We are starting with "implicit" animations which allows you to animate a control property by setting a target value; whenever that target value changes, the control animates the property from the old value to the new one.

Demo timeโ€‹

Explore demo sources. The demo is hosted on Heroku, by the way, so you can use it as a starting point for your own deployments.

Implicit animationsโ€‹

Implicit animations can be enabled for the following control properties:

Additionally, all Container control properties can be now animated and there is a new AnimatedSwitcher control for animated transition between old a new content.

Other new featuresโ€‹

Markdown controlโ€‹

Allows to render text in Markdown format. Supports various extensions: CommonMark, GitHub Web and GitHub Flavored.

See Markdown control docs for more information and examples.

URL launcherโ€‹

page.launch_url(url) method allows programmatically opening a URL in a new browser window, for example:

page.launch_url("https://google.com")

It also works nice with Markdown control for opening links within markdown document.

Keyboard shortcutsโ€‹

Page now contains on_keyboard_event event handlet to globally intercept all keystrokes.

Check this simple usage example.

Accessibility improvementsโ€‹

We added Accessibility section to the docs covering semantics support for screen readers.

ShaderMark controlโ€‹

A control that applies a mask generated by a shader to its content. Allows making nice effects like gradually fading out images.

That's it!

Give Flet a try and let us know what you think!

ยท 4 min read
Feodor Fitsner

We've just released Flet 0.1.46 adding new exciting features:

  • Gradient backgrounds in Container
  • Extensive styling for buttons, TextField and Dropdown controls
  • ...and more

Gradient backgroundsโ€‹

Linear gradientโ€‹

import math
import flet
from flet import Alignment, Container, LinearGradient, Page, alignment

def main(page: Page):

page.add(
Container(
alignment=alignment.center,
gradient=LinearGradient(
begin=alignment.top_left,
end=Alignment(0.8, 1),
colors=[
"0xff1f005c",
"0xff5b0060",
"0xff870160",
"0xffac255e",
"0xffca485c",
"0xffe16b5c",
"0xfff39060",
"0xffffb56b",
],
tile_mode="mirror",
rotation=math.pi / 3,
),
width=150,
height=150,
border_radius=5,
)
)

flet.app(target=main)

Check Container.gradient docs for more information about LinearGradient properties.

Radial gradientโ€‹

import flet
from flet import Alignment, Container, Page, RadialGradient, alignment

def main(page: Page):

page.add(
Container(
alignment=alignment.center,
gradient=RadialGradient(
center=Alignment(0.7, -0.6),
radius=0.2,
colors=[
"0xFFFFFF00", # yellow sun
"0xFF0099FF", # blue sky
],
stops=[0.4, 1.0],
),
width=150,
height=150,
border_radius=5,
)
)

flet.app(target=main)

Check Container.gradient docs for more information about RadialGradient properties.

Sweep gradientโ€‹

import math
import flet
from flet import Container, Page, SweepGradient, alignment

def main(page: Page):

page.add(
Container(
alignment=alignment.center,
gradient=SweepGradient(
center=alignment.center,
start_angle=0.0,
end_angle=math.pi * 2,
colors=[
"0xFF4285F4",
"0xFF34A853",
"0xFFFBBC05",
"0xFFEA4335",
"0xFF4285F4",
],
stops=[0.0, 0.25, 0.5, 0.75, 1.0],
),
width=150,
height=150,
border_radius=5,
)
)

flet.app(target=main)

Check Container.gradient docs for more information about SweepGradient properties.

Buttons stylingโ€‹

This Flet release introduces style property to all button controls which is an instance of ButtonStyle class. ButtonStyle allows controling all visual aspects of a button, such as shape, foreground, background and shadow colors, content padding, border width and radius!

Moreover, each individual style attribute could be configured for a different "Material states" of a button, such as "hovered", "focused", "disabled" and others. For example, you can configure a different shape, background color for a hovered state and configure fallback values for all other states.

Check this "extreme" styling example:

import flet
from flet import ButtonStyle, ElevatedButton, Page, colors
from flet.border import BorderSide
from flet.buttons import RoundedRectangleBorder

def main(page: Page):

page.add(
ElevatedButton(
"Styled button 1",
style=ButtonStyle(
color={
"hovered": colors.WHITE,
"focused": colors.BLUE,
"": colors.BLACK,
},
bgcolor={"focused": colors.PINK_200, "": colors.YELLOW},
padding={"hovered": 20},
overlay_color=colors.TRANSPARENT,
elevation={"pressed": 0, "": 1},
animation_duration=500,
side={
"": BorderSide(1, colors.BLUE),
"hovered": BorderSide(2, colors.BLUE),
},
shape={
"hovered": RoundedRectangleBorder(radius=20),
"": RoundedRectangleBorder(radius=2),
},
),
)
)

flet.app(target=main)

Empty string ("") state is a fallback style.

Button shape could also be changed with ButtonStyle.shape property:

import flet
from flet import ButtonStyle, FilledButton, Page
from flet.buttons import (
BeveledRectangleBorder,
CircleBorder,
CountinuosRectangleBorder,
RoundedRectangleBorder,
StadiumBorder,
)

def main(page: Page):
page.padding = 30
page.spacing = 30
page.add(
FilledButton(
"Stadium",
style=ButtonStyle(
shape=StadiumBorder(),
),
),
FilledButton(
"Rounded rectangle",
style=ButtonStyle(
shape=RoundedRectangleBorder(radius=10),
),
),
FilledButton(
"Continuous rectangle",
style=ButtonStyle(
shape=CountinuosRectangleBorder(radius=30),
),
),
FilledButton(
"Beveled rectangle",
style=ButtonStyle(
shape=BeveledRectangleBorder(radius=10),
),
),
FilledButton(
"Circle",
style=ButtonStyle(shape=CircleBorder(), padding=30),
),
)

flet.app(target=main)

Check ElevatedButton.style property docs for a complete description of ButtonStyle class and its properties.

TextField and Dropdown stylingโ€‹

It is now possible to configure text size, border style and corners radius for normal and focused states of TextField and Dropdown controls. TextField also allows configuring colors for a cursor and selection.

Additionally, the maximum length of entered into TextField can now be limited with max_length property.

We also introduced capitalization property for automatic capitalization of characters as you type them into TextField. You can choose from 4 capitalization strategies: none (default), characters, words and sentences.

An example of styled TextField with max_length and capitalization:

import flet
from flet import Page, TextField, colors

def main(page: Page):
page.padding = 50
page.add(
TextField(
text_size=30,
cursor_color=colors.RED,
selection_color=colors.YELLOW,
color=colors.PINK,
bgcolor=colors.BLACK26,
filled=True,
focused_color=colors.GREEN,
focused_bgcolor=colors.CYAN_200,
border_radius=30,
border_color=colors.GREEN_800,
focused_border_color=colors.GREEN_ACCENT_400,
max_length=20,
capitalization="characters",
)
)

flet.app(target=main)

An example of styled Dropdown control:

import flet
from flet import Dropdown, Page, colors, dropdown

def main(page: Page):
page.padding = 50
page.add(
Dropdown(
options=[
dropdown.Option("a", "Item A"),
dropdown.Option("b", "Item B"),
dropdown.Option("c", "Item C"),
],
border_radius=30,
filled=True,
border_color=colors.TRANSPARENT,
bgcolor=colors.BLACK12,
focused_bgcolor=colors.BLUE_100,
)
)

flet.app(target=main)

Other changesโ€‹

IconButton got selected state which plays nice with a new style.

This is an example of a toggle icon button:

import flet
from flet import ButtonStyle, IconButton, Page, colors, icons

def main(page: Page):

def toggle_icon_button(e):
e.control.selected = not e.control.selected
e.control.update()

page.add(
IconButton(
icon=icons.BATTERY_1_BAR,
selected_icon=icons.BATTERY_FULL,
on_click=toggle_icon_button,
selected=False,
style=ButtonStyle(color={"selected": colors.GREEN, "": colors.RED}),
)
)

flet.app(target=main)

Give Flet a try and let us know what you think!

ยท 5 min read
Feodor Fitsner

Flet 0.1.42 has been released with navigation and routing!

Navigation and routing is an essential feature of Single Page Applications (SPA) which allows organizing application user interface into virtual pages (views) and "navigate" between them while application URL reflects the current state of the app.

For mobile apps navigation and routing serves as a deep linking to specific application parts.

Well, it took more efforts than expected to add navigation and routing into Flet as the implementation is based on Navigator 2.0 Flutter API and required to replace Flet's "Page" abstraction with "Page and Views". Flutter's newer navigation and routing API has substantial improvements such as:

  1. Programmatic control over history stack.
  2. An easy way to intercept a call to "Back" button in AppBar.
  3. Robust synchronization with browser history.

Explore source code of the example above.

Page routeโ€‹

Page route is a portion of application URL after # symbol:

Default application route, if not set in application URL by the user, is /. All routes start with /, for example /store, /authors/1/books/2.

Application route can be obtained by reading page.route property, for example:

import flet
from flet import Page, Text

def main(page: Page):
page.add(Text(f"Initial route: {page.route}"))

flet.app(target=main, view=flet.WEB_BROWSER)

Grab application URL, open a new browser tab, paste the URL, modify its part after # to /test and hit enter. You should see "Initial route: /test".

Every time the route in the URL is changed (by editing the URL or navigating browser history with Back/Forward buttons) Flet calls page.on_route_change event handler:

import flet
from flet import Page, Text

def main(page: Page):
page.add(Text(f"Initial route: {page.route}"))

def route_change(route):
page.add(Text(f"New route: {route}"))

page.on_route_change = route_change
page.update()

flet.app(target=main, view=flet.WEB_BROWSER)

Now try updating URL hash a few times and then use Back/Forward buttons! You should see a new message added to a page each time the route changes:

Route can be changed programmatically, by updating page.route property:

import flet
from flet import ElevatedButton, Page, Text

def main(page: Page):
page.add(Text(f"Initial route: {page.route}"))

def route_change(route):
page.add(Text(f"New route: {route}"))

def go_store(e):
page.route = "/store"
page.update()

page.on_route_change = route_change
page.add(ElevatedButton("Go to Store", on_click=go_store))

flet.app(target=main, view=flet.WEB_BROWSER)

Click "Go to Store" button and you'll see application URL is changed and a new item is pushed in a browser history. You can use browser "Back" button to navigate to a previous route.

Page viewsโ€‹

Flet's Page now is not just a single page, but a container for View layered on top of each other like a sandwich:

A collection of views represents navigator history. Page has page.views property to access views collection.

The last view in the list is the one currently displayed on a page. Views list must have at least one element (root view).

To simulate a transition between pages change page.route and add a new View in the end of page.view list.

Pop the last view from the collection and change route to a "previous" one in page.on_view_pop event handler to go back.

Building views on route changeโ€‹

To build a reliable navigation there must be a single place in the program which builds a list of views depending on the current route. Other words, navigation history stack (represented by the list of views) must be a function of a route.

This place is page.on_route_change event handler.

Let's put everything together into a complete example which allows navigating between two pages:

import flet
from flet import AppBar, ElevatedButton, Page, Text, View, colors

def main(page: Page):
page.title = "Routes Example"

def route_change(route):
page.views.clear()
page.views.append(
View(
"/",
[
AppBar(title=Text("Flet app"), bgcolor=colors.SURFACE_VARIANT),
ElevatedButton("Visit Store", on_click=lambda _: page.go("/store")),
],
)
)
if page.route == "/store":
page.views.append(
View(
"/store",
[
AppBar(title=Text("Store"), bgcolor=colors.SURFACE_VARIANT),
ElevatedButton("Go Home", on_click=lambda _: page.go("/")),
],
)
)
page.update()

def view_pop(view):
page.views.pop()
top_view = page.views[-1]
page.go(top_view.route)

page.on_route_change = route_change
page.on_view_pop = view_pop
page.go(page.route)


flet.app(target=main, view=flet.WEB_BROWSER)

Try navigating between pages using "Visit Store" and "Go Home" buttons, Back/Forward browser buttons, manually changing route in the URL - it works no matter what! :)

note

To "navigate" between pages we used page.go(route) - a helper method that updates page.route, calls page.on_route_change event handler to update views and finally calls page.update().

Notice the usage of page.on_view_pop event handler. It fires when the user clicks automatic "Back" button in AppBar control. In the handler we remove the last element from views collection and navigate to view's root "under" it.

Route templatesโ€‹

Flet offers TemplateRoute - an utility class based on repath library which allows matching ExpressJS-like routes and parsing their parameters, for example /account/:account_id/orders/:order_id.

TemplateRoute plays great with route change event:

troute = TemplateRoute(page.route)

if troute.match("/books/:id"):
print("Book view ID:", troute.id)
elif troute.match("/account/:account_id/orders/:order_id"):
print("Account:", troute.account_id, "Order:", troute.order_id)
else:
print("Unknown route")

You can read more about template syntax supported by repath library here.

That's all for today!

Give Flet a try and let us know what you think!

ยท 2 min read
Feodor Fitsner

We have just released Flet 0.1.41 with drag-and-drop support and other neat features such as absolute positioning of controls inside stack and clickable container!

Drag and Dropโ€‹

Making drag-and-drop in Flet is a real joy - thanks to a smart drag-and-drop implementation in Flutter! You just have "draggable" control which could be dragged to a "drag target" which calls on_accept event handler when draggable is dropped.

Take a look at Drag-and-Drop example.

Explore Draggable and DragTarget controls, their properties and events.

Absolute positioning inside Stackโ€‹

All visible controls now have left top, right and bottom properties to let them be absolutely positioned inside Stack, for example:

import flet
from flet import Container, Page, Stack, Text, colors

def main(page: Page):

page.horizontal_alignment = "center"
page.vertical_alignment = "center"

page.add(
Container(
Stack(
[
Text("1", color=colors.WHITE),
Text("2", color=colors.WHITE, right=0),
Text("3", color=colors.WHITE, right=0, bottom=0),
Text("4", color=colors.WHITE, left=0, bottom=0),
Text("5", color=colors.WHITE, left=40, top=35),
]
),
border_radius=8,
padding=5,
width=100,
height=100,
bgcolor=colors.BROWN_700,
)
)

flet.app(target=main)

Clickable containerโ€‹

Container control has got on_click event which allows you to make a button from any control and with a beautiful material ripple effect when ink is set to True!

See source code for the example above.

Give Flet a try and let us know what you think!