Menu
CoddyTech

Application météo : choisir une ville

Fait partie de la section Données et réseau du Journey Flutter de Coddy. Leçon 27 sur 27.

Dernière fonctionnalité : une rangée de villes en haut, Lisbonne, Tokyo et Oslo. Touchez l’une d’elles pour charger sa météo.

Choisir une ville revient à recharger avec un chemin différent. Conservez la ville choisie dans l’état avec city, donnez un paramètre à loadWeather et construisez le chemin à partir de celui-ci :

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

Les boutons affichent Tokyo, mais le chemin du serveur est /weather/tokyo. toLowerCase() transforme l’un en l’autre. La casse est importante pour les chemins sur la plupart des serveurs : /weather/Tokyo renvoie Not found.

Placez la rangée de boutons à l’extérieur de FutureBuilder, afin qu’elle reste à l’écran pendant le chargement d’une ville. Une Column avec la rangée en haut et le FutureBuilder dans un Expanded en dessous permet de faire cela.

challenge icon

Défi

Difficile

Ajoutez un sélecteur de ville :

  • un String city = 'Lisbon'; dans l’état, et loadWeather(String city) qui récupère /weather/ suivi du nom de la ville en minuscules
  • en haut du corps, une Row centrée de trois TextButton : Lisbon, Tokyo, Oslo
  • appuyer sur l’un d’eux définit city et un nouveau _weather dans setState
  • sous la ligne, le FutureBuilder dans un Expanded, et Réessayer recharge la ville choisie

Essayez vous-même

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 iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Données et réseau

Entraînez-vous par vous-même : Compilateur Flutter en ligne