🥕
overrideWithは、main関数の中以外でも呼べるらしい?
Overview
最近仕事で、RiverpodのoverrideWithのコードをmain関数以外のところで呼ばれているのを見ることがあった...
どうやら本当にできるみたいだ👇
使い方の例は私はよく知らないのですが、Providerを上書きするのが目的ですね。
同様に、ウィジェット ツリー内の任意の場所に他の ProviderScope を挿入して、アプリケーションの一部のみのプロバイダーの動作をオーバーライドすることができます。
final themeProvider = Provider((ref) => MyTheme.light());
void main() {
runApp(
ProviderScope(
child: MaterialApp(
// Home uses the default behavior for all providers.
home: Home(),
routes: {
// Overrides themeProvider for the /gallery route only
'/gallery': (_) => ProviderScope(
overrides: [
themeProvider.overrideWithValue(MyTheme.dark()),
],
),
},
),
),
);
}
summary
色々試してみた。まずは、文字を上書きしてみよう。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final helloProvider = Provider<String>((ref) {
return 'Hello World';
});
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({
super.key,
});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Override Example'),
),
body: Center(
child: ProviderScope(
overrides: [
helloProvider.overrideWith((ref) => 'Providerを上書きした😁'),
],
child: Consumer(
builder: (context, ref, child) {
final message = ref.watch(helloProvider);
return Text(message);
},
),
),
),
);
}
}
ProviderScopeのparentでも上書きができるらしい?
この [ProviderScope] の子孫となる親 [ProviderContainer] を明示的にオーバーライドします。
一般的な使用例は、モーダルがスコープ指定されたプロバイダーにアクセスできるようにすることです。これ以外の場合、モーダルはウィジェット ツリーの別のブランチにあるためアクセスできません。
これは次のようにして実現できます。
ElevatedButton(
onTap: () {
final container = ProviderScope.containerOf(context);
showDialog(
context: context,
builder: (context) {
return ProviderScope(parent: container, child: MyModal());
},
);
},
child: Text('show modal'),
)
The [parent] variable must never change.
公式のソースコードを少し書き換えて、ダイアログとカウンターを組み合わせたプロバイダーを上書きするサンプルを作ってみた。
// Have a counter that is being incremented by the FloatingActionButton
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class CounterNotifier extends Notifier<int> {
build() {
return 0;
}
void increment() {
state++;
}
}
final counterProvider =
NotifierProvider<CounterNotifier, int>(CounterNotifier.new);
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends ConsumerWidget {
const MyApp({super.key});
Widget build(BuildContext context, WidgetRef ref) {
return const MaterialApp(
home: Home(),
);
}
}
class Home extends ConsumerWidget {
const Home({super.key});
Widget build(BuildContext context, WidgetRef ref) {
// ボタンを押すとカウントが表示されるダイアログを表示したい
return Scaffold(
appBar: AppBar(
title: const Text('ProviderScope'),
),
body: Column(
children: [
ElevatedButton(
onPressed: () {
showDialog<void>(
context: context,
builder: (c) {
// ダイアログを ProviderScope ウィジェットでラップし、
// ダイアログが同じプロバイダーにアクセスできるようにするための親コンテナー
// ホーム ウィジェットからアクセスできます。
return ProviderScope(
// parentプロパティを使用して、上書きをすることもできる。
parent: ProviderScope.containerOf(context),
child: const AlertDialog(
content: CounterDisplay(),
),
);
},
);
},
child: const Text('Show Dialog'),
),
],
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
ref.read(counterProvider.notifier).increment();
},
));
}
}
class CounterDisplay extends ConsumerWidget {
const CounterDisplay({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count');
}
}
thoughts
使ってみてわかったことは、プロバイダーを上書きしているだけ。勘違いしていたのは、main関数以外の場所でも呼ぶことができるということ!
今の所上書きしないと困ったのは、Isarを使った時くらいいですね。
コンストラクターの引数を上書きするのに、使ってましたね。
hooks_riverpodを使用したサンプル
これが、Isarで使った上書きをした例です
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:isar/isar.dart';
import 'package:isar_hooks/domain/person.dart';
import 'package:isar_hooks/screen/add_page.dart';
import 'package:isar_hooks/utils/db_service.dart';
import 'package:path_provider/path_provider.dart';
void main() async {
// Isarの初期化
WidgetsFlutterBinding.ensureInitialized();
// アプリのドキュメントディレクトリを取得
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[PersonSchema],
directory: dir.path,
);
runApp(
ProviderScope(
// overrides:は、Providerの値を上書きするためのものです。
// overrideWithValueは、上書きする値を指定するためのものです。
overrides: [
isarProvider.overrideWithValue(isar),
],
child: MyApp(isar: isar),
),
);
}
class MyApp extends StatelessWidget {
final Isar isar;
MyApp({required this.isar});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Memo App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: AddPage(isar: isar),
);
}
Discussion