為什麼用riverpod
ProviderScope
void main() {
// wrap the entire app with a ProviderScope so that widgets
// will be able to read providers
runApp(ProviderScope(
child: MyApp(),
));
}
試著創一個Provider
// provider that returns a string value
final helloWorldProvider = Provider<String>((ref) {
return 'Hello world';
});
- 使用ConsumerWidget
final helloWorldProvider = Provider<String>((_) => 'Hello world');
// 1. widget class now extends [ConsumerWidget]
class HelloWorldWidget extends ConsumerWidget {
@override
// 2. build method has an extra [WidgetRef] argument
Widget build(BuildContext context, WidgetRef ref) {
// 3. use ref.watch() to get the value of the provider
final helloWorld = ref.watch(helloWorldProvider);
return Text(helloWorld);
}
}
- 使用Consumer
final helloWorldProvider = Provider<String>((_) => 'Hello world');
class HelloWorldWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 1. Add a Consumer
return Consumer(
// 2. specify the builder and obtain a WidgetRef
builder: (BuildContext context, WidgetRef ref, _) {
// 3. use ref.watch() to get the value of the provider
final helloWorld = ref.watch(helloWorldProvider);
return Text(helloWorld);
},
);
}
}
各種Provider(Riverpod 2.0)
- Provider
- FutureProvider
- StreamProvider
- NotifierProvider
- AsyncNotifierProvider
Provider
跟剛剛的一樣
// declare the provider
final dateFormatterProvider = Provider<DateFormat>((ref) {
return DateFormat.MMMEd();
});
class SomeWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// retrieve the formatter
final formatter = ref.watch(dateFormatterProvider);
// use it
return Text(formatter.format(DateTime.now()));
}
}
FutureProvider
final weatherFutureProvider = FutureProvider.autoDispose<Weather>((ref) {
// get repository from the provider below
final weatherRepository = ref.watch(weatherRepositoryProvider);
// call method that returns a Future<Weather>
return weatherRepository.getWeather(city: 'London');
});
// example weather repository provider
final weatherRepositoryProvider = Provider<WeatherRepository>((ref) {
return WeatherRepository(); // declared elsewhere
});
when build:
Widget build(BuildContext context, WidgetRef ref) {
// watch the FutureProvider and get an AsyncValue<Weather>
final weatherAsync = ref.watch(weatherFutureProvider);
// use pattern matching to map the state to the UI
return weatherAsync.when(
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
data: (weather) => Text(weather.toString()),
);
}
StreamProvider
final authStateChangesProvider = StreamProvider.autoDispose<User?>((ref) {
// get FirebaseAuth from the provider below
final firebaseAuth = ref.watch(firebaseAuthProvider);
// call a method that returns a Stream<User?>
return firebaseAuth.authStateChanges();
});
// provider to access the FirebaseAuth instance
final firebaseAuthProvider = Provider<FirebaseAuth>((ref) {
return FirebaseAuth.instance;
});
when build:
Widget build(BuildContext context, WidgetRef ref) {
// watch the StreamProvider and get an AsyncValue<User?>
final authStateAsync = ref.watch(authStateChangesProvider);
// use pattern matching to map the state to the UI
return authStateAsync.when(
data: (user) => user != null ? HomePage() : SignInPage(),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
);
}
NotifierProvider
// counter.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
class Counter extends Notifier<int> {
@override
int build() {
return 0;
}
void increment() {
state++;
}
}
final counterProvider = NotifierProvider<Counter, int>(() {
return Counter();
});
when build:
import 'counter.dart';
class CounterWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 1. watch the provider and rebuild when the value changes
final counter = ref.watch(counterProvider);
return ElevatedButton(
// 2. use the value
child: Text('Value: $counter'),
// 3. change the state inside a button callback
onPressed: () => ref.read(counterProvider.notifier).state++,
or
onPressed: () => ref.read(counterProvider.notifier).increment(),
);
}
}
AsyncNotifierProvider
import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 2. extend [AsyncNotifier]
class AuthController extends AsyncNotifier<void> {
// 3. override the [build] method to return a [FutureOr]
@override
FutureOr<void> build() {
// 4. return a value (or do nothing if the return type is void)
}
Future<void> signInAnonymously() async {
// 5. read the repository using ref
final authRepository = ref.read(authRepositoryProvider);
// 6. set the loading state
state = const AsyncLoading();
// 7. sign in and update the state (data or error)
state = await AsyncValue.guard(authRepository.signInAnonymously);
}
}
final authControllerProvider = AsyncNotifierProvider<AuthController, void>(() {
return AuthController();
});
when build:
import 'counter.dart';
class CounterWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// watch the StreamProvider and get an AsyncValue<User?>
final authStateAsync = ref.watch(authControllerProvider);
// use pattern matching to map the state to the UI
return authStateAsync.when(
data: (user) => user != null ? HomePage() : SignInPage(),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
);
}
}
何時使用watch, read
基本上
- 在[build]裡ref.watch(provider)用來監視provider的state,並在state改變時rebuild。
- ref.read(provider)用來讀取provider的state一次(可以用於例如initState等)
- 其他還有listen, refresh等等
final counterStateProvider = StateProvider<int>((_) => 0);
class CounterWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// if we use a StateProvider<T>, the type of the previous and current
// values is StateController<T>
ref.listen<StateController<int>>(counterStateProvider.state, (previous, current) {
// note: this callback executes when the provider value changes,
// not when the build method is called
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Value is ${current.state}')),
);
});
// watch the provider and rebuild when the value changes
final counter = ref.watch(counterStateProvider);
return ElevatedButton(
// use the value
child: Text('Value: $counter'),
// change the state inside a button callback
onPressed: () => ref.read(counterStateProvider.notifier).state++,
);
}
}
Dependency Overrides with Riverpod
final sharedPreferences = await SharedPreferences.getInstance();
final sharedPreferencesProvider = Provider<SharedPreferences>((ref) {
throw UnimplementedError();
});
// asynchronous initialization can be performed in the main method
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final sharedPreferences = await SharedPreferences.getInstance();
runApp(ProviderScope(
overrides: [
// override the previous value with the new object
sharedPreferencesProvider.overrideWithValue(sharedPreferences),
],
child: MyApp(),
));
}
根據需求也能使用多個ProviderScope組成網狀結構
class ProductList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (_, index) => ProductItem(index: index),
);
}
}
// product_item.dart
class ProductItem extends StatelessWidget {
const ProductItem({super.key, required this.index});
final int index;
@override
Widget build(BuildContext context) {
// do something with the index
}
}
// 1. Declare a Provider
final currentProductIndex = Provider<int>((_) => throw UnimplementedError());
class ProductList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(itemBuilder: (context, index) {
// 2. Add a parent ProviderScope
return ProviderScope(
overrides: [
// 3. Add a dependency override on the index
currentProductIndex.overrideWithValue(index),
],
// 4. return a **const** ProductItem with no constructor arguments
child: const ProductItem(),
);
});
}
}
class ProductItem extends ConsumerWidget {
const ProductItem({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// 5. Access the index via WidgetRef
final index = ref.watch(currentProductIndex);
// do something with the index
}
}
上面的例子因為ProductItem是const widget。所以即使ListView 被rebuild,只要index沒變ProductItem也不會rebuild,因此能夠有更好的效能。
Combine providers with Riverpod
class SettingsRepository {
const SettingsRepository(this.sharedPreferences);
final SharedPreferences sharedPreferences;
// synchronous read
bool onboardingComplete() {
return sharedPreferences.getBool('onboardingComplete') ?? false;
}
// asynchronous write
Future<void> setOnboardingComplete(bool complete) {
return sharedPreferences.setBool('onboardingComplete', complete);
}
}
final settingsRepositoryProvider = Provider<SettingsRepository>((ref) {
// watch another provider to obtain a dependency
final sharedPreferences = ref.watch(sharedPreferencesProvider);
// pass it as an argument to the object we need to return
return SettingsRepository(sharedPreferences);
});
Passing Ref as an argument
class SettingsRepository {
const SettingsRepository(this.ref);
final Ref ref;
// synchronous read
bool onboardingComplete() {
final sharedPreferences = ref.read(sharedPreferencesProvider);
return sharedPreferences.getBool('onboardingComplete') ?? false;
}
// asynchronous write
Future<void> setOnboardingComplete(bool complete) {
final sharedPreferences = ref.read(sharedPreferencesProvider);
return sharedPreferences.setBool('onboardingComplete', complete);
}
}
final settingsRepositoryProvider = Provider<SettingsRepository>((ref) {
return SettingsRepository(ref);
});