Menu
CoddyTech

天気アプリ:今後3日間

CoddyのFlutterジャーニー「データとネットワーク」セクションの一部。レッスン 24/27。

都市の回答には予報も含まれています。オブジェクト内のJSONリストで、1日につき1項目です。

"forecast": [
  {"date": "2026-10-20", "condition": "Cloudy", "high": 22, "low": 15},
  ...
]

オブジェクト内のリストも、単独のリストと同じ方法で読み取ります。まず取り出して as List<dynamic> とし、次に各項目をそれぞれの fromJson に渡して map します。ファイルの末尾にある Weather.fromJson を見てください。すでにこの処理を行っているので、weather.forecast は List<DayForecast> です。

final days = json['forecast'] as List<dynamic>;

forecast: days
    .map((day) => DayForecast.fromJson(day as Map<String, dynamic>))
    .toList(),

各日には DateTime があります。DateFormat('EEEE') は、それを曜日の完全な名前である「火曜日」に変換します。Column の children 内に for を置くと、日ごとに1行ずつ作成されます。

challenge icon

チャレンジ

中級

現在の天気の下に、今後3日間を表示してください。

  • package:intl/intl.dartをimportします
  • SizedBox(height: 32)の後に、各日のRowを1つずつ配置します。Expanded内にDateFormat('EEEE')で曜日を表示し、その後に最高気温と最低気温を22° / 15°の形式で表示します
  • 各行の左右に32ピクセルのパディングを設定します

自分で試してみよう

import 'dart:convert';

import 'package:flutter/material.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));
  }

  @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)),
        Text(
          '${weather.temperature}°',
          style: const TextStyle(fontSize: 72, fontWeight: FontWeight.bold),
        ),
        Text(weather.condition, style: const TextStyle(fontSize: 24)),
      ],
    );
  }
}

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オンラインコンパイラ