🎉

FlutterのProvider入門に挫折した人のためのProvider入門

2020/10/23に公開

こんにちはYuKiO(@oo_forward)です。本業はイベント業ですが、副業の一貫として、Flutterでアプリを、RailsでWebサービスを開発してます。

これまで開発したアプリ

【ひらめきを刺激する-アイデアメモiX】
■AppStore
https://apps.apple.com/jp/app/id1517535550…

■GooglePlay
https://play.google.com/store/apps/details?id=com.IdeaShuffleMemoApp&hl=ja…

【ブラウザが透ける-スケルブラウザ】
https://apps.apple.com/jp/app/id1535896106

Providerがよく分からない・・・汗

「アイデアメモiX」を開発中に、どうしてもsetStateの状態管理だけでは実現できない機能があり、どうもProviderが必要そう。

しかし、「Provider?」、「Provide?」、「Bloc?」など色々な単語が盛り沢山で、全然理解できませんでした笑
(すんなり理解できる人ってすごいですよね。)

一度は挫折しかけましたが、なんとか一通りの機能をアプリに反映するところまではできました。

この記事でわかること

今回は「Provider」について学びたいけど、よく分からないというFlutter初心者の方ために、Providerって 「こんな感じ」という概念的 なものをお伝えしたいと思います。

具体的な実装方法はこの記事では説明しません。

具体的な方法を掲載している記事は多いのですが、大枠の概念を知っていて当然という形で書かれているものが多い。

もっと概念を説明する記事があってもいいのでは?と思って、この記事を書きました。

この記事が、Providerの実装記事を理解する手助けになればと思っています。

まず、Providerを説明する前に、なぜProviderが必要になるのか?を説明してから、Providerの説明をしていきます。

この記事の注意事項

私もProviderを学んだばかりなので、間違っているところもあるかもしれません。また概念を伝えるために、厳密には違う場合もありますので、あらかじめご了承ください。

間違っている部分がありましたら、ご指摘いただけると幸いです。

そもそもなぜ、Providerが必要になるのか?-アプリの状態管理について

アプリの状態管理でProviderが必要になる。

Providerについて説明する前に少しだけ、アプリの状態管理 について説明を。もう知っているという人は飛ばしてくださいね。

アプリの状態管理と言われるとわかりづらいですが、「状態管理 = 表示内容やデータの変更方法」 だと考えるとわかりやすいかもしれません。

アプリは、ユーザーの操作に応じて、表示内容やデータを変更する必要があります。

変更する方法はいくつかあるのですが、総じてアプリの状態管理と呼ばれています。

状態管理の中には、変更を自動で検知して、変更あった箇所だけを再描画する必要があったりします。

今回説明する Providerは、変更を自動で察知して、アプリ全体に変更があった事を伝える重要な機能を提供してくれる便利なパッケージ です。

入門で勧められる状態管理「setState」

Flutterを始めると、どの入門書もFlutterの状態管理ではProviderを使わない、「SetState」 を使う方法で説明されています。

SetStateを使った状態管理は、

【Stateless】

一度表示したら、変更することがない。 → 原則上書きしない状態

【Stateful】

何度か表示を変更する。 → 上書き可能な状態

これらを組み合わせアプリの変化を管理しますが、表示が変化する箇所は「Stateful」を使います。

変更する場合に、setStateを呼び出して、Statefulの箇所をすべて再描画することで、表示内容を変更していきます。

setStateの方法は、シンプルで分かり易い ので、入門書で採用されているのだと思います。

ただし、欠点がありました。

setStateだと何がダメなの?

シンプルなアプリを作る上では、setStateでもまったく問題にはなりません。

もし、1つでもFlutterでアプリを制作していないなら、setStateの状態管理でアプリを作ればいいと思います。

主に、setStateの問題点は2つ。

  • パフォーマンス
  • メンテナンス性

setStateの場合は、変更する毎に、画面全体を更新してしまいます。__無関係なところも再描画されてるため無駄が多く、パフォーマンスが悪くなりがち。

複雑なアプリになると、地味にパフォーマンスの部分で首を絞められていきます。

またアプリの複雑化することで、setStateの範囲外で更新が必要になったり、メンテナンス上、処理を分割したいことがあったりと、setStateでは管理に不都合 が生じます。

そんな時に、Providerが活躍するわけです。

変更を通知する仕組みを提供するProvider

Providerって何?

前置きが長くなりましたが、いよいよ本題です。

Providerとは表示内容やデータの変更を、アプリ全体に効率的に伝える仕組みを提供するパッケージのことです。

setStateでは限られた範囲にしか変更を通知できず、さらにその範囲全てを無条件に書き換えてしまう効率の悪いものでした。

Providerを使う事で、プログラム内の離れた箇所へダイレクトに変更を通知して、該当箇所のみデータや表示内容を変更するなんてことが、簡単できるようになります。 (Providerがなくてもできるらしいのですが、大変らしいです)

その他に DI(依存性の注入) という機能もあったりします(この記事の後半で。)

Providerは変更を通知してくる役目だけなので、これとは別に状態を管理する方法が必要になるわけです。

BLocとか、MVVMとは何が違う?

Providerについて調べていると、 Bloc とか MVVM とか用語が目に入ってきます。

Providerと何が違うの?と混乱してしまいますが、Providerは、あくまで状態が変化を伝える方法。

BLoCやMVVMは、状態管理する方法や考え方です。

#### 会社の例
会社で例えるなら、業務を、営業、経理、生産など、部署に分けるのがBLoCやMVVMの役目で、各部署への指示の伝達方法がProviderの役目です。

例えば MVVM(モデル・ビュー・ビューモデル) の場合、以下のようにアプリ内で業務を分けます。

モデルの業務:データの保存や読み出しに関連すること

ビューモデルの業務:モデルとビューのやり取りの仲介に関すること

ビューの業務:データの表示、アプリの見た目に関すること

これらの業務間で、データが変更された、データを変更して、データをください、などをやり取りする時にProviderの機能を使って連携するのです。

私もいまだにBLoCは理解できていないので、最初にProviderを使ってアプリを作るなら、MVVMがいいのではないでしょうか?

Providerのメリット・デメリット

Providerを使うメリット

setStateをやめて、Providerを使って変更を通知する仕組みするメリットは主に2つ。

  • パフォーマンスのアップ
  • メンテナンスのしやすさ

Providerは変更箇所を限定できるので、無駄がない分パフォーマンス的にはよくなります。

また表示と処理を分離できるようになるので、後からメンテナンスがしやすくなるメリットもあります。

Providerのデメリット

Providerでは、慣れるまでは複雑なので、オブジェクト思考について理解していないと実装が難しい。

また、しっかりとアプリを設計しないと混沌とする可能性も。

また、この状態管理手法が、進化している分野だったりします。

現在ProviderをGoogleが推奨していますが、今後は使用するパッケージが変わることがあるかもしれません。

Providerってどんな仕組み

頂点から見渡して、それぞれの変更を見る。

アプリ全体の変更を確認するには、高い位置から見てないと気づけません。
なので、Providerで監視するためのWidgetを、Widgetツリーの一番上に置いておく必要があります。

「ChangeNotifierProvider」 というWidgetが、変更を管理するWidgetで、一番最初のメイン関数が呼ばれるタイミングで組み込まれてます。

void main()  {
  runApp(ChangeNotifierProvider(
      create: (_) => TopScreenModel(),
      child: MyApp()));
}

これでによって、アプリ全体の変更を察知くれるわけです。

それぞれの役目

Providerの役割は、大きく分けると3つの役目があります。(ほかにも色々ありますが、抜粋で)

変更を通知する。→ Provider.of<TopScreenModel>(context, listen: false)
変更をあった事を全体に通知する。→ notifyListeners();
変更があったら、その分を変更する。→Consumer

あらかじめ、ChangeNotifierをextendした、TopScreenModelのクラスを作成しておきます。

class TopScreenModel extends ChangeNotifier {
  bool _powerSwitch = false;
  bool get powerSwitch = _powerSwitch;

void onPowerSwitch() {
   _powerSwitch = true
    notifyListeners();
  }
	
}

1.例えばビュー上でボタンを押されると、「Provider.of<TopScreenModel>(context, listen: false).onPowerSwitch()」を呼び出します。

2.onPowerSwitch()内で、notifyListeners()が呼ばれることで、powerSwitch(_powerSwitch)の値が変わったことがアプリ全体に通知されます。

3.Consumer以下のWidgetで、変更があった箇所の値の部分が再描画されます。

Consumer<WebScreenModel>(builder: (context, model, child) {
	return model.powerSwitch == true ? Text("on") : Text("off");
	}

離れている場所でも、通知ができる。

こうすることで、これまでは繋がりのあるWidget間がでしか更新ができませんでしたが、頂点経由で繋がっているなら、 離れたWidgetにも変更を通知 できるようになるのです。

DI(依存性の注入)ってなに?

ProviderではDI(依存性の注入)も機能として備わっています。
なんかヤバそうな名前ですが、少しだけ解説しておきます。

依存性の注入とは

依存性の注入というと、依存度を高めるようなイメージですが、その逆です。実際は、プログラム(クラス)同士の依存度を下げること を意味します。

「依存って何」? ということですが、プログラミングはクラス同士が依存しながら動いてます。

それぞれのクラスを使う場合にインスタンスを作成するのですが、Aクラスのインスタンスを、Bクラス内で作成してしまうと、もしAクラスに変更があった場合に影響が出てしまします。

そこで他のクラスを使う場合は、外部でインスタンスを作って、自分のクラスはもらうだけにすると、独立性が保てるのです。それを実現するのが、DI(依存性の注入)です。

工場での例

工場で例えるなら、工場Aは、工場Bが開発した工作機械を使って、製品を生産しているとします。

この場合、工場Aは工場Bに依存していると言えます。

だだし工場Bの工作機器は、設計図とパーツのみ提供となり、工場Aで組み立てしてから使います。

そのため、工場Aの従業員は組み立て、セッティングしなければなりません。

工場Bの製品変更になってしまうと、工場Aの従業員はセッティングをやり直さないと大変です。

それは困ったということで、工場Aで使う工場Bの工作品、用件を満たせば中身は変わってもいいので、完成した状態で届くようにしたいわけです。

でも頑なに工場Bは完成品を提供してくれません。

そこで完成品を代わりに届けてくれるのが、ProviderのDI(依存性の注入)なのです。

まとめ

いかがでしたか?
自分がProviderを勉強してみて、よく分からなかったところを中心に解説してみました。
今回は私のアウトプットの意味も込めてだったので、わかりづらいところあったかもしれません。

一番は一度Providerで何かアプリを作ってみてください。
1回作ってみると、不思議とすんなりで理解できたりします。

わかりづらい、ここ違うよってところがあったら教えてください。

ツイッターやってます。
よかったら、フォローしてください😁

Discussion