16
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Flutter】実践!state_notifier + freezedでGoogle Books APIを叩いてみよう【初心者向け】

Last updated at Posted at 2020-07-25

2021/7/3 追記

providerの上位版ライブラリであるRiverpodで書き換えたサンプルも作りました!
Null安全でも(一応)動くように修正してあります。
StateNotifierの宣言もファイル内に書くだけという簡単仕様になるなど、色々とメリットがあるため、今後はこちらで書いたほうが良さそうですね!

https://github.com/alpha2048/riverpod_test


こんにちは、Flutter民です:v:

概要

ここ最近でFlutterで熱いパッケージとして、以下のパッケージがあります

  • freezed
    • いわゆるdataクラス的なものをサクッと作れるパッケージ
    • json周りのコードを自動生成してくれるのでAPIの受け皿にも使えます
  • state_notifier
    • 状態管理をやってくれるパッケージ
    • 参照しやすいViewModel的なものを簡単に作れます

これらを使い、実際にGoogle Books APIを叩いて情報を表示するアプリを作りました。
(巷にはカウンターアプリのサンプルはたくさんあるのに、なんでAPI呼ぶようなサンプルはないんや・・と思いながら作りました。。

前提

各パッケージの細かい説明は省略しましたが、以下の記事が参考になります

Sample

https://github.com/alpha2048/StateNotifierTest

↓ iOSの表示例

スクリーンショット 2020-07-25 10.09.48.png

解説

パッケージ類のインストール

なにはともあれパッケージ類のインストールです。
ここでflutter_state_notifierproviderも一緒にインストールすることで、より快適にstate_notifierを使えるようになります。
API呼び出しを行うため、httpも入れます。

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^0.1.3

  state_notifier: ^0.5.0
  flutter_state_notifier: ^0.4.2
  freezed_annotation: ^0.11.0
  provider: ^4.3.1
  json_annotation: ^3.0.1
  http: ^0.12.2
  url_launcher: ^5.5.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  json_serializable: ^3.3.0
  build_runner: ^1.10.0
  freezed: ^0.11.4

model

Response

freezedで作っていきます。
作り終わったら、flutter pub run build_runner build を叩いてbuildしましょう。

GoogleBooksResponse.dart
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:state_notifier_test/model/GoogleBookResponse.dart';
part 'GoogleBooksResponse.freezed.dart';
part 'GoogleBooksResponse.g.dart';

@freezed
abstract class GoogleBooksResponse with _$GoogleBooksResponse {
  const factory GoogleBooksResponse({
    String kind,
    int totalItems,
    List<GoogleBookResponse> items
  }) = _GoogleBooksResponse;

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

API

httpで呼び出し、先程freezedで作ったResponseクラスでdecodeします。

GoogleBooksAPIService.dart
import 'dart:convert' as convert;
import 'package:http/http.dart' as http;
import 'package:state_notifier_test/model/GoogleBooksResponse.dart';

Future<GoogleBooksResponse> getBooks(String keyword) async {

  var url = 'https://www.googleapis.com/books/v1/volumes?q=$keyword';
  print(url);

  final response = await http.get(url);
  if (response.statusCode == 200) {
    return GoogleBooksResponse.fromJson(convert.jsonDecode(response.body));
  } else {
    throw Exception('Failed to connect to webservice');
  }
}

ViewModel

State

StateNotifierが管理するStateです。
こちらも作り終わったらbuildしましょう。

実際にView側で使うレスポンスの他、状態を通知するEnumを定義しています。

MainViewModelData.dart
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:state_notifier_test/model/GoogleBooksResponse.dart';

part 'MainViewModelData.freezed.dart';

enum MainViewModelState { normal, loading, error }

@freezed
abstract class MainViewModelData with _$MainViewModelData {
  const factory MainViewModelData({
    GoogleBooksResponse response,
    MainViewModelState viewModelState
  }) = _MainViewModelData;
}

StateNotifier

StateNotifierの実装です。
シンプルでわかりやすいですね。

MainViewModel.dart
import 'package:state_notifier/state_notifier.dart';
import 'package:state_notifier_test/model/GoogleBooksAPIService.dart';
import 'package:state_notifier_test/view/MainViewModelData.dart';

class MainViewModel extends StateNotifier<MainViewModelData> {
  MainViewModel(): super(MainViewModelData());

  void fetch(String keyword) {
    state = state.copyWith(viewModelState: MainViewModelState.loading);
    getBooks(keyword)
        .then((res) {
          state = state.copyWith(response: res, viewModelState: MainViewModelState.normal);
        }).catchError((_) {
          state = state.copyWith(response: null, viewModelState: MainViewModelState.error);
        });
  }
}

View

StateNotifier生成

ViewModelを使うWidgetの親側のどこかで、StateNotifierを生成します。
いろんなWidgetで参照したい場合は、なるべく上層側で定義してあげましょう。

main.dart
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Google Book State Notifier Sample',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: StateNotifierProvider<MainViewModel, MainViewModelData>(
        create: (_) => MainViewModel(),
        child: MyHomePage(title: 'Google Book State Notifier Sample'),
      ),
    );
  }
}

StateNotifierのStateを読む

context.select<Data, T>でデータを読み出します。
selectを使うと、データに変化があった場合に自動でrebuildしてくれます。

main.dart
final response = context.select<MainViewModelData, GoogleBooksResponse>((data) => data.response);
final state = context.select<MainViewModelData, MainViewModelState>((data) => data.viewModelState);
final List<GoogleBookResponse> bookList = response != null ? response.items : [];

あとは読みだしたデータを参考にViewを作っていくだけです!

おわりに

FlutterはBLoCパターンなどアーキテクチャ周りがやや難しいな、という印象がありましたが、state_notifierを使えばMVVMパターンをスッキリ書けるようになり、個人的にはとても嬉しい進化を遂げています。
今後はstate_notifierが一般的になっていくのでしょうか?

参考資料

16
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?