跳到主要内容

· 阅读需 3 分钟
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 as ft
from flet.auth.providers.github_oauth_provider import GitHubOAuthProvider

def main(page: ft.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(ft.ElevatedButton("Login with GitHub", on_click=login_click))

ft.app(target=main, port=8550, view=ft.WEB_BROWSER)
备注

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 as ft
def main(page: ft.Page):
page.window_bgcolor = ft.colors.TRANSPARENT
page.bgcolor=ft.colors.TRANSPARENT
page.window_title_bar_hidden = True
page.window_frameless = True
page.window_left = 400
page.window_top = 400
page.add(ft.ElevatedButton("I'm a floating button!"))
ft.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 分钟
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 as ft

file_picker = ft.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:

ft.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 as ft

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

file_picker = ft.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 as ft

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)

ft.ElevatedButton("Upload", on_click=upload_files)
备注

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:

ft.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:

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

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

page.add(ft.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 分钟
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 分钟
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 as ft

def main(page: ft.Page):

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

ft.app(target=main)

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

Radial gradient

import flet as ft

def main(page: ft.Page):

page.add(
ft.Container(
alignment=ft.alignment.center,
gradient=ft.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,
)
)

ft.app(target=main)

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

Sweep gradient

import math
import flet as ft

def main(page: ft.Page):

page.add(
ft.Container(
alignment=ft.alignment.center,
gradient=SweepGradient(
center=ft.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,
)
)

ft.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 as ft
from flet.border import BorderSide
from flet.buttons import RoundedRectangleBorder

def main(page: ft.Page):

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

ft.app(target=main)

ft.MaterialState.DEFAULT state is a fallback style.

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

import flet as ft
from flet.buttons import (
BeveledRectangleBorder,
CircleBorder,
CountinuosRectangleBorder,
RoundedRectangleBorder,
StadiumBorder,
)

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

ft.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 as ft

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

ft.app(target=main)

An example of styled Dropdown control:

import flet as ft

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

ft.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 as ft

def main(page: ft.Page):

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

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

ft.app(target=main)

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

· 阅读需 3 分钟
Feodor Fitsner

Flet controls are objects and to access their properties we need to keep references (variables) to those objects.

Consider the following example:

import flet as ft

def main(page):

first_name = ft.TextField(label="First name", autofocus=True)
last_name = ft.TextField(label="Last name")
greetings = ft.Column()

def btn_click(e):
greetings.controls.append(ft.Text(f"Hello, {first_name.value} {last_name.value}!"))
first_name.value = ""
last_name.value = ""
page.update()
first_name.focus()

page.add(
first_name,
last_name,
ft.ElevatedButton("Say hello!", on_click=btn_click),
greetings,
)

ft.app(target=main)

In the very beginning of main() method we create three controls which we are going to use in button's on_click handler: two TextField for first and last names and a Column - container for greeting messages. We create controls with all their properties set and in the end of main() method, in page.add() call, we use their references (variables).

When more and mode controls and event handlers added it becomes challenging to keep all control definitions in one place, so they become scattered across main() body. Glancing at page.add() parameters it's hard to imagine (without constant jumping to variable definitions in IDE) what would the end form look like:

    page.add(
first_name,
last_name,
ft.ElevatedButton("Say hello!", on_click=btn_click),
greetings,
)

Is first_name a TextField, does it have autofocus set? Is greetings a Row or a Column?

Ref class

Flet provides Ref utility class which allows to define a reference to the control, use that reference in event handlers and set the reference to a real control later, while building a tree. The idea comes from React.

To define a new typed control reference:

first_name = ft.Ref[ft.TextField]()

To access referenced control (control de-reference) use Ref.current property:

# empty first name
first_name.current.value = ""

To assign control to a reference set Control.ref property to a reference:

page.add(
ft.TextField(ref=first_name, label="First name", autofocus=True)
)
备注

All Flet controls have ref property.

We could re-write our program to use references:

import flet as ft


def main(page):

first_name = ft.Ref[ft.TextField]()
last_name = ft.Ref[ft.TextField]()
greetings = ft.Ref[ft.Column]()

def btn_click(e):
greetings.current.controls.append(
ft.Text(f"Hello, {first_name.current.value} {last_name.current.value}!")
)
first_name.current.value = ""
last_name.current.value = ""
page.update()
first_name.current.focus()

page.add(
ft.TextField(ref=first_name, label="First name", autofocus=True),
ft.TextField(ref=last_name, label="Last name"),
ft.ElevatedButton("Say hello!", on_click=btn_click),
ft.Column(ref=greetings),
)

ft.app(target=main)

Now we can clearly see in page.add() the structure of the page and all the controls it's built of.

Yes, the logic becomes a little bit more verbose as you need to add .current. to access ref's control, but it's a matter of personal preference :)

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

· 阅读需 4 分钟
Feodor Fitsner

Flet project has received a lot of attention recently and we would like to thank all the developers who tried Flet and have been spreading the word about it in the communities! Your support motivates us to move Flet project forward with faster pace!

New Flet developers are constantly asking if there is a way to package Flet program to an .apk file to deploy to Android devices or .ipa to deploy to iOS.

In this post I would like to share our vision for Flet going mobile and provide a roadmap.

Server-Driven UI

Flet is a Server-driven UI (SDUI) framework. SDUI is an emerging technology which is the best described in Technology Radar post:

Server-driven UI separates the rendering into a generic container in the mobile app while the structure and data for each view is provided by the server. This means that changes that once required a round trip to an app store can now be accomplished via simple changes to the responses the server sends.

Companies like DoorDash, Airbnb, Lyft and others have been successfully implementing Server-driven UI in their mobile apps to reduce time-to-market.

Flet approach

Flet is going to implement Server-Driven UI approach where program written in Python or other language is running on the server and only a thin client - either standalone Flutter app (.apk or .ipa package) in app store or a Flutter widget as a part of another app - is delivered to a mobile:

Once SDUI experience is ready we'll start working on a standalone mobile package.

Roadmap

To provide the best experience for Flet apps on mobile platforms, we plan to release the following items by the end of this year:

Flet widget for Flutter

The first step we are going to do is to separate Flet client into a Flutter widget and publish the package at https://pub.dev. Flet widget could be then integrated by mobile developers into existing or new Flutter apps for adding dynamic server-driven UI experiences to the core app functionality. A new Flutter app could be also created with a single Flet widget just for the purpose of hosting a complete Flet app.

Developers will follow Flutter guide for packaging, signing and distributing their apps to Android, iOS, Linux, macOS or Windows platforms.

Flet team will provide sample CI pipelines to automate packaging, signing and publishing of Flutter apps.

Flet Studio for iOS and Android

The next step is a standalone "Flet Studio" app (the name is not final) in App Store and Google Play for "testing mobile experiences developed with Flet framework". Developers or beta testers will be able to "register" URL of their hosted Flet app within Flet Studio and instantly see how it performs on a mobile device.

White-labeled Flet mobile app

We are going to provide a guide and CI pipeline for automatic publishing of white-labeled Flet app to a user App Store or Google Play account. This app will be "pinned" to a specific app URL and could additionally bundle app assets (media, fonts) to minimize network usage.

Standalone mobile package for Flet app

We are going to investigate the way and develop a prototype for bundling together Flet framework, user program, language runtime and all dependencies into a standalone mobile package (.apk or .ipa package), so Flet program does not require a web server.

Embedding Flet into native apps

We are going to provide a guide, sample apps and CI pipeline to integrate Flet widget into existing native Android and iOS apps (not developed with Flutter) using Flutter Add-to-App feature. Put Flutter to work article gives a real-world example on how to integrate Flutter into existing mobile app.

This is the current plan.

In the meantime, give Flet a try and let us know what you think!

· 阅读需 5 分钟
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 as ft

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

ft.app(target=main, view=ft.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 as ft

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

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

page.on_route_change = route_change
page.update()

ft.app(target=main, view=ft.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 as ft

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

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

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

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

ft.app(target=main, view=ft.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 as ft

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

def route_change(route):
page.views.clear()
page.views.append(
ft.View(
"/",
[
ft.AppBar(title=ft.Text("Flet app"), bgcolor=ft.colors.SURFACE_VARIANT),
ft.ElevatedButton("Visit Store", on_click=lambda _: page.go("/store")),
],
)
)
if page.route == "/store":
page.views.append(
ft.View(
"/store",
[
ft.AppBar(title=ft.Text("Store"), bgcolor=ft.colors.SURFACE_VARIANT),
ft.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)


ft.app(target=main, view=ft.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! :)

备注

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 分钟
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 as ft

def main(page: ft.Page):

page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.vertical_alignment = ft.MainAxisAlignment.CENTER

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

ft.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!

· 阅读需 2 分钟
Feodor Fitsner

You can now use your own fonts in a Flet app!

The following font formats are supported:

  • .ttc
  • .ttf
  • .otf

Use page.fonts property to import fonts.

Set page.fonts property to a dictionary where key is the font family name to refer that font and the value is the URL of the font file to import:

def main(page: ft.Page):
page.fonts = {
"Kanit": "https://raw.githubusercontent.com/google/fonts/master/ofl/kanit/Kanit-Bold.ttf",
"Aleo Bold Italic": "https://raw.githubusercontent.com/google/fonts/master/ofl/aleo/Aleo-BoldItalic.ttf"
}
page.update()

# ...

Font can be imported from external resource by providing an absolute URL or from application assets by providing relative URL and assets_dir.

Specify assets_dir in flet.app() call to set the location of assets that should be available to the application. assets_dir could be a relative to your main.py directory or an absolute path. For example, consider the following program structure:

/assets
/fonts
/OpenSans-Regular.ttf
main.py

Code sample

The following program loads "Kanit" font from GitHub and "Open Sans" from the assets. "Kanit" is set as a default app font and "Open Sans" is used for a specific Text control:

import flet as ft

def main(page: ft.Page):
page.title = "Custom fonts"

page.fonts = {
"Kanit": "https://raw.githubusercontent.com/google/fonts/master/ofl/kanit/Kanit-Bold.ttf",
"Open Sans": "fonts/OpenSans-Regular.ttf",
}

page.theme = Theme(font_family="Kanit")

page.add(
ft.Text("This is rendered with Kanit font"),
ft.Text("This is Open Sans font example", font_family="Open Sans"),
)

ft.app(target=main, assets_dir="assets")

Static vs Variable fonts

At the moment only static fonts are supported, i.e. fonts containing only one spacific width/weight/style combination, for example "Open Sans Regular" or "Roboto Bold Italic".

Variable fonts support is still work in progress.

However, if you need to use a variable font in your app you can create static "instantiations" at specific weights using fonttools, then use those:

fonttools varLib.mutator ./YourVariableFont-VF.ttf wght=140 wdth=85

To explore available font features (e.g. possible options for wght) use Wakamai Fondue online tool.

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

· 阅读需 2 分钟
Feodor Fitsner

Today we announce the first release of Flet!

Flet is a framework for building real-time web, desktop and mobile applications in Python.

No more complex architecture with JavaScript frontend, REST API backend, database, cache, etc. With Flet you just write a monolith stateful app in Python only and get multi-user, realtime Single-Page Application (SPA) or a mobile app.

To start developing with Flet, you just need your favorite IDE or text editor. No SDKs, no thousands of dependencies, no complex tooling - Flet has built-in web server with assets hosting and desktop clients.

Flet UI is built with Flutter, so your app looks professional and can be delivered to any platform. Flet simplifies Flutter model by combining smaller "widgets" into ready-to-use "controls" with imperative programming model. You get all the power of Flutter without having to learn Dart!

Flet app is deployed as a regular web app and can be instanly accessed with a browser or installed as a PWA on a mobile device. Web app also exposes an API that can be used by a Flet client (planned for future releases) running on iOS and Android and providing native mobile experience.

Some examples:

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