ハンズオンで学ぶFlutter(状態管理編)

##以前の振り返り
以前の記事でFlutterのUI構築をハンズオン形式で学んでいきました。
具体的には、Flutterとは何か、StatelessWidgt、StatefulWidgetの違い、その他のWidgetなどを学んでいきました。以下のURLから見てください。
[Flutterハンズオン(UI構築編)](https://qiita.com/syo2255/items/a578611632f0dc7e4d40)

以前の記事で、とりあえず見た目の部分は完成したので、今回の記事で状態管理の方法を学び、実際にニュースのデータを取ってきて表示していきましょう。

git clone https://github.com/shokiogawa/hands_on.git

にコードのデータがあるので、こちらのコードをベースに進めてください。

##Flutterでの状態管理

今の段階では、statelessWidgetを使っています。しかし、状態を管理するため、stateFulWidgetを使用しないといけないかというと、そうではありません。
もちろん、statefulwidgetを使用した方法もあるのですが、今回は、状態管理にProviderというライブラリを活用します。
このproviderを使うと、statelesswidgetなのかstatefulwidgetなのかを意識することなく状態を管理することが出来ます。

###Providerとは??
Providerとはパッケージの名前で、Flutterの状態管理の一つの手法です。
手法の一つといったようにFlutterには多くの状態管理の方法があります。しかし、そのなかでもGoogleはprovider推奨しています。
詳しくは[Flutterの状態管理](https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple)から見てください。

また、このproviderの中でも、複数の状態管理手法があります。
・Provider
・change_notifier
・state_notifier
etc...
この中で、今回はstate_notifierを使い状態管理をしていきます。
また、[state_notifier](https://pub.dev/packages/state_notifier)と同時に[freezed](https://pub.dev/packages/freezed)というパッケージも合わせて使用します。

###freezedとは
freezedは、イミュータブルなデータモデルを作成するために使うパケージです。
また、stateにcopyWithというメソッドが自動で作成されます。

state_notifierは管理する状態をイミュータブルなものとして扱うため、freezedと相性が良いです。

言葉で説明されても難しいと思うので実際にコードを書きながら学んでいきましょう。

##state_notifierとfreezedを使った状態管理
Flutterでこれらのパッケージを使い状態管理をする場合、いくつかのステップがあります。

>1. パッケージをインストール
1. freezedでイミュータブルなデータを管理するクラスを作成する。
1. 1.で作成したデータクラスをもとにfreezedのパッケージを使い自動でコードを生成する。(ここで、copyWithなどのメソッドが生成される。)
>3. 1.で作成したデータモデルをもとに、状態を変化させるロジックなどを管理するクラスを作成する。(クラスはstateNotifierを継承)

ここでわかる通り、二つのクラスを作成します。
1つは状態を保管するクラス(freezed)。
もう1つはその状態を変化させるためのクラス(state_notifier)です。

###ステップ1: 必要なパッケージをインストールする
pubspec.yamlに以下のパッケージを記述しインストールします。

```dart
dependencies:
  flutter:
    sdk: flutter
  provider: ^4.1.0-dev
  state_notifier: ^0.4.0
  flutter_state_notifier: ^0.3.0
  freezed_annotation:

dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner:
  freezed:
```

開発日によってそれぞれのバージョンが異なる可能性があるので、一つ一つチェックしてください。
[state_notifier](https://pub.dev/packages/state_notifier)
[flutter_state_notifier](https://pub.dev/packages/flutter_state_notifier)
[freezed](https://pub.dev/packages/freezed)
[freezed_annotation](https://pub.dev/packages/freezed_annotation/install)
[provider](https://pub.dev/packages/provider)
[build_runner](https://pub.dev/packages/build_runner)
###ステップ2: freezedでイミュータブルなデータを管理するクラスを作成する

```dart:lib/presentation/controller/news_controller.dart
part 'news_controller.freezed.dart';
@freezed
abstract class NewsState with _$NewsState {
  factory NewsState({
    List<Article> articles,
  }) = _NewsState;
}
```
ここでは、ニュースの情報を管理するクラスの名前を、NewsStateとします。
NewsStateクラスの中にあるarticles(型 List<Article>)にニュースのデータが入ります。

今の段階では、エラーが出ると思いますが、いまはほっておいて大丈夫です。

###ステップ3: freezedのパッケージを使いコードを自動生成。

コードを自動生成していきます。
ターミナルで、`flutter pub run build_runner build`と打つと、自動でコードが生成されます。
news_controller.dartがあるディレクトリにnews_controller.freezed.dartというファイルが自動で作られたと思います。

この段階でステップ2で出ていたエラーが消えたかと思います。

###ステップ4: 状態を変化させるロジックなどを管理するクラスを作成する

今回は、apiからnewのデータを取ってきたいので、newsデータを取ってきて、ステップ1で作成した`articles`に代入するロジックをこのクラスで書きます。

```dart:lib/presentation/controller/news_controller.dart

class NewsController extends StateNotifier<NewsState> {

  //apiからデータを照ってくる処理が書いてあるクラスのインスタンス化
  NewsQueryService _newsQueryService = NewsQueryService();

  NewsController() : super(NewsState());

  //ニュース情報を、apiからとってきて、NewsStateの中のarticlesに格納するメソッド
  Future<void> getNews() async {
    List<Article> articles = [];
    articles = await _newsQueryService.getNews();

    //NewsStateで格納しているデータの変更処理(state変数にすべて格納されている)
    //データの変更するためには、copyWithメソッドを使用する。
    state = state.copyWith(articles: articles);
  }
}
```

上のコードで書かれている、getNewsメソッドでニュースデータをapiからとってきて、NewsStateクラスの中のarticlesに格納しています。

ちなみに、NewsQueryServiceクラスに関しては、

```dart:lib/infrastructure/news_query_service.dart

class NewsQueryService {
  final ApiService _apiService = ApiService.create();

  Future<List<Article>> getNews() async {
    Response response;
    List<Article> result = List<Article>();

    try {
      response = await _apiService.getHeadlines();
      if (response.isSuccessful) {
        final responseBody = response.body;
        result = News.fromJson(responseBody).articles;
      } else {
        final error = response.error;
        print('isSuccessful$error');
      }
    } catch (error) {}

    return result;
  }
}
```

となっており、NewsQueryServiceクラスのgetNewsメソッドはListのArticleを返しています。それを、NewsControllerのメソッドで、NwesState内のarticlesに格納しています。
他にも必要なコードがありますが、こちらで提供します。

これで、apiからのデータをNewsStateのクラスに格納することに成功しました。

次は、実際にそのデータを画面に表示していきます。