Skip to content

Lezione 10: API HTTP — Dati dal web

  • Capire cos’è un’API e come funziona
  • Fare richieste HTTP a servizi web
  • Parsare risposte JSON
  • Gestire loading e errori nella UI

API (Application Programming Interface) è un modo per due programmi di comunicare tra loro. Nel nostro caso, l’app Flet parla con un server web per ottenere dati.

App Flet ──── richiesta HTTP (GET) ────→ Server API
│ │
←──── risposta JSON (dati) ────────────┘

API pubbliche gratuite (non serve registrazione)

Section titled “API pubbliche gratuite (non serve registrazione)”
APIURLCosa restituisce
JSONPlaceholderhttps://jsonplaceholder.typicode.com/postsPost finti
JSONPlaceholderhttps://jsonplaceholder.typicode.com/usersUtenti finti
Dog APIhttps://dog.ceo/api/breeds/image/randomImmagine cane casuale
Pokemonhttps://pokeapi.co/api/v2/pokemon/pikachuDati Pokemon
Open Trivia DBhttps://opentdb.com/api.php?amount=5Domande trivia

import urllib.request
import json
def fetch_data(url):
"""Scarica dati JSON da un URL."""
with urllib.request.urlopen(url) as response:
data = json.loads(response.read().decode())
return data

Usando il pacchetto requests (più semplice)

Section titled “Usando il pacchetto requests (più semplice)”

Prima installa: pip install requests

import requests
def fetch_data(url):
"""Scarica dati JSON da un URL usando requests."""
response = requests.get(url)
response.raise_for_status() # Solleva errore se status non è 200
return response.json()

In questo corso useremo urllib.request (non serve installare nulla).


import flet as ft
import urllib.request
import json
def main(page: ft.Page):
page.title = "Fetch API"
page.padding = 30
risultato = ft.Column()
def fetch_posts(e):
risultato.controls.clear()
risultato.controls.append(ft.Text("Caricamento...", italic=True, color="grey"))
page.update()
try:
# Chiamata API
url = "https://jsonplaceholder.typicode.com/posts"
with urllib.request.urlopen(url) as response:
posts = json.loads(response.read().decode())
risultato.controls.clear()
for post in posts[:5]: # Primi 5 post
risultato.controls.append(
ft.Container(
content=ft.Column([
ft.Text(post["title"], weight="bold", size=16),
ft.Text(post["body"], color="grey"),
]),
padding=10,
bgcolor="white",
border_radius=8,
margin=5,
shadow=ft.BoxShadow(blur_radius=3, color=ft.colors.GREY_300),
)
)
except Exception as ex:
risultato.controls.clear()
risultato.controls.append(ft.Text(f"❌ Errore: {ex}", color="red"))
page.update()
page.add(
ft.Text("📡 API Demo", size=28, weight="bold"),
ft.ElevatedButton("Carica Post", on_click=fetch_posts, icon=ft.icons.DOWNLOAD),
ft.Divider(),
risultato,
)
ft.app(target=main)

import flet as ft
import urllib.request
import json
def main(page: ft.Page):
page.title = "Pokédex"
page.padding = 30
nome_pokemon = ft.TextField(
label="Nome Pokemon",
hint_text="es. pikachu, charizard, mewtwo",
prefix_icon=ft.icons.SEARCH,
)
card_pokemon = ft.Container(visible=False) # Nascosta inizialmente
errore = ft.Text("", color="red")
caricamento = ft.ProgressBar(visible=False)
def cerca_pokemon(e):
if not nome_pokemon.value.strip():
return
# Mostra caricamento
caricamento.visible = True
card_pokemon.visible = False
errore.value = ""
page.update()
try:
url = f"https://pokeapi.co/api/v2/pokemon/{nome_pokemon.value.strip().lower()}"
with urllib.request.urlopen(url) as response:
dati = json.loads(response.read().decode())
# Costruisce card
card_pokemon.content = ft.Container(
content=ft.Column([
ft.Row([
ft.Container(
content=ft.Text(dati["name"].upper(), size=24, weight="bold", color="white"),
bgcolor="red",
padding=20,
border_radius=10,
),
], alignment=ft.MainAxisAlignment.CENTER),
ft.Text(f"Altezza: {dati['height'] / 10} m", size=16),
ft.Text(f"Peso: {dati['weight'] / 10} kg", size=16),
ft.Text("Tipi:", weight="bold"),
ft.Row([
ft.Container(
content=ft.Text(tipo["type"]["name"]),
bgcolor="lightblue",
padding=5,
border_radius=5,
) for tipo in dati["types"]
]),
ft.Text("Abilità:", weight="bold"),
ft.Column([
ft.Text(f"• {abilita['ability']['name']}")
for abilita in dati["abilities"]
]),
]),
padding=20,
bgcolor="white",
border_radius=15,
shadow=ft.BoxShadow(blur_radius=15, color=ft.colors.GREY_400),
)
card_pokemon.visible = True
except urllib.error.HTTPError:
errore.value = f"❌ Pokemon '{nome_pokemon.value}' non trovato!"
except Exception as ex:
errore.value = f"❌ Errore: {ex}"
finally:
caricamento.visible = False
page.update()
page.add(
ft.Text("🔍 Pokédex", size=28, weight="bold"),
ft.Row([nome_pokemon, ft.ElevatedButton("Cerca", on_click=cerca_pokemon)]),
errore,
caricamento,
card_pokemon,
)
ft.app(target=main)

5. Loading indicator: ProgressBar e CircularProgress

Section titled “5. Loading indicator: ProgressBar e CircularProgress”
# Barra orizzontale (indeterminata)
ft.ProgressBar(
visible=True, # True quando carica
color="blue",
bgcolor="#e0e0e0",
)
# Cerchio di caricamento
ft.ProgressRing(
visible=True,
color="blue",
width=50,
height=50,
stroke_width=4,
)
caricamento = ft.ProgressBar(visible=False)
errore = ft.Text("", color="red")
contenuto = ft.Column()
def carica_dati(e):
# 1. Reset
contenuto.controls.clear()
errore.value = ""
caricamento.visible = True
page.update()
try:
# 2. Chiamata API
dati = fetch_api()
# 3. Mostra dati
for item in dati:
contenuto.controls.append(...)
except Exception as ex:
# 4. Errore
errore.value = f"❌ {ex}"
finally:
# 5. Fine caricamento
caricamento.visible = False
page.update()

Molte API accettano parametri per filtrare i dati:

# URL con parametri
base = "https://jsonplaceholder.typicode.com/posts"
params = "?userId=1" # Solo post dell'utente 1
url = base + params
# Oppure più parametri
url = "https://opentdb.com/api.php?amount=5&category=18&difficulty=easy"

ComandoCosa fa
urllib.request.urlopen(url)Apre connessione HTTP
response.read().decode()Legge risposta come stringa
json.loads(testo)Converte JSON → Python
requests.get(url) (con pacchetto)Richiesta HTTP GET
response.raise_for_status()Controlla errori HTTP
ft.ProgressBar(visible=True)Barra caricamento
ft.ProgressRing(...)Cerchio caricamento

Crea un file citazioni.py che usi l’API https://api.quotable.io/random per mostrare citazioni casuali.

  1. Un titolo ”💬 Generatore di Citazioni”
  2. Un bottone “Nuova citazione” che chiama l’API
  3. Una card che mostra:
    • Il testo della citazione (in corsivo, grande)
    • L’autore (sotto, allineato a destra, grassetto)
  4. Un tag che mostra la categoria (es. “wisdom”, “success” — guarda la risposta API)
  5. Loading indicator mentre si carica
  6. Gestione errore se la chiamata fallisce

Chiamando https://api.quotable.io/random ricevi:

{
"content": "La vita è bella",
"author": "Qualcuno",
"tags": ["filosofia"]
}
  • Gestisci il caricamento con ft.ProgressBar
  • Per il corsivo: ft.Text(italic=True)
  • Per il tag, usa ft.Container(bgcolor="lightblue", border_radius=5, padding=5)
  • Fai fetch ad ogni click del bottone