Skip to content

Lezione 11: Esercitazione guidata — App Meteo

  • Mettere insieme API, navigazione e UI
  • Gestire lo stato di caricamento
  • Visualizzare dati strutturati
  • Completare un’app funzionante dall’inizio alla fine

Costruiamo un’app che:

  1. Chiede il nome di una città
  2. Chiama un’API meteo per ottenere i dati
  3. Mostra temperatura, umidità, descrizione del tempo
  4. Permette di cercare un’altra città

Open-Meteo (gratuita, senza API key):

https://api.open-meteo.com/v1/forecast?latitude=LAT&longitude=LON&current_units=temperature_2m&current=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code&timezone=auto

Ma per semplificare, prima cerchiamo le coordinate della città con Geocoding API (gratuita, senza API key):

https://geocoding-api.open-meteo.com/v1/search?name=NOME_CITTÀ

import flet as ft
import urllib.request
import json
def main(page: ft.Page):
page.title = "Meteo App"
page.padding = 30
page.scroll = ft.ScrollMode.AUTO
# Widget UI
input_citta = ft.TextField(
label="Città",
hint_text="es. Roma, Milano, Parigi",
prefix_icon=ft.icons.SEARCH,
on_submit=lambda e: cerca_meteo(e),
)
bottone_cerca = ft.ElevatedButton("Cerca", on_click=cerca_meteo)
caricamento = ft.ProgressBar(visible=False)
errore = ft.Text("", color="red")
risultato = ft.Column()
page.add(
ft.Text("🌤️ Meteo App", size=28, weight="bold"),
ft.Row([input_citta, bottone_cerca]),
errore,
caricamento,
risultato,
)
# Funzioni (da implementare)
def cerca_meteo(e):
pass
ft.app(target=main)

def cerca_coordinate(citta):
"""Cerca le coordinate di una città usando Geocoding API."""
url = f"https://geocoding-api.open-meteo.com/v1/search?name={citta}&count=1"
with urllib.request.urlopen(url) as response:
dati = json.loads(response.read().decode())
if dati.get("results"):
risultato = dati["results"][0]
return {
"nome": risultato["name"],
"lat": risultato["latitude"],
"lon": risultato["longitude"],
"paese": risultato.get("country", ""),
}
return None

def cerca_meteo_dati(lat, lon):
"""Recupera i dati meteo per coordinate."""
url = (
f"https://api.open-meteo.com/v1/forecast?"
f"latitude={lat}&longitude={lon}"
f"&current=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code"
f"&timezone=auto"
)
with urllib.request.urlopen(url) as response:
return json.loads(response.read().decode())

Open-Meteo usa codici numerici per il tempo. Creiamo una funzione per convertirli:

def decodifica_meteo(codice):
"""Converte il codice WMO in descrizione testuale."""
meteo = {
0: "☀️ Sereno",
1: "🌤️ Prevalentemente sereno",
2: "⛅ Parzialmente nuvoloso",
3: "☁️ Coperto",
45: "🌫️ Nebbia",
48: "🌫️ Nebbia con ghiaccio",
51: "🌧️ Pioggerella leggera",
53: "🌧️ Pioggerella moderata",
55: "🌧️ Pioggerella intensa",
61: "🌦️ Pioggia leggera",
63: "🌧️ Pioggia moderata",
65: "🌧️ Pioggia intensa",
71: "🌨️ Neve leggera",
73: "🌨️ Neve moderata",
75: "🌨️ Neve intensa",
80: "🌦️ Rovesci leggeri",
81: "🌧️ Rovesci moderati",
82: "🌧️ Rovesci intensi",
95: "⛈️ Temporale",
96: "⛈️ Grandine leggera",
99: "⛈️ Grandine forte",
}
return meteo.get(codice, f"❓ Codice {codice}")

6. Step 5: Funzione principale — cerca meteo

Section titled “6. Step 5: Funzione principale — cerca meteo”
def cerca_meteo(e):
if not input_citta.value.strip():
return
# Reset UI
errore.value = ""
risultato.controls.clear()
caricamento.visible = True
page.update()
try:
# Cerca coordinate città
luogo = cerca_coordinate(input_citta.value.strip())
if not luogo:
errore.value = f"❌ Città '{input_citta.value}' non trovata!"
caricamento.visible = False
page.update()
return
# Recupera meteo
meteo = cerca_meteo_dati(luogo["lat"], luogo["lon"])
corrente = meteo["current"]
# Estrai dati
temperatura = corrente["temperature_2m"]
percepita = corrente["apparent_temperature"]
umidita = corrente["relative_humidity_2m"]
codice_meteo = corrente["weather_code"]
descrizione = decodifica_meteo(codice_meteo)
# Costruisci UI risultato
risultato.controls.append(
ft.Container(
content=ft.Column([
# Città e paese
ft.Text(f"{luogo['nome']}, {luogo['paese']}", size=24, weight="bold"),
ft.Divider(),
# Icona meteo e temperatura grande
ft.Text(descrizione, size=22),
ft.Text(f"{temperatura}°C", size=60, weight="bold", color="blue"),
ft.Divider(),
# Dettagli
ft.Row([
ft.Column([
ft.Text("🌡️ Percepita", color="grey"),
ft.Text(f"{percepita}°C", size=20, weight="bold"),
], horizontal_alignment=ft.CrossAxisAlignment.CENTER),
ft.Column([
ft.Text("💧 Umidità", color="grey"),
ft.Text(f"{umidita}%", size=20, weight="bold"),
], horizontal_alignment=ft.CrossAxisAlignment.CENTER),
], alignment=ft.MainAxisAlignment.SPACE_AROUND),
]),
padding=25,
bgcolor="white",
border_radius=20,
shadow=ft.BoxShadow(blur_radius=15, color=ft.colors.GREY_300),
width=350,
)
)
except Exception as ex:
errore.value = f"❌ Errore: {ex}"
finally:
caricamento.visible = False
page.update()

import flet as ft
import urllib.request
import json
WEATHER_CODES = {
0: "☀️ Sereno", 1: "🌤️ Prevalentemente sereno", 2: "⛅ Parzialmente nuvoloso",
3: "☁️ Coperto", 45: "🌫️ Nebbia", 48: "🌫️ Nebbia con ghiaccio",
51: "🌧️ Pioggerella leggera", 53: "🌧️ Pioggerella moderata",
55: "🌧️ Pioggerella intensa", 61: "🌦️ Pioggia leggera",
63: "🌧️ Pioggia moderata", 65: "🌧️ Pioggia intensa",
71: "🌨️ Neve leggera", 73: "🌨️ Neve moderata", 75: "🌨️ Neve intensa",
80: "🌦️ Rovesci leggeri", 81: "🌧️ Rovesci moderati",
82: "🌧️ Rovesci intensi", 95: "⛈️ Temporale",
96: "⛈️ Grandine leggera", 99: "⛈️ Grandine forte",
}
def decodifica_meteo(codice):
return WEATHER_CODES.get(codice, f"❓ Codice {codice}")
def cerca_coordinate(citta):
url = f"https://geocoding-api.open-meteo.com/v1/search?name={citta}&count=1"
with urllib.request.urlopen(url) as response:
dati = json.loads(response.read().decode())
if dati.get("results"):
r = dati["results"][0]
return {"nome": r["name"], "lat": r["latitude"], "lon": r["longitude"],
"paese": r.get("country", "")}
return None
def cerca_meteo_dati(lat, lon):
url = (f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}"
f"&current=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code"
f"&timezone=auto")
with urllib.request.urlopen(url) as response:
return json.loads(response.read().decode())
def main(page: ft.Page):
page.title = "🌤️ Meteo App"
page.padding = 30
page.scroll = ft.ScrollMode.AUTO
input_citta = ft.TextField(label="Città", hint_text="es. Roma, Milano, Parigi",
prefix_icon=ft.icons.SEARCH, on_submit=lambda e: cerca_meteo(e))
bottone_cerca = ft.ElevatedButton("Cerca", on_click=cerca_meteo)
caricamento = ft.ProgressBar(visible=False)
errore = ft.Text("", color="red")
risultato = ft.Column()
page.add(
ft.Text("🌤️ Meteo App", size=28, weight="bold"),
ft.Text("Controlla il meteo in qualsiasi città", color="grey"),
ft.Divider(),
ft.Row([input_citta, bottone_cerca]),
errore, caricamento, risultato,
)
def cerca_meteo(e):
if not input_citta.value.strip():
return
errore.value = ""
risultato.controls.clear()
caricamento.visible = True
page.update()
try:
luogo = cerca_coordinate(input_citta.value.strip())
if not luogo:
errore.value = f"❌ Città '{input_citta.value}' non trovata!"
return
meteo = cerca_meteo_dati(luogo["lat"], luogo["lon"])
c = meteo["current"]
risultato.controls.append(
ft.Container(
content=ft.Column([
ft.Text(f"{luogo['nome']}, {luogo['paese']}", size=24, weight="bold"),
ft.Divider(),
ft.Text(decodifica_meteo(c["weather_code"]), size=22),
ft.Text(f"{c['temperature_2m']}°C", size=60, weight="bold", color="blue"),
ft.Divider(),
ft.Row([
ft.Column([ft.Text("🌡️ Percepita", color="grey"),
ft.Text(f"{c['apparent_temperature']}°C", size=20, weight="bold")],
horizontal_alignment=ft.CrossAxisAlignment.CENTER),
ft.Column([ft.Text("💧 Umidità", color="grey"),
ft.Text(f"{c['relative_humidity_2m']}%", size=20, weight="bold")],
horizontal_alignment=ft.CrossAxisAlignment.CENTER),
], alignment=ft.MainAxisAlignment.SPACE_AROUND),
]),
padding=25, bgcolor="white", border_radius=20,
shadow=ft.BoxShadow(blur_radius=15, color=ft.colors.GREY_300), width=350,
)
)
except Exception as ex:
errore.value = f"❌ Errore: {ex}"
finally:
caricamento.visible = False
page.update()
ft.app(target=main)

Dopo aver completato l’app base, prova ad aggiungere:

  1. Cronologia ricerche: salva le ultime città cercate (JSON)
  2. Preferiti: stella per salvare città preferite
  3. Icona meteo animata: usa emoji diverse in base al codice
  4. Previsioni 7 giorni: mostra anche i giorni successivi (usa daily invece di current nell’API)
  5. Tema scuro per l’app
url = (
f"https://api.open-meteo.com/v1/forecast?"
f"latitude={lat}&longitude={lon}"
f"&daily=temperature_2m_max,temperature_2m_min,weather_code"
f"&timezone=auto"
)

🎯 Esercizio: “Personalizza la Meteo App”

Section titled “🎯 Esercizio: “Personalizza la Meteo App””

Modifica l’app meteo per aggiungere:

  1. Colore di sfondo che cambia in base al tempo:
    • Sereno → azzurro
    • Nuvoloso → grigio
    • Pioggia → blu scuro
  2. Salvataggio ultima città in un file JSON (così all’avvio mostra subito il meteo)
  3. Bottone “Aggiorna” per ricaricare i dati senza riscrivere la città
  4. Extra: se cerchi una città già vista, mostra direttamente i dati senza chiamare la geocoding API