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関連の記事監修も行い、技術の共有と普及に励んでいます。 監修実績(レバテックフリーランス
\ 登録しておいて損なし!IT就活支援サービス /
1位:レバテックルーキー
レバテックルーキー」とは、新卒でITエンジニアを目指す学生の支援に特化した就職エージェントです。業界支持率NO.1の実績を誇っており、エージェント選びに悩む学生におすすめです。エンジニア業界を熟知したアドバイザーが担当します。面接対策だけでなく、学生の志向性やスキル、入社後のキャリアパスを考慮したアドバイスをしてくれる就職活動の強い味方となります。
おすすめ度
URLレバテックルーキーはこちら

 

2位:OfferBox
OfferBox」とは、企業が興味を持った学生にオファーする、新しい就活サイトです。学生は自分のプロフィールを登録しておくだけで、企業からオファーをもらえます。学生利用率No.1で就活生の3人に1人が利用しており、IT業界を志望する学生にもおすすめです。いわゆる一斉配信ができないため、企業はしっかりと学生のプロフィールを見て、オファーを送ってくるので安心です。
おすすめ度
URLオファー型就活アプリOfferBox

 

3位:GeekSalon
GeekSalon」とは、全国展開の大学生限定プログラミングスクールです。Web開発やアプリ開発など多様なコースを提供しています。お値段は他スクールの3分の1程度の料金で、大学生でも通いやすい値段設定です。専属メンターが全面的にサポートしてくれるため、未経験でも安心です。また、切磋琢磨できる同世代の仲間に出会えます。スクールを検討している大学生におすすめです。
おすすめ度
URL大学生限定コミュニティ「GeekSalon」