Wiki_Flutter_Provider - inoueshinichi/Wiki_Flutter GitHub Wiki

Providerによる状態管理

  • Google標準
  • Googleもメインでこの方法を利用している

Providerの適用スコープ

void main() {
  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
  );
}

Providerへのアクセス

Method Content
ref.watch(SomeProvider) 値の変化を監視. 値が変化したら対象のWidgetをリビルド.
ref.read(SomeProvider) 値を監視せず, その時のProviderにアクセスして値を参照. リビルドはしない.
ref.listen(SomeProvider, (prev, next) { ... }) 値の変化を監視をする. リビルドはしない.
ref.refresh(SomeProvider) プロバイダーを即時初期状態に戻して, 再評価する.
ref.invalidate(SomeProvider) 次のフレームでプロバイダーの状態を戻して, 再評価する.
// ref.watch
Consumer(
  builder: (BuildContext context, WidgetRef ref, Widget child) {
    ref.watch(SomeProvider);
  },
);

// ref.read
Consumer(
  builder: (BuildContext context, WidgetRef ref, Widget child) {
    ref.read(SomeProvider);
  },
);

// ref.listen
Consumer(
  builder: (BuildContext context, WidgetRef ref, Widget child) {
    ref.listen(SomeProvider, (pref, next) {
      ...
    },
  ),
);

// ref.refresh
Consumer(
  builder: (BuildContext context, WidgetRef ref, Widget child) {
    return ElevatedButton(
      onPress: () => ref.refresh(SomeProvider),
      child: const Text('Reset provider'),
    );
  },
);

// ref.invalidate
Consumer(
  builder: (BuildContext context, WidgetRef ref, Widget child) {
    onPress: () => ref.refresh(SomeProvider),
    child: const Text('Invalidate provider'),
  },
);

Providerアクセス時のリビルド条件を制御する

  • SomeProvider.select((Repository) => monitoredValue)
// 状態変数
class Repository {
  final int? bar;
  final int? baz;
  
  const Repository({this.bar, this.baz});
}

// Provider
final repoProvider = SomeProvider<Repository>((Ref ref) => Repository(10, 15));

~~~

Consumer(
  builder: (BuildContext context, WidgetRef ref, Widget child) {
    // 状態変数(Repository.bar)の変化のみを監視する
    var bar = ref.watch(repoProvider.select((repo) => repo.bar));
    return Text($bar);
  },
); 

Providerから他のProviderへのアクセス

  • worldProviderの値が変化するとMonitorWidgetがリビルドする.
helloProvider ---> worldProvider
     ↑        間接      ↑
     ----------------- MonitorWidget
e.g.

final worldProvider = SomeProvider((Ref ref) => 0);
final helloProvider = SomeProvider((Ref ref) => ref.watch(worldProvider) * 2);

class MonitorWidget extends StatelessWidget {
  const MonitorWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer(
      builder: (BuildContext context, WidgetRef ref, Widget child) {
        return Text(ref.watch(helloProvider);
      },
    );
  }
}

Providerの種類

  • アクセス権限などの制御関連やStream専用のProviderなど様々な種類が存在する.
Provider Note
Provider 利用者に値の操作はさせず, 参照のみ許可
StateProvider 利用者に値の操作と参照の両方を許可
StateNotifierProvider 利用者に値の操作と参照の両方を許可. ただし, 操作方法は, 公開しているメソッドを使用する.
ChangeNotifierProvider ミュータブルな状態を扱うStateNotifierProvider.
FutureProvider 非同期なFuture処理を扱うProvider. 値の参照のみ許可
StreamProvider 非同期なStream処理を扱うProvider. 値の参照のみ許可

プロバイダに引数を渡す方法 (family)

e.g.
final fooProvider = StateProvider<int>((Ref ref) => 0);
final barProvider = StateProvider.family<int, int>((Ref ref, int v) {
  return ref.watch(fooProvider) * v;
});

~~~

Consumer(
  builder: (BuildContext context, WidgetRef ref, Widget child) {
    return Text(ref.watch(barProvider(100)));
  }
);

プロバイダに別名をつける

  • リファクタリングに役立つ
final fooProvider = StateProvider.family<int,int>((Ref ref, int value) => value);
final barProvider = StateProvider<int>((Ref ref) => throw UnimplementedError());

ProviderScope(
  overrides: [
    barProvider.overrideWith((Ref ref) => ref.watch(fooProvider(100)),
  ],
  child: Consumer(
    builder: (BuildContext context, WidgetRef ref, Widget child) {
      return Text(ref.watch(barProvider);
    },
  ),
);

プロバイダーを上書きする

  • リファクタリングに役立つ
final fooProvider = StateProvider<int>((Ref ref) => 0);

ProviderScope(
  overrides: [
    fooProvider.overrideWith((Ref ref) => 100),
  ],
  child: Consumer(
    builder: (BuildContext context WidgetRef ref, Widget child) {
      return Text(ref.watch(fooProvider);
    },
  ),
);

autoDispose

  • 利用されなくなったプロバイダーをGCで自動削除する
  • また, onDispose()で削除するときにコールバックを設定できる.

e.g.

  • HTTPリクエストが完了する前にプロバイダーが削除されそうな際, リクエストをキャンセルする用途など.
final fooProvider = FutureProvider.autoDispose<Foo>((Ref ref) async {
  // HTTPリクエストをキャンセルするのに使用するトークン
  final token = CancelToken();
  
  // プロバイダーが削除されたら呼ばれるコールバックでHTTPリクエストをキャンセルする.
  ref.autoDispose(() => token.cancel());

  var res = await Dio().get('https://domain/your/path/to', cancelToken: token);

  return Foo.fromResponse(res);
});

Provider

  • 利用者に値の操作はさせず, 参照のみ許可する.
final fooProvider = Provider((Ref ref) => "foo");

class FooWidget extends StatelessWidget {
  const FooWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer(
      builder: (BuildContext context, Ref ref, Widget child) {
        return Text(ref.watch(fooProvider));
      },
    );
  }
}

StateProvider

  • 値の操作と参照を許可.
final fooProvider = StateProvider((Ref ref) => 0);

class FooWidget extends StatelessWidget {
  const FooWidget({super.key});
  
  @override
  Widget build(BuildContext context) {
    ~~~
    Consumer(
      builder: (BuildContext context, WidgetRef ref, Widget child) {
        return Text(ref.watch(fooProvider).state);
      },
    );
    
    ~~~
    Consumer(
      builder: (BuildContext context, WidgetRef ref, Widget child) {
        return ElevatedButton(
          onPressed: () => ref.read(fooProvider.notifier).state++;
        );
      }
    );
  }
}

StateNotifierProvider

  • 利用者に値の操作と参照を許可する. ただし, 操作方法は, 公開しているメソッド経由のみ.
  • StateNotifierProviderで管理する状態はイミュータブルが前提.
@immutable // 不変
class ImmutableFoo {
  final int value;
  const Foo(this.value);
}

class FooController extends StateNotifier<ImmutableFoo> {
  FooController() : super(const ImmutableFoo(0));
  
  void increment() {
    state = ImmutableFoo(state.value + 1);
  }
}

~~~
final fooProvider = StateNotifierProvider<FooController, ImmutableFoo>((Ref ref) => FooController());

class FooWidget extends StatelessWidget {
  const FooWidget({super.key});
  
  @override
  Widget build(BuildContext context) {
    ~~~
    Consumer(
      builder: (BuildContext context, WidgetRef ref, Widget child) => Text(ref.watch(fooProvider).value),
    );
    
    ~~~
    Consumer(
      builder: (BuildContext context, WidgetRef ref, Widget child) {
        ref.read(fooProvider.notifier/*State*/).increment();
      }.
    );
  }
}

ChangeNotifierProvider

  • ミュータブルな状態を扱うStateNotifierProvider
  • イミュータブルな状態を扱う場合はStateNotifierProviderを使う
class MutableFoo {
  int value; // ※ final修飾子がついていない!
  MutableFoo(this.value);
}

class FooController extends ChangeNotifier {
  MutableFoo foo;
  FooController() : foo = MutableFoo(0);
  void increment() {
    foo.value++;
    notifyListeners(); // notifyListeners()を呼び出して変更を通知する.
  }
}

~~~
final fooProvider = ChangeNotifierProvider((Ref ref) => FooController());

class FooWidget extends StatelessWidget {
  const FooWidget({super.key});
  
  @override
  Widget build(BuildContext context) {
    ~~~
    Consumer(
      builder: (BuildContext context, WidgetRef ref, Widget child) => Text(ref.watch(fooProvider).value),
    );
    
    ~~~
    Consumer(
      builder: (BuildContext context, WidgetRef ref, Widget child) {
        ref.read(fooProvider.notifier/*State*/).increment();
      }.
    );
  }
}

FutureProvider

  • 非同期処理を行う. 値の参照のみ.
  • 非同期処理の進行状態をloading, error, dataの3種類で管理する
final fooProvider = FutureProvider<String>((Ref ref) async {
  await Future.delayed(const Duration(seconds: 3));
  return "foo after 3 [s]";
});

~~~
class FooWidget extends StatelessWidget {
  const FooWidget({super.key});
  
  @override
  Widget build(BuildContext context) {
    return Consumer(
      builder: (BuildContext context, WidgetRef ref, Widget child) {
        return ref.watch(fooProvider)
          .switch(
            data: (foo) => Text(foo),
            error: (e, st) => const Text('Future Error'),
            loading: => const Text('Loading...'),
          );
      },
    );
  }
}

StreamProvider

  • Stream(非同期なイベント)を扱うプロバイダ
  • FutureProviderのStream版
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stream_map/model/user.dart';

final fooProvider = StreamProvider((Ref ref) {
  // Firebase with firestore
  final collection = FirebaseFirestore.instance.collection('users');
  
  // データ (Map型)を取得
  final stream = collection.snapshots().map(
    // CollectionのデータからUserクラスを生成
    (elem) => elem.docs.map((e) => User.fromJson(e.data())).toList();

  //final controller = StreamController();
  //controller.add("Hello");
  return controller.stream; // Stream<User?>
});

~~~
class FooWidget extends StatelessWidget {
  const FooWidget({super.key});
  
  @override
  Widget build(BuildContext context) {
    return Consumer(
      builder: (BuildContext context, WidgetRef ref, Widget child) {
        final asyncValue /* AsyncValue<User?> */ = ref.watch(streamProvider);
        return asyncValue.switch(
          data: (user) => user != null ? MyUserPage() : SignupPage(),
          loading: () => const CircularProgressIndicator(),
          error: (err, st) => Text("Error: ${err}, StackTrace: ${st}"),
        );
      },
    );
  }
}

Providerへのアクセス時に使用するクラス

Widget Content
StatelessWidget -
Consumer Providerアクセスの最小単位
ConsumerWidget StatelessWidget + Consumer. Widget全体がリビルド対象になるので注意.
StatefulWidget -
State -
ConsumerState -

Stateless系

StatelessWidget & Consumer

class SomeStatelessWidget extends StatelessWidget {
  const SomeStatelessWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer(
      builder: (BuildContext context, WidgetRef ref, Widget child) {
        return Text(ref.watch(SomeProvider));
      },
    ); // Consumer
  }
}

ConsumerWidget

  • Widget全体がリビルド対象になるので注意.
  • 一部だけリビルドで再描画する場合は, 個別にConsumerを使う方針がベスト.
class SomeConsumerWidget extends ConsumerWidget {
  const SomeConsumerWidget({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    ~~~
    ref.watch(SomeProvider); // 値の変化を監視. 値が変化したらSomeConsumerWidget全体をリビルド
    ~~~
    ref.read(SomeProvider); // 値を監視せず, その時点のProviderが提供する状態にアクセスする. リビルドしない.
  }
}

Stateful系

StatefulWidget & State

class SomeStatefulWidget extends StatefulWidget {
  const SomeStatefulWidget({super.key});
  
  @override
  State<SomeStatefulWidget> createState() => SomeState;
}

class SomeState extends State<SomeStatefulWidget> {
  
  @override
  void initState() {
    super.initState();
  }
  
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
  }
  
  @override
  void didUpdateWidget(SomeStatefulWidget oldWidget) {
    super.didUpdateWidget(oldWidget)
  }
  
  @override
  void dispose() {
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Consumer(
      builder: (BuildContext context, WidgetRef ref, Widget child) {
        return Text(ref.watch(SomeProvider);
      },
    );
  }
}

ConsumerState

  • 元々のStateクラスによる状態管理
  • Providerによる状態管理
  • 上記の2つの状態管理手法が混ざる
class SomeStatefulWidget extends StatefulWidget {
  const SomeStatefulWidget({super.key});
  
  @override
  ConsumerState<SomeStatefulWidget> createState() => SomeConsumerState;
}

class SomeConsumerState extends State<SomeStatefulWidget> {
  const SomeConsumerState({super.key});
  
  @override
  void initState() {
    super.initState();
    ref.read(SomeProvider);
  }
  
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
  }
  
  @override
  void didUpdateWidget(SomeStatefulWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
  }

  @override
  void dispose() {
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context, WidgetRef ref, Widget child) {
    return Text(ref.watch(SomeProvider);
  }
}
⚠️ **GitHub.com Fallback** ⚠️