Menu
CoddyTech

App meteo: scegli una città

Fa parte della sezione Dati e rete del percorso Flutter di Coddy. Lezione 27 di 27.

Ultima funzionalità: una fila di città in alto, Lisbona, Tokyo e Oslo. Toccandone una, viene caricato il meteo.

Scegliere una città equivale a ricaricare usando un percorso diverso. Mantieni la città scelta nello stato, assegna un parametro a loadWeather e costruisci il percorso a partire da esso:

Future<Weather> loadWeather(String city) async {
  final body = await fetch('/weather/${city.toLowerCase()}');
  return Weather.fromJson(jsonDecode(body));
}

I pulsanti riportano Tokyo, ma il percorso del server è /weather/tokyo. toLowerCase() trasforma l'uno nell'altro. I percorsi sulla maggior parte dei server distinguono tra maiuscole e minuscole, quindi /weather/Tokyo restituisce Not found.

Metti la fila di pulsanti fuori dal FutureBuilder, così rimane sullo schermo mentre viene caricato il meteo di una città. Una Column con la fila in alto e il FutureBuilder in un Expanded sotto fa proprio questo.

challenge icon

Sfida

Difficile

Aggiungi un selettore di città:

  • una String city = 'Lisbon'; nello stato e loadWeather(String city) che recupera /weather/ seguito dal nome della città in minuscolo
  • in cima al corpo, una Row centrata con tre TextButton: Lisbon, Tokyo, Oslo
  • quando ne tocchi uno, imposta city e un nuovo _weather in setState
  • sotto la riga, inserisci il FutureBuilder in un Expanded, e Riprova ricarica la città scelta

Provalo tu

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

import 'api.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: WeatherScreen(),
    );
  }
}

class WeatherScreen extends StatefulWidget {
  const WeatherScreen({super.key});

  @override
  State<WeatherScreen> createState() => _WeatherScreenState();
}

class _WeatherScreenState extends State<WeatherScreen> {
  late Future<Weather> _weather;

  @override
  void initState() {
    super.initState();
    _weather = loadWeather();
  }

  Future<Weather> loadWeather() async {
    final body = await fetch('/weather/lisbon');
    return Weather.fromJson(jsonDecode(body));
  }

  void tryAgain() {
    setState(() {
      _weather = loadWeather();
    });
  }

  IconData iconFor(String condition) {
    return switch (condition) {
      'Sunny' => Icons.wb_sunny,
      'Cloudy' => Icons.cloud,
      'Rainy' => Icons.umbrella,
      'Snowy' => Icons.ac_unit,
      _ => Icons.help_outline,
    };
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Weather')),
      body: Center(
        child: FutureBuilder<Weather>(
          future: _weather,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const CircularProgressIndicator();
            }
            if (snapshot.hasError) {
              return buildError(snapshot.error!);
            }
            return buildWeather(snapshot.data!);
          },
        ),
      ),
    );
  }

  Widget buildWeather(Weather weather) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Text(weather.city, style: const TextStyle(fontSize: 32)),
        Icon(iconFor(weather.condition), size: 96, color: Colors.orange),
        Text(
          '${weather.temperature}°',
          style: const TextStyle(fontSize: 72, fontWeight: FontWeight.bold),
        ),
        Text(weather.condition, style: const TextStyle(fontSize: 24)),
        const SizedBox(height: 32),
        for (final day in weather.forecast)
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 8),
            child: Row(
              children: [
                Icon(iconFor(day.condition)),
                const SizedBox(width: 16),
                Expanded(
                  child: Text(
                    DateFormat('EEEE').format(day.date),
                    style: const TextStyle(fontSize: 18),
                  ),
                ),
                Text('${day.high}° / ${day.low}°', style: const TextStyle(fontSize: 18)),
              ],
            ),
          ),
      ],
    );
  }

  Widget buildError(Object error) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        const Icon(Icons.cloud_off, size: 64),
        const SizedBox(height: 16),
        const Text('Could not load the weather', style: TextStyle(fontSize: 20)),
        Text('$error'),
        const SizedBox(height: 16),
        ElevatedButton(onPressed: tryAgain, child: const Text('Try again')),
      ],
    );
  }
}

class Weather {
  Weather({
    required this.city,
    required this.temperature,
    required this.condition,
    required this.forecast,
  });

  final String city;
  final int temperature;
  final String condition;
  final List<DayForecast> forecast;

  factory Weather.fromJson(Map<String, dynamic> json) {
    final days = json['forecast'] as List<dynamic>;
    return Weather(
      city: json['city'] as String,
      temperature: json['temperature'] as int,
      condition: json['condition'] as String,
      forecast: days
          .map((day) => DayForecast.fromJson(day as Map<String, dynamic>))
          .toList(),
    );
  }
}

class DayForecast {
  DayForecast({
    required this.date,
    required this.condition,
    required this.high,
    required this.low,
  });

  final DateTime date;
  final String condition;
  final int high;
  final int low;

  factory DayForecast.fromJson(Map<String, dynamic> json) {
    return DayForecast(
      date: DateTime.parse(json['date'] as String),
      condition: json['condition'] as String,
      high: json['high'] as int,
      low: json['low'] as int,
    );
  }
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Dati e rete

Esercitati da solo: Compilatore Flutter online