【Flutter】APIと通信する【http】

ネコニウム研究所

PCを利用したモノづくりに関連する情報や超個人的なナレッジを掲載するブログ

【Flutter】APIと通信する【http】

2024-4-16 | ,

Flutterでパッケージhttpを使ってAPIと通信(GETとかPOSTとか)したい!

概要

今回の記事では、Flutterでパッケージhttpを使ってAPIとGETPOSTなどの通信をする手順を掲載する。

仕様書

環境

  • Android Studio Giraffe | 2023.2.1 Patch 2
  • Flutter 3.19.6
  • flutter_riverpod: 2.5.1
  • http: 1.1.0

手順書

インストール編とコード編の2部構成です。

インストール編

ターミナルでコマンドを実行するか

flutter pub add flutter_riverpod http

pubspec.yamldependencies:に下記のような感じで追加して

dependencies:
  flutter_riverpod: ^2.5.1
  http: ^1.1.0

ターミナルでflutter pub getする。

flutter pub get

コード編

今回はOpen-Meteoという天気予報のAPIと通信してみる。

このAPIはAPIキーなどの認証をしなくても使わせてもらえるので、楽にAPIとのやり取りを試せる。

パッケージflutter_riverpodを使って非同期でAPIからデータを取得する。

東京の7日間分の最高気温、最低気温の予想をAPIから取得してTableに表示する例。

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: const HttpSample1()
      )
    )
  );
}

class WeatherInfo {
  late List<double> temperature2mMax;
  late List<double> temperature2mMin;
  late List<String> time;
  late List<int> weatherCode;

  WeatherInfo(
      {required this.temperature2mMax,
      required this.temperature2mMin,
      required this.time,
      required this.weatherCode});
}

final weatherInfoProvider = FutureProvider<WeatherInfo>((ref) async {
  return fetchWeatherInfo();
});

Future<WeatherInfo> fetchWeatherInfo() async {
  try {
    final response = await http.get(
      Uri.parse(
          'https://api.open-meteo.com/v1/forecast?latitude=35.6785&longitude=139.6823&daily=weather_code,temperature_2m_max,temperature_2m_min&timezone=Asia%2FTokyo'),
    );

    debugPrint('response.statusCode: ${response.statusCode}');
    debugPrint('response.headers: ${response.headers}');
    debugPrint('response.body: ${response.body}');

    if (response.statusCode == 200) {
      final Map<String, dynamic> responseJson = jsonDecode(response.body);
      return WeatherInfo(
        temperature2mMax:
            List<double>.from(responseJson['daily']['temperature_2m_max']),
        temperature2mMin:
            List<double>.from(responseJson['daily']['temperature_2m_min']),
        time: List<String>.from(responseJson['daily']['time']),
        weatherCode: List<int>.from(responseJson['daily']['weather_code']),
      );
    }
  } catch (e) {
    debugPrint(e.toString());
    throw Exception(e.toString());
  }

  throw Exception('Failed to load weather info');
}

class HttpSample1 extends StatelessWidget {
  const HttpSample1({super.key});

  @override
  Widget build(BuildContext context) {
    return ProviderScope(
      child: SafeArea(
        child: Center(
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Consumer(
                builder: (BuildContext context, WidgetRef ref, Widget? child) {
              List<String> headers = [
                'Date',
                'Max Temp (°C)',
                'Min Temp (°C)',
                'Weather Code'
              ];
              final asyncValue = ref.watch(weatherInfoProvider);

              return asyncValue.when(
                data: (data) => Table(
                  border: TableBorder.all(),
                  columnWidths: {
                    for (int i = 0; i < headers.length; i++)
                      i: const FlexColumnWidth(),
                  },
                  children: [
                    TableRow(
                      decoration: BoxDecoration(color: Colors.grey[300]),
                      children: headers.map((header) {
                        return Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: Text(
                            header,
                            textAlign: TextAlign.center,
                            style: const TextStyle(fontWeight: FontWeight.bold),
                          ),
                        );
                      }).toList(),
                    ),
                    ...List.generate(data.time.length, (index) {
                      return TableRow(
                        children: [
                          Padding(
                            padding: const EdgeInsets.all(8.0),
                            child: Text(
                              data.time[index],
                              textAlign: TextAlign.center,
                            ),
                          ),
                          Padding(
                            padding: const EdgeInsets.all(8.0),
                            child: Text(
                              data.temperature2mMax[index].toString(),
                              textAlign: TextAlign.right,
                            ),
                          ),
                          Padding(
                            padding: const EdgeInsets.all(8.0),
                            child: Text(
                              data.temperature2mMin[index].toString(),
                              textAlign: TextAlign.right,
                            ),
                          ),
                          Padding(
                            padding: const EdgeInsets.all(8.0),
                            child: Text(
                              data.weatherCode[index].toString(),
                              textAlign: TextAlign.right,
                            ),
                          ),
                        ],
                      );
                    }),
                  ],
                ),
                loading: () => const CircularProgressIndicator(),
                error: (error, stack) => Text(
                  'Error: $error',
                  style: const TextStyle(color: Color(0xFFFF0000)),
                ),
              );
            }),
          ),
        ),
      ),
    );
  }
}

APIにリクエストを送ってる部分。

    final response = await http.get(
      Uri.parse(
          'https://api.open-meteo.com/v1/forecast?latitude=35.6785&longitude=139.6823&daily=weather_code,temperature_2m_max,temperature_2m_min&timezone=Asia%2FTokyo'),

今回は、経度緯度や受け取りたい情報の指定をクエリーパラーメーターで送ってるのとAPIキーなどの認証のための情報をこちらから送信する必要がないのでヘッダーやボディの設定はない。

ヘッダーやボディを送信する場合は下記のような感じになる。JSONを送信する例。

    Map<String, String> headers = {
      'Content-Type': 'application/json',
    };

    final response = await http.get(
      Uri.parse(
          'https://api.open-meteo.com/v1/forecast?latitude=35.6785&longitude=139.6823&daily=weather_code,temperature_2m_max,temperature_2m_min&timezone=Asia%2FTokyo'),
      headers: headers,
      body: jsonEncode({
        'info': "xxxx",
      }),

戻り値のプロパティstatusCodeを確認することで通信が成功したか失敗したか確認できる。

APIからデータの取得に成功してれば戻り値のプロパティbodyにJSON形式で保管される。convert.dartjsonDecodeを使ってオブジェクトに変換してデータにアクセスする感じ。

戻り値のプロパティheadersにヘッダーがは保管される。例えば、クッキー認証した場合はクッキーがヘッダーの中に入ってたり。

まとめ(感想文)

APIサーバーと通信するアプリを作る時に使えるかもね!