אפליקציית מזג אוויר: בחירת עיר
חלק מהיחידה נתונים ורשת במסלול ה-Flutter של Coddy. שיעור 27 מתוך 27.
התכונה האחרונה: שורת ערים בחלק העליון, ליסבון, טוקיו ואוסלו. הקשה על אחת מהן טוענת את מזג האוויר שלה.
בחירת עיר היא טעינה מחדש עם נתיב אחר. שומרים את city שנבחרה במצב, מעבירים ל־loadWeather פרמטר ובונים ממנו את הנתיב:
Future<Weather> loadWeather(String city) async {
final body = await fetch('/weather/${city.toLowerCase()}');
return Weather.fromJson(jsonDecode(body));
}על הכפתורים כתוב Tokyo, אבל הנתיב בשרת הוא /weather/tokyo. toLowerCase() הופכת אחד לשני. ברוב השרתים הנתיבים מבחינים בין אותיות גדולות לקטנות, לכן /weather/Tokyo מחזיר Not found.
מציבים את שורת הכפתורים מחוץ ל־FutureBuilder, כדי שהיא תישאר על המסך בזמן טעינת עיר. Column עם השורה למעלה ועם FutureBuilder בתוך Expanded למטה עושה זאת.
אתגר
קשההוסיפו בורר עיר:
String city = 'Lisbon';במצב, ו-loadWeather(String city)שטוענת את/weather/בתוספת שם העיר באותיות קטנות- בחלק העליון של גוף המסך,
Rowממורכזת ובה שלושהTextButton:Lisbon,Tokyo,Oslo - הקשה על אחד מהם מגדירה את
cityואת_weatherהחדש בתוךsetState - מתחת לשורה, ה-
FutureBuilderבתוךExpanded, ו-Try again טוען מחדש את העיר שנבחרה
נסו בעצמכם
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,
);
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה נתונים ורשת
3כשקריאות נכשלות
תפיסת שגיאות באמצעות tryזריקת שגיאהניקוי באמצעות finallyמצב השגיאה של FutureBuilderכפתור לניסיון חוזר6פרויקט: אפליקציית מזג אוויר
אפליקציית מזג אוויר: עכשיואפליקציית מזג אוויר: שלושת הימים הבאיםתרגלו בעצמכם: קומפיילר Flutter אונליין