1
1

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 1 year has passed since last update.

Provider (Flutter Roadmap Provider)

Posted at

はじめに

Flutterを網羅的に学習するにあたってRoadmapを使って学習を進めることにしました。

この記事では、Flutter初学者やこれからFlutterを学習し始める方に向けて、Providerについてまとめています。

RoadmapはFlutterだけでなく、他の言語やスキルのロードマップも提供されており、何から学習して良いか分からないと悩んでいる方にとって有用なサイトになっています。
ぜひRoadmapを利用して学習してみてください。

Roadmapとは

簡潔に言えば、Roadmap.shは学習者にとってのガイドブックであり、学習の方向性を提供する学習ロードマップサイトです。

初心者から上級者まで、ステップバイステップでスキルを習得するための情報が提供されています。

学習の進め方が分かりやすく示されているだけでなく、個々の項目に参考資料やリソースへのリンクも提供されているので、学習者は目標を設定し、自分自身のペースで学習を進めることができます。

Provider

FlutterロードマップProviderでは以下の2つのサイトが紹介されています。興味のある方はぜひお読みください。

Providerとは

Provider とは、Flutterアプリケーションで状態管理を行うためのパッケージです。特に ChangeNotifier などの状態オブジェクトをウィジェットツリーに提供するために利用されます。Provider を使用することで、状態変更を検知してUIを更新するといった作業が簡単になります。

Providerのメリット

  • 簡潔なコード構造: UIとデータのバインディングを容易に行うことができるため、状態管理のためのコードが簡潔になります。

  • 依存性の注入: アプリケーションの各部分で使用するデータを、依存性の注入を通じて渡すことができるため、特に大規模なアプリケーションでは、データの一貫性を確保しやすくなります。

  • プロバイダーの統一: ChangeNotifierProvider、FutureProvider、StreamProvider など、異なるタイプのプロバイダーを提供しているため、異なるデータのタイプに対して統一的な手法で状態管理を行えます。

  • 再利用性の向上: Provider を利用することで、ウィジェットツリー内での再利用が容易になります。データの提供や更新に関するロジックをカプセル化し、再利用可能なコンポーネントを構築できます。

Provider.value() Provider.of()

Widget ツリーの中に Provider.value<T>() を挟んでおくことで、それ以下の Widget から値を取得できるようになります。Tには共有したいデータの型を指定します。

Provider.of<T>(context) は Widget ツリー内のどこからでもProviderで提供されたデータにアクセスすることができます。Tには参照したいProviderの型を指定します。

Provider.of(context) は内部では InheritedWidgetinheritFromWidgetOfExactType メソッドを使っているため、 initState より後でしか呼ぶことができません。

まとめると Provider.value() でデータを渡し、 Provider.of() はそのデータを受けとるために使用します。この二つのメソッドを組み合わせることで、Flutterアプリ内で状態を簡単に管理できるようになります。

Provider.value<T>()の他にもProvider<T>()を使用する方法があります。

value と create
Provider(
  create: (_) => UserRepository(),
)

Provider.value(
  value: UserRepository(),
)

どちらを選択するかは使用ケースに依存しますが、既にインスタンスがある場合は .value を使用し、都度新しいインスタンスを生成する必要がある場合は通常のコンストラクタを使用します。

既に生成されたインスタンスを提供するため、.value の方が再利用性が高く、処理が最小限に抑えられます。

context.read() / context.watch()

context.read()context.watch() は、 Providerパッケージで状態を読み込むためのメソッドです。

  • context.read<T>():

    • Providerの現在の値を読み込むだけです。変更通知を購読しないため、状態変更時に自動的に再ビルドされることはありません。
    • 値の変更が関係なく、一度だけデータを取得したい場合に使用します。
    • 変更があってもウィジェットは再構築されません。
  • context.watch<T>():

    • Providerの現在の値を読み込み、同時に変更通知を購読するため、状態が変更されるたびにWidgetを再ビルドします。
    • 変更通知が発生するとウィジェットが再構築されます。
    • context.read<T>()との違いは再ビルドするかしないかです。
    • ウィジェットの再構築をトリガーするときに、関連するウィジェットのビルドをトリガーすることになりますが、 Provider.of() は関連するすべてのウィジェットを再構築するため、 context.watch() の方が最適化された再構築が行えます。

MultiProvider

Provider を複数使いたいときに使用します。

MultiProvider を使用しない場合、個別に Provider を定義する必要があり、以下のようにネストした Provider を定義することで、 MyApp 以下のWidgetツリーから UserRepositoryProductRepository にアクセスできるようになります。

しかし、この方法ではコードが冗長になり、Providerが増えるたびにコードの可読性が低下します。

MultiProvider を使用することで、複数の Provider をまとめて定義することができ、コードはシンプルかつ可読性が高くなるのです。

MultiProviderを使用しなかった場合
Provider(
  create: (_) => UserRepository(),
  child: Provider(
    create: (_) => ProductRepository(), 
    child: MyApp()
  )
)
MultiProviderを使用した場合
MultiProvider(
  providers: [
    Provider(create: (_) => UserRepository()), 
    Provider(create: (_) => ProductRepository()),
  ],
  child: MyApp() 
)

主な Provider

名前 説明
Provider シンプルな値やオブジェクトを提供するための基本的なプロバイダーです。create コンストラクタを使用してインスタンスを生成します
ChangeNotifierProvider ChangeNotifier クラスを使用したプロバイダーで、状態が変更されたときに通知され、UIの再構築をトリガーします
ListenableProvider Listenable クラスを使用したプロバイダーで、変更をリッスンすることができます。Listenable クラスは notifyListeners メソッドを持つクラスです
ProxyProvider 他のプロバイダーの値を基にして新しい値を生成するためのプロバイダーです。変更が検知されたときに、新しい値が計算されます
StreamProvider 非同期処理である Stream の結果を提供するためのプロバイダーです。Stream が新しいデータを生成するたびに通知されます
FutureProvider 非同期処理である Future の結果を提供するためのプロバイダーです。非同期処理が完了すると、その結果が提供されます

Provider と ChangeNotifierProvider の違い

基本、ChangeNotifier を継承したクラスの受け渡しを行う場合は ChangeNotifierProvider を使い、それ以外のデータを受け渡す時は Provider を使用します。

  • Provider: 単にデータのみ受け渡せれば良い時に使用する、データは更新されない。
  • ChangeNotifierProvider: データの受け渡しだけでなくデータの更新も行いたい場合に使用する。

ProxyProvider

ProxyProvider とは、 provider パッケージで提供されているプロバイダーの一種です。このプロバイダーは、他のプロバイダーからの変更を受けて新しい値を生成するとき、例えば、ログイン前とログイン後で表示する記事一覧を変えたい場合などに使用されます。

ProxyProvider を使用すると、新しい値の計算を行う際に他のプロバイダーの値に依存できるため、異なるプロバイダー間でのデータの依存関係を効果的に管理できます。

以下の例では、 ProxyProvider を使用して DependencyProvider の変更に基づきCounterModel を提供しています。初回のみ DependencyProvider の値から CounterModel を作成し、それ以降は前回の結果を再利用します。

"Update Dependency" ボタンを押すと、 DependencyProviderdependencyValue が更新され、これに基づいて CounterModel も再計算されます。

サンプルコード

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class CounterModel {
  int count;

  CounterModel(this.count);
}

class DependencyProvider extends ChangeNotifier {
  int dependencyValue = 42;

  void updateDependencyValue(int newValue) {
    dependencyValue = newValue;
    notifyListeners();
  }
}

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => DependencyProvider()),
        ProxyProvider<DependencyProvider, CounterModel>(
          update: (_, dependency, previous) =>
              previous ?? CounterModel(dependency.dependencyValue),
        ),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counterModel = Provider.of<CounterModel>(context);
    final dependencyProvider = Provider.of<DependencyProvider>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('ProxyProvider Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Counter: ${counterModel.count}'),
            Text('Dependency: ${dependencyProvider.dependencyValue}'),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                counterModel.count += 1;
              },
              child: Text('Increment Counter'),
            ),
            ElevatedButton(
              onPressed: () {
                dependencyProvider.updateDependencyValue(99);
              },
              child: Text('Update Dependency'),
            ),
          ],
        ),
      ),
    );
  }
}

StreamProvider

StreamProvider は、 provider パッケージで提供されているプロバイダーの一つで、主に非同期のデータを扱う際に使用されます。

  • 非同期データの表示: リアルタイムなアップデートがあるデータなどバックエンドからの非同期データを表示する際に使用されます。

  • リアルタイムなUI更新: Stream がデータを提供すると、そのデータが変更されるたびにUIが自動的に更新されます。

  • 非同期データのプロバイダー: Stream からデータを非同期に取得し、それをアプリケーション内の他の部分で利用可能にします。

StreamProviderの主なプロパティ

  • create: Stream を生成するための関数を指定します。この関数は Stream<T> を返す必要があります。

  • initialData: Stream がデータを返さない場合に使用される初期データを指定します。

  • catchError: Stream でエラーが発生した場合に使用されるデータを指定します。

  • updateShouldNotify: 新しいデータが古いデータと異なる場合にリビルドするかどうかを制御するためのコールバック関数です。通常は (previous, next) => true としておくと、新しいデータが来たときにリビルドが行われます。

サンプルコード

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(
    MaterialApp(
      home: StreamProvider<int>(
        create: (_) => counterStream(),
        initialData: 0,
        catchError: (_, error) => 0,
        updateShouldNotify: (prev, next) => true,
        child: const MyApp(),
      ),
    ),
  );
}

Stream<int> counterStream() async* {
  int count = 0;
  while (true) {
    await Future.delayed(const Duration(seconds: 1));
    yield count++;
  }
}

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

  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<int>(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('StreamProvider Example'),
      ),
      body: Center(
        child: Text('Counter: $counter'),
      ),
    );
  }
}

このアプリケーションは、1秒ごとに増加する値をリアルタイムにUIに表示し、StreamProvider を使用して非同期データを効果的に扱う方法を示しています。

  • counterStream 関数では、1秒ごとに値が増加する非同期ストリームを生成します。 async* キーワードを使用して非同期ジェネレーター関数を宣言しています。

  • await Future.delayed(const Duration(seconds: 1)); で1秒待っています。 yield count++; で現在のカウント値を Stream に送信し、カウントを増やしています。

  • StreamProvider は、 Stream から非同期データを提供するためのものです。

  • create パラメータは、非同期データを生成するための関数で、この例では counterStream 関数が1秒ごとに増加するカウンターの値を生成しています。

  • initialData は、 Stream がデータを返さない場合に使用される初期データを指定します。この例では初期値を0に設定しています。

  • catchError は、 Stream でエラーが発生した場合に使用されるデータを指定します。この例ではエラーが発生した場合に0を返すようにしています。

  • MyApp ウィジェットでは、 Provider.of<int>(context) を使用して現在のカウンターの値を取得しています。この値は Text('Counter: $counter') で表示されます。

FutureProvider

FutureProvider は、非同期でデータを生成する Future を提供するためのプロバイダーです。主に非同期の処理が終了した後にその結果をUIに渡す場合に使用されます。

  • 非同期データの提供: FutureProvider は非同期でデータを取得するために使用されます。例えば、リクエスト処理やデータベースクエリなどの非同期操作が完了した後に、その結果をUIに提供する場合に活用されます。

  • ローディングの管理: データがまだ取得されていない場合や、取得中にローディング状態を表示する際に利用されます。

FutureProviderの主なプロパティ

  • create: 非同期処理を行う Future を返す関数を指定します。この関数は Provider が初めて読み込まれたときに呼び出され、その結果が提供されます。

  • initialData: 非同期処理が完了する前に使用される初期データを指定します。非同期処理が完了すると、この初期データは無視されます。

  • catchError: 非同期処理中にエラーが発生した場合に使用されるデータを指定します。これにより、エラーが発生した場合でもUIが正常に表示されるようになります。

  • updateShouldNotify: Provider の値が変更されたときにUIを更新するかどうかを制御します。通常は Future の結果が変化するので、変更を通知するためには (_, __) => true とします。

サンプルコード

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(
    MaterialApp(
      home: FutureProvider<int>(
        create: (_) => fetchData(),
        initialData: 0,
        catchError: (_, error) => 0,
        updateShouldNotify: (_, __) => true,
        child: const MyApp(),
      ),
    ),
  );
}

Future<int> fetchData() async {
  await Future.delayed(const Duration(seconds: 2)); 
  return 42;
}

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

  @override
  Widget build(BuildContext context) {
    final int data = Provider.of<int>(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('FutureProvider Example'),
      ),
      body: Center(
        child: Text('Data from Future: $data'),
      ),
    );
  }
}
  • main 関数:

    • FutureProvider を使用して、非同期データを提供するための MyApp をラップしています。

    • create プロパティは非同期関数 fetchData を指定しています。この関数は2秒後に値 42 を返します。

    • initialData プロパティは非同期処理が完了する前に使用される初期データを指定しますこの例では0を返すように指定しています。

    • catchError プロパティは非同期処理中にエラーが発生した場合に使用されるデータを指定しますここでは0を返すように指定しています。

  • fetchData 関数:

    • fetchData 関数は非同期で2秒待機し、その後に 42 を返す単純な非同期関数です。
  • MyApp クラス:

    • MyApp クラスは FutureProvider から提供された非同期データを受け取り、UIに表示しています。

    • Provider.of<int>(context) を使用して非同期データにアクセスしています。

まとめ

Providerとは

Providerは、Flutterアプリ内で状態を管理するためのライブラリです。特に以下のようなメリットがあります。

  • 簡潔なコード構造が実現できる
  • データの依存性注入がしやすい
  • 統一的な状態管理ができる
  • コンポーネントの再利用性が向上する

ウィジェットツリーに状態を提供することで、UIとデータを簡単にバインドできるのが Provider の強みです。

基本的な使い方

Providerには Provider.value()Provider.of() の2つの主要なメソッドがあります。

Provider.value() でデータを提供し、 Provider.of() でそのデータにアクセスします。この2つのメソッドを組み合わせることで、状態共有が実現できます。

またデータにアクセスするには別の方法も存在します。それがcontext.read()context.watch()です。

データを取得する場合、 context.read() を使うと状態変更時に再ビルドされません。一方 context.watch() を使うと、状態変更時にウィジェットツリーを再ビルドします。

便利な機能

  • MultiProvider を使うと、複数の Provider をまとめて定義できます。これによりコードの可読性が上がります。

  • ProxyProvider は、既存の Provider から値を生成するカスタムProviderです。状態の変換などに利用できます。

  • StreamProviderFutureProvider を使うと、非同期処理の結果を簡単に状態として扱えます。

Providerは状態管理を抽象化し、簡潔で柔軟なコードを実現してくれます。大規模アプリ開発において重宝するライブラリのため、使い方を覚えることで、開発効率が大きく向上するはずです。

参考資料

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?