Menu
CoddyTech

אפליקציית מזג אוויר: לנסות שוב

חלק מהיחידה נתונים ורשת במסלול ה-Flutter של Coddy. שיעור 26 מתוך 27.

פותחים אפליקציות מזג אוויר ברכבות, במנהרות ועל הרים. בלי קליטה, האפליקציה צריכה לומר זאת בבירור ולהציע דרך לחזור, ולא להציג מד טעינה שלא מפסיק להסתובב או שורת שגיאה.

תנו לשגיאה מסך קטן משלה: סמל, משפט שאנשים מבינים, הפרטים מתחתיו, וכפתור לנסות שוב שיוצר עתיד חדש ב־setState. שימו אותו בשיטה משלו, buildError, לצד buildWeather, כדי ש־build יישאר קצר.

if (snapshot.connectionState == ConnectionState.waiting) {
  return const CircularProgressIndicator();
}
if (snapshot.hasError) {
  return buildError(snapshot.error!);
}
return buildWeather(snapshot.data!);

בדיקה של ConnectionState.waiting תחילה מציגה את מד הטעינה בכל טעינה, כולל הניסיון השני. לאחר שהעתיד מסתיים, יש שגיאה או נתונים, ולכן השורה האחרונה יכולה להשתמש ב־snapshot.data!.

כדי לראות זאת בתצוגה המקדימה, כתבו offline = true; בשורה הראשונה של main ולחצו על Run. הסירו זאת לפני שתלחצו על Check.

challenge icon

אתגר

בינוני

הוסף לאפליקציה מסך שגיאה:

  • מתודה buildError(Object error): Icons.cloud_off (גודל 64), Could not load the weather (גודל 20), הטקסט של השגיאה, וכפתור נסה שוב
  • מתודה tryAgain שמגדירה loadWeather() חדש בתוך setState
  • ב־builder, בדוק תחילה את ConnectionState.waiting, אחר כך את השגיאה, ואז הצג את מזג האוויר

נסו בעצמכם

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));
  }

  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.hasError) {
              return Text('Could not load: ${snapshot.error}');
            }
            if (snapshot.hasData) {
              return buildWeather(snapshot.data!);
            }
            return const CircularProgressIndicator();
          },
        ),
      ),
    );
  }

  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)),
              ],
            ),
          ),
      ],
    );
  }
}

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 iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה נתונים ורשת

תרגלו בעצמכם: קומפיילר Flutter אונליין