Flet 2 - Flaskr Flet App
Inleiding
Met de basiskennis uit de vorige les op zak zullen we in deze les een Flet frontend maken voor de bestaande Flaskr blog applicatie.
Als backend kiezen we natuurlijk voor de Connexion implementatie. Een standaard (Open)API is eenvoudiger aan te spreken en we willen het liefst gestructureerde data, zoals JSON, terugkrijgen om mee te werken.
We kunnen op zich gewoon verderwerken in het bestaand flet-tutorial project. Je kan ook de Flet frontend toevoegen in het Flask project (flask-tutorial), of een nieuw project starten.
Het is wel belangrijk dat je een werkende Flaskr API hebt uit de Connexion les :
cd flask-tutorial
git switch flaskr-api-sqla
uv sync
uv run python -m flaskr.app
# Vanuit een andere terminal:
curl http://127.0.0.1:5000/posts/2
# Output:
{
"author": "test",
"body": "Somebody once told me",
"created": "2025-12-04T12:53:17Z",
"id": 2,
"title": "And another one!"
}
Voor het vervolg van deze les gaan we er van uit dat de flaskr-api altijd draait! Het is handig als dit in een apart terminal venster is waar je gemakkelijk de (fout)meldingen van de API kan bekijken.
Proof-of-Concept
Als eerste stap is het altijd nuttig om een zo eenvoudig mogelijk proof-of-concept te bouwen. We bekommeren ons hier nog niet om structuur, design of ‘propere’ code. We willen iets dat werkt en ons een idee geeft van wat we verder kunnen verwachten.
API Requests
Als eerste stap moeten we natuurlijk HTTP requests naar de API kunnen sturen, en de respons interpreteren.
Uit de Python standard library zouden we urllib.request kunnen gebruiken. Dit is echter erg “low level” - we zouden heel wat code moeten schrijven, o.a. rond JSON parsen, HTTP headers, timeouts, fouten, …
Er bestaan verschillende extra Python packages die de interactie met HTTP endpoints sterk vereenvoudigen. De populairste op dit moment zijn:
urllib3is iets gebruiksvriendelijker dan de basisurllibmaar nog steeds redelijk low-level.requestsis al jaren het meest gebruikte package en is gebouwd opurllib3.aiohttpis specifiek ontwikkeld voor async programma’s.httpxis recenter, ondersteunt HTTP/2 en zowel sync als async.
We kiezen hier voor de populaire optie: requests.
Het ophalen van alle blogposts via de /posts API is erg eenvoudig:
Blogposts als Columns
Bovenstaande json() method geeft ons een lijst met dictionaries die elke een blogpost voorstellen.
Werk als oefening volgende code af zodat we een Column krijgen met alle blogposts. Elke blogpost kan opnieuw voorgesteld worden als een Column met verschillende Text controls voor de verschillende onderdelen van een post.
src/main.py
Oplossing
src/main.py
import flet as ft
import requests
def main(page: ft.Page):
api_response = requests.get("http://127.0.0.1:5000/posts")
posts = api_response.json()
posts_list_view = ft.Column()
for post in posts:
post_view = ft.Column(
controls=[
ft.Text(f"Title: {post['title']}"),
ft.Text(f"by {post['author']} on {post['created']}"),
ft.Text(post["body"]),
]
)
posts_list_view.controls.append(post_view)
page.add(posts_list_view)
ft.app(target=main)Mooi is het niet, maar hiermee kunnen we wel besluiten dat het zinvol is om met deze aanpak verder te werken.
Design
Na zo een proof-of-concept is interessant om eerst eens na te denken hoe de applicatie er uit zou kunnen zien en welke componenten (Flet Controls) we daar voor willen gebruiken.
Een eenvoudige tekening, of wireframe, zal ons helpen tijdens het schrijven van de code.
Net zoals bij de Todo-App kunnen we gebruik maken van geneste Columns (en Rows) om de layout samen te stellen.
Bij het opstarten zullen we onmiddellijk de posts van de API inlezen en weergeven. Met een refresh knop (Icon) worden de posts opnieuw opgehaald.
Met een settings knop kan je een username en password ingeven via een dialoogvenster. Hierdoor worden de edit en delete knoppen op posts zichtbaar. Maar enkel voor de posts van de overeenkomstige auteur.
De new post en edit knoppen openen een gelijkaardig dialoogvenster maar bij edit moet de huidige title en body van de post al ingevuld zijn.
Data model en Client
Client
We maken een eenvoudige client class waar we de API interacties en complexiteit mee kunnen “encapsuleren”. Op die manier moet de rest van de code zich niet bekommeren hoe die interactie precies verloopt.
Om te starten implementeren we enkel het GET /posts endpoint.
src/flaskrclient.py
import requests
class FlaskrClient:
url = "http://127.0.0.1:5000"
def __init__(self):
self.session = requests.Session()
def posts(self) -> list[dict]:
print("Gettings posts")
response = self.session.get(f"{self.url}/posts")
response.raise_for_status()
data = response.json()
print(f"Got {len(data)} posts")
return dataData model
FlaskrClient.post() geeft een lijst met “blogpost-dictionaries” terug. Maar met dictionaries werken is erg onhandig. Je moet steeds de juiste keys onthouden en fouten merk je pas tijdens het uitvoeren van het programma.
Met Python dataclasses kan je gemakkelijke een eenvoudig model maken van de data waarmee gewerkt wordt.
src/flaskrclient.py
In een blogpost dictionary is created een gewone string. Omdat het handiger is om met echte datetime objecten te werken kunnen we de dataclass __post_init__ method gebruiken:
src/flaskrclient.py
Om een BlogPost object te maken kunnen we gewoon een dictionary unpacken naar keyword-arguments.
De FlaskrClient.posts method zal nu een lijst van BlogPost teruggeven. De volledige code wordt dan:
src/flaskrclient.py
from dataclasses import dataclass
from datetime import datetime
import requests
@dataclass
class BlogPost:
id: int
author: str
created: datetime
title: str
body: str
def __post_init__(self):
if isinstance(self.created, str):
self.created = datetime.fromisoformat(self.created)
class FlaskrClient:
url = "http://127.0.0.1:5000"
def __init__(self):
self.session = requests.Session()
def posts(self) -> list[BlogPost]:
print("Gettings posts")
response = self.session.get(f"{self.url}/posts")
response.raise_for_status()
data = response.json()
print(f"Got {len(data)} posts")
return [BlogPost(**d) for d in data]Model en Client gebruiken
Pas nu als oefening de bestaande code in src/main.py aan om gebruik te maken van de nieuwe Client en Model.
Oplossing
src/main.py
import flet as ft
from flaskrclient import FlaskrClient
def main(page: ft.Page):
client = FlaskrClient()
posts = client.posts()
posts_list_view = ft.Column()
for post in posts:
post_view = ft.Column(
controls=[
ft.Text(f"Title: {post.title}"),
ft.Text(f"by {post.author} on {post.created}"),
ft.Text(post.body),
]
)
posts_list_view.controls.append(post_view)
page.add(posts_list_view)
ft.app(target=main)PostView
Net zoals een Task bij de Todo app kunnen we Column subclassen in een eigen class die één enkele blogpost voorstelt, op basis van een gegeven BlogPost data-object.
We maken van de gelegenheid gebruik om de weergave van een blogpost al iets meer layout en stijl te geven.
src/main.py
import flet as ft
from flaskrclient import BlogPost, FlaskrClient
class PostView(ft.Column):
def __init__(self, post: BlogPost):
super().__init__()
self.post = post
self.controls = [
ft.Row(
controls=[
ft.Text(
self.post.title, theme_style=ft.TextThemeStyle.HEADLINE_MEDIUM
),
],
),
ft.Text(
f"by {self.post.author} on {self.post.created}",
theme_style=ft.TextThemeStyle.LABEL_SMALL,
),
ft.Text(self.post.body),
ft.Divider(),
]
def main(page: ft.Page):
client = FlaskrClient()
posts = client.posts()
posts_list_view = ft.Column()
for post in posts:
posts_list_view.controls.append(PostView(post))
page.add(posts_list_view)
ft.app(target=main)Authenticatie
Voordat we andere functionaliteit kunnen toevoegen moeten we voor authenticatie zorgen.
FlaskrClient auth method
Eerst breiden we de FlaskrClient uit met een optie om (achteraf, aan een bestaand object) authenticatiegegevens toe te voegen.
In een requests.Session object kan HTTP basic auth worden ingesteld als een (username, password) tuple in de auth property.
Settings knop
Vervolgens moeten we een “settings” knop (IconButton) toevoegen. Een goed moment om ook de titel van de applicatie toe te voegen. Als we naar het design kijken willen we een Row met daarin o.a. de titel en de settings knop. Die Row komt dan samen met de bestaande posts_list_view Column in een andere “main” Column
src/main.py
def main(page: ft.Page):
client = FlaskrClient()
posts = client.posts()
def enter_creds(e):
# TODO
...
posts_list_view = ft.Column()
for post in posts:
posts_list_view.controls.append(PostView(post))
app_view = ft.Column(
controls=[
ft.Row(
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
controls=[
ft.Text(
value="FlaskR Blog",
theme_style=ft.TextThemeStyle.HEADLINE_LARGE,
),
ft.IconButton(icon=ft.Icons.SETTINGS, on_click=enter_creds),
],
),
posts_list_view,
],
)
page.add(app_view)Wat moet er gebeuren wanneer deze nieuwe settings IconButton wordt aangeklikt?
- Een nieuw dialoogvenster moet geopend worden.
- In het dialoogvenster kunnen username en password worden ingegeven.
- Via een Save knop worden de ingegeven waarden opgeslagen d.m.v. de nieuwe
FlaskrClient.set_authmethod. Daarna wordt het dialoogvenster terug gesloten. - Via een Cancel knop kan het dialoogvenster zonder verdere acties gesloten worden.
Probeer als oefening de enter_creds functies te implementeren.
Voor het dialoogvenster gebruik je best een AlertDialog.
Zo een dialoogvenster open (en sluit) je met de page.open(dialog) (en page.close(dialog)) functie, waarbij dialog het AlertDialog object is.
In het venster zul je (via de AlertDialog.content property) twee TextField velden nodig hebben, een voor de username en een voor het password.
Voor de actions van het AlertDialog kan je twee ElevatedButton’s gebruiken, een voor Save en een voor Cancel.
De Save knop zal een aparte, geneste functie moeten aanroepen (bvb. set_creds) die enerzijds client.set_auth gebruikt om de ingegeven username/password in te stellen, en vervolgens het AlertDialog sluit.
Oplossing
src/main.py
def enter_creds(e):
def set_creds(e):
client.set_auth(str(username_field.value), str(password_field.value))
page.close(dialog)
username_field = ft.TextField(label="Username", autofocus=True)
password_field = ft.TextField(label="Password", password=True)
dialog = ft.AlertDialog(
title=ft.Text("API Credentials"),
content=ft.Column([username_field, password_field], tight=True),
actions=[
ft.ElevatedButton("Cancel", on_click=lambda _: page.close(dialog)),
ft.ElevatedButton("Save", on_click=set_creds),
],
modal=True,
)
page.open(dialog)New Post
Met de authenticatie opgelost moet het nu mogelijk zijn nieuwe blogposts aan te maken.
FlaskrClient new_post method
Opnieuw moeten we eerst de FlaskrClient uitbreiden met een method om een nieuwe blogpost naar de API te sturen
De requests.post functie zorgt automatisch voor de juiste headers (bvb. Content-Type) en JSON encoding als we aan de json parameter een gewone dictionary meegeven.
Door de API response om te zetten in een BlogPost en dit te returnen zullen we deze onmiddellijk kunnen toevoegen aan de posts_list_view Column.
src/flaskrclient.py
class FlaskrClient:
def new_post(self, title: str, body: str) -> BlogPost:
print("Creating new post")
response = self.session.post(
f"{self.url}/posts", json={"title": title, "body": body}
)
response.raise_for_status()
post = BlogPost(**response.json())
print(f"New post created with ID {post.id}")
return postNew Post knop
Voor de New Post knop kunnen we een ElevatedButton gebruiken. Deze knop moet disabled zijn tot de API credentials zijn opgegeven.
Als dialoogvenster gebruiken we opnieuw AlertDialog. De implementatie is erg vergelijkbaar met deze om de API credentials in te geven.
src/main.py
def main(page: ft.Page):
client = FlaskrClient()
posts = client.posts()
def enter_creds(e):
def set_creds(e):
client.set_auth(str(username_field.value), str(password_field.value))
2 new_post_button.disabled = False
new_post_button.update()
page.close(dialog)
...
def new_post(e):
def submit_post(e):
post = client.new_post(
title=str(title_field.value), body=str(body_field.value)
)
3 posts_list_view.controls.insert(0, PostView(post))
posts_list_view.update()
page.close(dialog)
title_field = ft.TextField(label="Title", autofocus=True, max_length=100)
body_field = ft.TextField(
label="Body", multiline=True, min_lines=5, max_lines=10, max_length=5000
)
dialog = ft.AlertDialog(
title=ft.Text("New Post"),
content=ft.Column([title_field, body_field], tight=True, width=500),
actions=[
ft.ElevatedButton("Cancel", on_click=lambda _: page.close(dialog)),
ft.ElevatedButton("Submit", on_click=submit_post),
],
)
page.open(dialog)
1 new_post_button = ft.ElevatedButton( text="New Post", on_click=new_post, disabled=True )
app_view = ft.Column(
controls=[
ft.Row(...),
new_post_button,
posts_list_view,
],
)
page.add(app_view)- 1
-
De
new_post_buttonElevatedButtonis initieeldisabled. - 2
-
Nadat de API creds zijn ingevoerd, wordt de
new_post_buttongeactiveerd. DeElevatedButtonmoet ook worden geupdate zodat het resultaat zichtbaar wordt. - 3
-
Het resultaat van
client.new_postmoet vooraan worden toegevoegd aan de lijst van posts. Ook hier moeten weupdate()gebruiken.
Refactor
De code in main begint erg lang en onoverzichtelijk te worden, met geneste functies en variabelen zoals new_post_button die in verschillende functies gebruikt worden.
We volgen dezelfde aanpak als bij de Todo app en herschrijven de code in een nieuwe FlaskrApp class.
FlaskrApp subclassed Column en stelt de huidige app_view voor. We willen de main functie reduceren tot het volgende:
src/main.py
De FlaskrApp class ziet er als volgt uit:
src/main.py
class FlaskrApp(ft.Column):
def __init__(self, client: FlaskrClient):
super().__init__()
self.client = client
self.new_post_button = ft.ElevatedButton(
text="New Post", on_click=self.new_post, disabled=True
)
self.posts_list_view = ft.Column()
self.controls = [
ft.Row(
controls=[
ft.Text(value="FlaskR Blog", theme_style=ft.TextThemeStyle.HEADLINE_LARGE),
ft.IconButton(icon=ft.Icons.SETTINGS, on_click=self.enter_creds),
],
),
self.new_post_button,
self.posts_list_view,
]
1 def refresh(self):
self.posts_list_view.controls = [PostView(post=post) for post in self.client.posts()]
self.update()
def enter_creds(self, e):
def set_creds(e):
self.client.set_auth(str(username_field.value), str(password_field.value))
self.new_post_button.disabled = False
2 self.update()
3 self.page.close(dialog)
username_field = ft.TextField(label="Username", autofocus=True)
password_field = ft.TextField(label="Password", password=True)
dialog = ft.AlertDialog(
title=ft.Text("API Credentials"),
content=ft.Column([username_field, password_field], tight=True),
actions=[
ft.ElevatedButton("Cancel", on_click=lambda _: self.page.close(dialog)),
ft.ElevatedButton("Save", on_click=set_creds),
],
modal=True,
)
self.page.open(dialog)
def new_post(self, e):
...- 1
-
Het houden interactieve code liefst uit de
__init__method dus voor het ophalen van de blogposts en opstellen van deself.posts_list_viewColumnmaken we een extra method aan. Deze kunnen we bovendien later opnieuw gebruiken om de blogposts te refreshen. - 2
-
Omdat we
Columnsubclassen kunnen we om te updaten gewoonself.update()aanroepen. - 3
-
De
pageis in elk control object beschikbaar viaself.page.
Implementeer als oefening de new_post method, op basis van de bestaande functie.
Oplossing
src/main.py
def new_post(self, e):
def submit_post(e):
post = self.client.new_post(
title=str(title_field.value), body=str(body_field.value)
)
self.posts_list_view.controls.insert(0, PostView(post))
self.update()
self.page.close(dialog)
title_field = ft.TextField(label="Title", autofocus=True, max_length=100)
body_field = ft.TextField(
label="Body", multiline=True, min_lines=5, max_lines=10, max_length=5000
)
dialog = ft.AlertDialog(
title=ft.Text("New Post"),
content=ft.Column([title_field, body_field], tight=True, width=500),
actions=[
ft.ElevatedButton("Cancel", on_click=lambda _: self.page.close(dialog)),
ft.ElevatedButton("Submit", on_click=submit_post),
],
)
self.page.open(dialog)Posts Editen
FlaskrClient edit_post method
We breiden nogmaals de FlaskrClient class uit met een nieuwe method. We voegen ook direct een extra boolean property toe om aan te geven of de client al dan niet authenticatie credentials bevat.
src/flaskrclient.py
def edit_post(self, post_id: int, title: str, body: str) -> BlogPost:
print(f"Updating post with ID {post_id}")
response = self.session.put(
f"{self.url}/posts/{post_id}", json={"title": title, "body": body}
)
response.raise_for_status()
post = BlogPost(**response.json())
print(f"Post with ID {post.id} updated")
return postOns design bepaalt dat een post kan geëdit worden met een IconButton naast de titel van de post. Maar enkel wanneer die post is aangemaakt door de huidige user.
Die IconButton zal dus deel uitmaken van een PostView. Maar de uit te voeren functie, wanneer de knop wordt aangeklikt, zal buiten PostView liggen. PostView moet zich niet bezighouden met API interacties. Elke PostView zal dus een extra on_edit functie meekrijgen. Door deze optioneel te maken (enkel wanneer de post van de huidige user is) kan PostView de edit knop al dan niet zichtbaar maken.
PostView
We bekijken eerst de aanpassingen aan de PostView class:
src/main.py
class PostView(ft.Column):
1 def __init__(self, post: BlogPost, on_edit: Optional[Callable] = None):
super().__init__()
self.post = post
self.on_edit = on_edit
2 self.title = ft.Text(self.post.title, theme_style=ft.TextThemeStyle.HEADLINE_MEDIUM)
self.subtitle = ft.Text(f"by {self.post.author} on {self.post.created}", theme_style=ft.TextThemeStyle.LABEL_SMALL)
self.body = ft.Text(self.post.body)
3 interaction_buttons = ft.Row()
if self.on_edit:
interaction_buttons.controls.append(
ft.IconButton(icon=ft.Icons.EDIT, on_click=lambda e: self.on_edit(self))
)
self.controls = [
ft.Row(controls=[self.title, interaction_buttons]),
self.subtitle,
self.body,
ft.Divider(),
]
4 def update(self):
self.title.value = self.post.title
self.body.value = self.post.body
super().update()- 1
-
Een
PostViewkan nu optioneel een functie meekrijgen, uit te voeren via de edit knop. - 2
-
De verschillende controls in een
PostViewworden apart bijgehouden zodat ze later geupdate kunnen worden. - 3
-
De (optionele) edit knop zetten we in een
Rowwant later zal nog een delete knop worden toegevoegd. Bijon_clickwordt opgegeven functie uitgevoerd met dePostViewzelf als argument. - 4
-
De standard
updatemethod (vanColumn) wordt aangepast om eerst de titel en bodyTextcontrols te updaten.
FlaskrApp
We bekijken eerst de aanpassingen bij het maken van de PostView controls:
src/main.py
class FlaskrApp(ft.Column):
def __init__(self, client: FlaskrClient):
self.username = ""
1 def refresh(self):
postview_list = []
for post in self.client.posts():
if self.client.authenticated and self.username == post.author:
postview_list.append(PostView(post=post, on_edit=self.edit_post))
else:
postview_list.append(PostView(post=post))
self.posts_list_view.controls = postview_list
self.update()
def enter_creds(self, e):
def set_creds(e):
2 self.username = str(username_field.value)
...
...- 1
-
Bij het aanmaken van de
PostViewcontrols wordton_edit=self.edit_postmeegegeven enkel als er authenticatie beschikbaar is en de username overeen komt met de auteur van de post. - 2
-
Hiervoor moeten we dus de ingegeven username bijhouden als extra property in de
FlaskrApp.
En dan de nieuwe edit_post method:
src/main.py
class FlaskrApp(ft.Column):
...
def edit_post(self, postview: PostView):
def submit_post(e):
title = str(title_field.value)
body = str(body_field.value)
post = self.client.edit_post(post_id=postview.post.id, title=title, body=body)
self.page.close(dialog)
1 postview.post = post
postview.update()
2 title_field = ft.TextField(
value=postview.post.title, label="Title", autofocus=True, max_length=100
)
body_field = ft.TextField(
value=postview.post.body,
label="Body",
multiline=True,
min_lines=5,
max_lines=10,
max_length=5000,
)
dialog = ft.AlertDialog(
title=ft.Text("Edit Post"),
content=ft.Column([title_field, body_field], tight=True, width=500),
actions=[
ft.ElevatedButton("Cancel", on_click=lambda _: self.page.close(dialog)),
ft.ElevatedButton("Submit", on_click=submit_post),
],
)
self.page.open(dialog)- 1
-
Na het updaten van de blogpost via de API moet de
PostViewcontrol worden geupdate. - 2
-
Het dialoogvenster gebruikt een
AlertDialogcontrol die bijna identiek is als bij een New Post.
Posts Deleten
Implementeer als oefening alle nodige aanpassingen om de delete knop toe te voegen naast de edit knop. Gebruik dezelfde aanpak als voor de edit knop.
Oplossing
src/flaskrclient.py
src/main.py
class PostView(ft.Column):
def __init__(
self,
post: BlogPost,
on_edit: Optional[Callable] = None,
on_delete: Optional[Callable] = None,
):
...
self.on_delete = on_delete
...
if self.on_delete:
interaction_buttons.controls.append(
ft.IconButton(
icon=ft.Icons.DELETE,
icon_color=ft.Colors.RED_300,
on_click=lambda e: self.on_delete(self),
)
)src/main.py
class PostView(ft.Column):
...
def refresh(self):
postview_list = []
for post in self.client.posts():
if self.client.authenticated and self.username == post.author:
postview_list.append(
PostView(
post=post, on_edit=self.edit_post, on_delete=self.delete_post
)
)
else:
postview_list.append(PostView(post=post))
self.posts_list_view.controls = postview_list
self.update()
def delete_post(self, postview: PostView):
def delete_post(e):
self.client.delete_post(post_id=postview.post.id)
self.page.close(dialog)
self.posts_list_view.controls.remove(postview)
self.posts_list_view.update()
dialog = ft.AlertDialog(
title=ft.Text(f"Delete '{postview.post.title}'?"),
actions=[
ft.ElevatedButton("Cancel", on_click=lambda _: self.page.close(dialog)),
ft.ElevatedButton("Delete", on_click=delete_post),
],
)
self.page.open(dialog)Final touches
Posts lijst scrollen
Als er meer blogposts zijn dan er verticale ruimte is wordt momenteel de laatste zichtbare post gewoon afgesneden en kan er niet gescrolled worden.
Om dit op te lossen zijn twee aanpassingen nodig:
- De
post_list_viewColumnmoet descrollparameter hebben. - Zowel diezelfde
Columnals die er buiten (deFlaskrAppzelf) moetenexpand=Truekrijgen zodat ze de volledig beschikbare (hier vooral de verticale) ruimte innemen.
Ingelogde user weergeven
Het design toont linksboven de naam van de ingelogde user.
Dit kan opgelost worden met een extra Text control in the bovenste Row. Initieel is die Text leeg.
src/main.py
Na het ingeven van de API credentials wordt diezelfde Text dan geupdate:
src/main.py
class FlaskrApp(ft.Column):
...
def enter_creds(self, e):
def set_creds(e):
self.username = str(username_field.value)
self.client.set_auth(self.username, str(password_field.value))
self.new_post_button.disabled = False
self.user_info.value = f"Logged in as: {self.username}"
self.refresh()
self.page.close(dialog)Refresh knop
Naast de “settings” knop willen we ook een knop om manueel de lijst met blogposts te refreshen.
FlaskrApp heeft al een refresh method. We hoeven dus enkel een extra IconButton te voorzien die deze method aanroept.
src/main.py
class FlaskrApp(ft.Column):
def __init__(self, client: FlaskrClient):
...
self.controls = [
ft.Row(
controls=[
self.user_info,
ft.Text( value="FlaskR Blog", theme_style=ft.TextThemeStyle.HEADLINE_LARGE, ),
ft.IconButton(icon=ft.Icons.REFRESH, on_click=lambda e: self.refresh()),
ft.IconButton(icon=ft.Icons.SETTINGS, on_click=self.enter_creds),
],
),Bovenste row uitlijnen
Om de Row beter uit te lijnen kan de parameter alignment=ft.MainAxisAlignment.SPACE_BETWEEN gebruikt worden. Om de twee IconButtons volledig rechts samen te houden kan je ze groeperen in een extra Row - met spacing=0 om de iconen zo dicht mogelijk bij elkaar te houden.
src/main.py
class FlaskrApp(ft.Column):
def __init__(self, client: FlaskrClient):
...
self.controls = [
ft.Row(
controls=[
self.user_info,
ft.Text( value="FlaskR Blog", theme_style=ft.TextThemeStyle.HEADLINE_LARGE, ),
ft.Row(
[
ft.IconButton( icon=ft.Icons.REFRESH, on_click=lambda e: self.refresh() ),
ft.IconButton( icon=ft.Icons.SETTINGS, on_click=self.enter_creds ),
],
spacing=0,
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
),Extra
Referentie Implementatie
Een volledig werkende implementatie gebaseerd op deze les vind je op https://github.com/dvx76/flet-tutorial/tree/flaskr-app.
Flet Apps builden
Een afgewerkte Flet applicatie wil je natuurlijk kunnen verdelen zonder dat gebruikers daarvoor Python en Flet zelf moeten installeren.
De manier en complexiteit om een Flet applicatie de builden hangt sterkt af van het gewenste platform (MacOS, Windows, Android, …).
Details per platform vind je op https://flet.dev/docs/publish.
Pagination en Infinite Scroll
Zowel de Flet applicatie als de onderliggende API bevatten een typische beginnersfout: ze ondersteunen geen paginering.
Er worden telkens alle posts teruggestuurd en weergegeven. Dit is geen probleem tijdens onze eenvoudige testen waarbij maximum een paar tiental posts aanwezig zijn. Maar stel je voor dat we duizenden posts in de database hebben.
Een API zal in principe resultaten altijd pagineren.
Deze commit toont hoe we eenvoudige paginering kunnen toevoegen aan de API.
In de Flet applicatie willen we dan graag pas de volgende ‘pagina’(s) met posts ophalen wanneer het nodig is. Ideaal is dat dit automatisch gebeurd wanneer de gebruiker dicht bij het einde komt van de huidige lijst posts. We spreken over een zogenaamde infinite scroll.
In de Flet documentatie vinden we voor Column het volgende voorbeeld: https://flet.dev/docs/controls/column/#infinite-scroll-list
Om snel een grote hoeveelheid posts toe te voegen aan de database kunnen we volgend commando gebruiken (voor SQLite):

