🤖

【Flutter】Dioとjson_serializableで始めるモダンHTTP通信

に公開

はじめに

FlutterでAPI通信を行うなら、機能豊富で使いやすいDioがデファクトスタンダードです。この記事では、JSONのモデル化を自動化する json_serializable と組み合わせた、クリーンで型安全な通信のベストプラクティスを解説します。

Dio単体での利用手順


まずは、最もシンプルなGETリクエストの実行方法です。

導入

flutter pub add dio

pubspec.yaml

dependencies:
  dio: ^5.9.0

import

import 'package:dio/dio.dart';

Dioインスタンスの作成

Dio()インスタンスを作成します。

final dio = Dio();

GETリクエストの作成

awaitで非同期的にリクエストを実行します。

Dioは自動でJSONをデコードします

final dio = Dio();

Future<void> fetchItems() async {
  final response = await dio.get('http://localhost:3000/datas'); 
  print(response);
}

非同期処理を呼び出し

ウィジェットが画面に表示された直後に一度だけ実行する場合、initStateから呼び出します。このとき、async/awaitではなく、initStateは同期的に完了させるため、単に非同期関数を呼び出す形式をとります。

  
  void initState() {
    super.initState();
    fetchItems();
  }

Dio + json_serializableで型安全な通信を実現


JSONデータを安全なDartの独自のクラス型(モデル)に変換するために、コード生成ツールを組み合わせます。

導入

flutter pub add dio
flutter pub add json_annotation
flutter pub add --dev json_serializable
flutter pub add --dev build_runner

pubspec.yaml

dependencies:
  dio: ^5.9.0
  json_annotation: ^4.9.0

dev_dependencies:
  json_serializable: ^6.11.1
  build_runner: ^2.10.2

クラス(データモデルとなる型)を作成

  1. クラスを作成
class Data {
  final int id;
  final String title;
  Data(this.id, this.title);
}
  1. part 'data.g.dart';とクラスに@JsonSerializable()を付与

モデルクラスにアノテーションを付与し、自動生成コードを取り込む指示を記述します。

import 'package:json_annotation/json_annotation.dart';

part 'data.g.dart';

()
class Data {
  final int id;
  final String title;
  Data(this.id, this.title);
}
  1. コード生成を実行
dart run build_runner watch
  1. JSONとの相互変換のメソッドやコンストラクタを記述

partで取り込んだ自動生成コードの機能を呼び出す

import 'package:json_annotation/json_annotation.dart';

part 'data.g.dart';

()
class Data {
  final String id;
  final String title;
  Data(this.id, this.title);

  factory Data.fromJson(Map<String, dynamic> json) => _$DataFromJson(json);

  Map<String, dynamic> toJson() => _$DataToJson(this);
}

GETリクエストの作成

取得したdynamicなデータを、Data.fromJsonを使って型安全なList<Data>に変換します。

import 'package:dio/dio.dart';
import '../model/data.dart';

final dio = Dio();

Future<List<Data>> fetchItems() async {
  final res = await dio.get<List>('http://localhost:3000/datas');

  // List<dynamic> -> List<Data>に変換
  final dataList = res.data!.map((el) {
    return Data.fromJson(el);
  }).toList();

  return dataList;
}

非同期処理を呼び出し

  
  void initState() {
    super.initState();
    fetchItems().then((dataList) => print(dataList));
  }

POSTの例

POSTリクエストを作成

データを送信するPOSTリクエストでは、モデルのtoJson()メソッドを活用して、DartオブジェクトをJSON形式に変換して送信します。

import 'package:dio/dio.dart';
import '../model/data.dart';

final dio = Dio();

Future<void> postData(Data newData) async {
  final res = await dio.post(
    'http://localhost:3000/datas',
    data: newData.toJson(),
  );
  print(res.data);
}

非同期処理を呼び出し

ボタンタップでの呼び出す例です。ユーザー操作をきっかけとする非同期処理は、コールバック関数にasyncを付けてawaitを使用します。

ElevatedButton(
  onPressed: () async {
    final newData = Data('999', 'some_title');
    await postData(newData);
  },
  child: Text('send'),

サンプルコードの全体像を掲載


data.dart

import 'package:json_annotation/json_annotation.dart';

part 'data.g.dart';

()
class Data {
  final String id;
  final String title;
  Data(this.id, this.title);

  factory Data.fromJson(Map<String, dynamic> json) => _$DataFromJson(json);

  Map<String, dynamic> toJson() => _$DataToJson(this);
}

fetch.dart

import 'package:dio/dio.dart';
import '../model/data.dart';

/// DIOインスタンス
final dio = Dio();

/// GETリクエスト
Future<List<Data>> fetchItems() async {
  final res = await dio.get<List>('http://localhost:3000/datas');

  // List<dynamic> -> List<Data>に変換
  final dataList =
      res.data!.map((el) {
        return Data.fromJson(el);
      }).toList();

  return dataList;
}

/// POSTリクエスト
Future<void> postData(Data newData) async {
  final res = await dio.post(
    'http://localhost:3000/datas',
    data: newData.toJson(),
  );
  print(res.data);
}

おわりに

これらの手順を踏むことで、Dioとjson_serializableを活用した、保守性が高くエラーの少ないFlutterアプリの開発が可能になります。

Discussion