Ứng dụng thời tiết: thử lại
Thuộc phần Dữ liệu và mạng trong hành trình Flutter của Coddy. Bài học 26 trên 27.
Người dùng mở ứng dụng thời tiết trên tàu, trong đường hầm và trên núi. Khi không có tín hiệu, ứng dụng nên nói rõ điều đó và đưa ra cách quay lại, thay vì hiển thị vòng xoay không bao giờ dừng hoặc một dòng văn bản báo lỗi.
Hãy dành một màn hình nhỏ riêng cho lỗi: một biểu tượng, một câu dễ hiểu, thông tin chi tiết bên dưới và nút Thử lại tạo một future mới trong setState. Đặt màn hình này trong phương thức riêng, buildError, cạnh buildWeather, để build vẫn ngắn gọn.
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasError) {
return buildError(snapshot.error!);
}
return buildWeather(snapshot.data!);Kiểm tra ConnectionState.waiting trước sẽ hiển thị vòng xoay trong mỗi lần tải, kể cả lần thử thứ hai. Khi future hoàn tất, sẽ có lỗi hoặc dữ liệu, nên dòng cuối có thể dùng snapshot.data!.
Để xem trong bản xem trước, hãy viết offline = true; ở dòng đầu tiên của main rồi nhấn Chạy. Xóa dòng đó trước khi nhấn Kiểm tra.
Thử thách
Trung bìnhThêm màn hình lỗi cho ứng dụng:
- một phương thức (method)
buildError(Object error):Icons.cloud_off(kích thước 64),Could not load the weather(kích thước 20), văn bản lỗi và nút Thử lại - một phương thức
tryAgainđặt mộtloadWeather()mới trongsetState - trong builder, kiểm tra
ConnectionState.waitingtrước, rồi kiểm tra lỗi, sau đó hiển thị thời tiết
Tự làm thử
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,
);
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Dữ liệu và mạng
3Khi lời gọi hàm thất bại
Bắt lỗi bằng tryNém lỗiDọn dẹp với finallyTrạng thái lỗi của FutureBuilderNút thử lại6Dự án: ứng dụng thời tiết
Ứng dụng thời tiết: hiện tạiỨng dụng thời tiết: ba ngày tớiTự luyện tập: Trình biên dịch Flutter online