Flutter

【Flutter】Chopperを使ってAPI通信をする!サンプルを用いて解説

こんにちは、テルプロです!

「Chopperを使ったAPI通信のやり方がわからない」とお悩みではないでしょうか?

テルプロ

本記事ではそんな悩みを解決していきます!

本記事を読むことで
  1. Riverpod + freezed + Chopperの使い方がわかるようになる
  2. サンプルを用いて解説しているので理解しやすい
  3. コードを公開しているので、自分の環境で確かめることができる

Chopperを使ってAPI通信をする【サンプル】

事前準備

パッケージをインストール

今回使用するパッケージは以下の通りです。

dependencies:
  build_runner: ^2.1.8
  chopper: ^4.0.5
  chopper_generator: ^4.0.5
  flutter:
    sdk: flutter
  flutter_riverpod: ^1.0.3
  freezed: ^1.1.1
  freezed_annotation: ^1.1.0
  json_serializable: ^6.1.5

dev_dependencies:
  flutter_lints: ^1.0.0
  flutter_test:
    sdk: flutter

プロジェクト構成

リポジトリ構成

完成イメージ

本アプリは1画面のみの構成になります。無料で公開されているサンプルAPIを取得し、Viewに反映させるというシンプルなアプリです。

それでは、Chopperを使って上記のアプリを作っていきましょう!

サンプルAPI:https://jsonplaceholder.typicode.com/posts

ソースコード

main.dart

import 'package:news_list_chopper/view/home_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'News List',
      theme: ThemeData(primarySwatch: Colors.blueGrey),
      home: const HomePage(),
    );
  }
}

model /news.dart

まず初めに、APIから取得したい内容のクラスを作成していきましょう。今回はタイトルと説明文の2つを取得していきます。

クラスの作成にはfreezedを使用しています。freezedを使用することで、不変クラスを作成することができます。

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';

//  自動生成されるファイル
part 'news.freezed.dart';
part 'news.g.dart';

@freezed
class News with _$News {
  factory News({
    String? title,
    String? body,
  }) = _News;

  factory News.fromJson(Map<String, dynamic> json) => _$NewsFromJson(json);
}

freezedでクラスを作成したら、下記のコマンドを回してください。

flutter pub run build_runner watch --delete-conflicting-outputs

これにより、ファイルが自動生成されます。–delete-conflicting-outputsを加えることで、クラスに変更が加えられる度に再生成されます。

modelフォルダが上記のようになっていれば問題ありません。これで取得したいデータを保持するクラスを作成することができました。

service / api_client.dart

次に、APIを呼び出しデータを取得するためのクラスを作っていきます。

APIの取得にはChopperを使用しています。

import 'package:chopper/chopper.dart';
import 'package:news_list_chopper/model/news.dart';

// 自動生成されるファイル
part 'api_client.chopper.dart';

@ChopperApi(baseUrl: 'https://jsonplaceholder.typicode.com')
abstract class ApiClient extends ChopperService {
  static ApiClient create() => _$ApiClient(ChopperClient());

  @Get(path: '/posts')
  Future<Response> getNews();
}

上記のクラスを作成したら、先ほどと同じように下記のコマンドを回してください。

flutter pub run build_runner watch --delete-conflicting-outputs

これにより新しいファイルが生成され、Chopperを用いたAPIの取得が可能になります。

serviceフォルダが上記のようになっていれば問題ありません。これで、APIを取得するためのクラスを作成することができました。

repository / repository.dart

APIを取得するためのメソッドをrepositoryから呼び出します。メソッドを切り分けることで、APIを取得するためのメソッドだと理解しやすくなります。

import 'dart:convert';

import 'package:news_list_chopper/model/news.dart';
import 'package:news_list_chopper/service/api_client.dart';

class Repository {
  Future<List<News>> getNews() {
    return ApiClient.create().getNews().then((value) {
      final list = json.decode(value.body) as List<dynamic>;
      final result = List<News>.from(list.map((e) => News.fromJson(e)));
      return result;
    });
  }
}

これで、APIを取得するためのメソッドをrepositoryに切り分けることができました。

view_model / provider.dart

次に、取得したAPIの状態を非同期で管理できるようにしていきます。状態管理にはRiverpodを使用しています。

今回は、非同期処理を行うのに適しているFutureProviderを使用していきます。

import 'package:news_list_chopper/model/news.dart';
import 'package:news_list_chopper/repository/repository.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// APIの取得を状態管理する
final repositoryProvider = Provider((ref) => Repository());

// APIの取得を非同期で管理する
final listProvider = FutureProvider<List<News>>((ref) async {
  final repository = ref.read(repositoryProvider);
  return await repository.getNews();
});

これにより、APIの取得を非同期で行えるようになりました。

home_page.dart

最後に、取得したAPIをViewに反映させる作業をしていきます。

FutureProviderのwhenメソッドを使用することで、Viewの反映、ローディング時、エラー時の3つの処理を書くことができるようになっています。

import 'package:news_list_chopper/model/news.dart';
import 'package:news_list_chopper/view_model/provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class HomePage extends ConsumerWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final asyncValue = ref.watch(listProvider);
    return Scaffold(
      appBar: AppBar(title: Text('News List')),
      body: Center(
        child: asyncValue.when(
          data: (data) {
            return data.isNotEmpty
                ? ListView(
                    children: data
                        .map(
                          (News news) => Card(
                            child: GestureDetector(
                              onTap: () {
                                showDialog(
                                  context: context,
                                  builder: (context) {
                                    return SimpleDialog(
                                      title: Text(news.title!),
                                      children: [
                                        SimpleDialogOption(
                                          child: Text(news.body!),
                                        ),
                                      ],
                                    );
                                  },
                                );
                              },
                              child: ListTile(
                                title: Text(news.title!),
                                subtitle: Text(news.body!),
                                trailing: const Icon(Icons.more_vert),
                              ),
                            ),
                          ),
                        )
                        .toList(),
                  )
                : const Text('Data is empty.');
          },
          loading: () => const CircularProgressIndicator(),
          error: (error, _) => Text(error.toString()),
        ),
      ),
    );
  }
}

大変お疲れ様でした!以上でアプリは完成です!

今回ご紹介したアプリ全体のソースコードはこちらです。

よろしければ、ご参考にどうぞ。

GitHub:https://github.com/terupro/chopper_sample

まとめ

今回はChopperでAPI通信を実装する方法をご紹介しました。

Chopperを使うことで、API通信を楽に実装することができます。ご紹介したサンプルを参考に、自分で色々と試してみてください。

▼以下では、私の実体験に基づいて「Flutterの効率的な勉強法」の具体的な手順を詳しく解説しています。よろしければ、ご参考にどうぞ!

最後までご覧いただきありがとうございました。ではまた!

参考文献
ABOUT ME
テルプロ
東京都在住のアプリエンジニア。大学では、ソフトウェア開発の研究に取り組む。長期のエンジニアインターンシップを経て、実務スキルを磨き、現在はフリーランスエンジニアとしても活動中。メインはモバイルアプリ開発。IT関連の記事監修も行い、技術の共有と普及に励んでいます。 監修実績(レバテックフリーランス
Flutter関連の書籍を出版しました!