Riverpod状态管理-各Provider的区别

Chris Scoot
14 min readMay 4, 2024

Provider

最基础的Provider,提供以下功能:

  • 可以监听、读取其他任意数量、类型的Provider
  • 初始化的计算结果(build方法),并缓存起来,共享其他人使用(被其他Provider读取、监听),直到所依赖的provider有更新,才会重新计算结果(再次调用build方法),并缓存最新值。
  • 持有的状态无法被外部主动修改。
final completedTodosProvider = Provider<List<Todo>>((ref) {
final todos = ref.watch(todosProvider);///通过ref可以监听或读取,其他任意数量的provider

// we return only the completed todos
return todos.where((todo) => todo.isCompleted).toList();
});
///UI监听方式
Consumer(builder: (context, ref, child) {
final completedTodos = ref.watch(completedTodosProvider);
// TODO show the todos using a ListView/GridView/.../* SKIP */
return Container();
});

FutureProvider

相当于最基础的Provider + 异步处理

final configProvider = FutureProvider<Configuration>((ref) async {
final content = json.decode(
await rootBundle.loadString('assets/configurations.json'),
) as Map<String, Object?>;

return Configuration.fromJson(content);
});

///UI监听方式
Widget build(BuildContext context, WidgetRef ref) {
AsyncValue<Configuration> config = ref.watch(configProvider);

return switch (config) {
AsyncData(:final value) => Text(value.host),
AsyncError(:final error) => Text('Error: $error'),
_ => const CircularProgressIndicator(),
};
}

StreamProvider

与FutureProvider类似,只不过返回的是Streams,而不是Future

final chatProvider = StreamProvider<List<String>>((ref) async* {
// Connect to an API using sockets, and decode the output
final socket = await Socket.connect('my-api', 4242);
ref.onDispose(socket.close);

var allMessages = const <String>[];
await for (final message in socket.map(utf8.decode)) {
// A new message has been received. Let's add it to the list of all messages.
allMessages = [...allMessages, message];
yield allMessages;
}
});
///UI监听方式
Widget build(BuildContext context, WidgetRef ref) {
final liveChats = ref.watch(chatProvider);

// Like FutureProvider, it is possible to handle loading/error states using AsyncValue.when
return switch (liveChats) {
// Display all the messages in a scrollable list view.
AsyncData(:final value) => ListView.builder(
// Show messages from bottom to top
reverse: true,
itemCount: value.length,
itemBuilder: (context, index) {
final message = value[index];
return Text(message);
},
),
AsyncError(:final error) => Text(error.toString()),
_ => const CircularProgressIndicator(),
};
}

(Async)NotifierProvider

相当于最基础的Provider + 支持外部修改其缓存state

  • 【优点】将所有的修改state的业务逻辑代码集中放置在Notifier类(如下的TodosNotifier)中,有利于代码维护
  • NotifierProvider和AsyncNotifierProvider的区别主要是前者build方法是同步,后者是异步
  • 对于数据(如下的Todo),必须是imutable的,如果是mutable的需要自己手动调用notifyListeners
class Todo {
Todo(this.description, this.isCompleted);
final bool isCompleted;
final String description;
}

class TodosNotifier extends Notifier<List<Todo>> {
@override
List<Todo> build() {
return [];
}

void addTodo(Todo todo) {
state = [...state, todo];///修改缓存,不需要调用notifyListeners
}
// TODO add other methods, such as "removeTodo", ...
}

final todosProvider = NotifierProvider<TodosNotifier, List<Todo>>(() {
return TodosNotifier();
});

StateProvider

一种简化版本的NotifierProvider,可以直接调用update方法更新state,而不用写notifier类进行修改。

  • 相比于最基础的Provider(不能外部修改其缓存state),StateProvider提供一种简单方法让外部可以修改state
  • 相比于NotifierProvider,只能使用update方法进行简单修改,如果有很复杂逻辑还得是NotifierProvider
final pageIndexProvider = StateProvider<int>((ref) => 0);

class PreviousButton extends ConsumerWidget {
const PreviousButton({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
// if not on first page, the previous button is active
final canGoToPreviousPage = ref.watch(pageIndexProvider) != 0;

void goToPreviousPage() {
ref.read(pageIndexProvider.notifier).update((state) => state - 1);
}

return ElevatedButton(
onPressed: canGoToPreviousPage ? goToPreviousPage : null,
child: const Text('previous'),
);
}
}

ChangeNotifierProvider

【不鼓励使用,推荐使用NotifierProvider】与NotifierProvider的主要区别是,当数据(state状态)变化后,必须手动通知更新。一般只有如下情况使用:

  • 从package:provider组件过度过来的
  • 当你的数据是mutable的时候,这时候必须手动通知更新
class Todo {///mutable数据(属性不是final类型)
Todo({
required this.id,
required this.description,
required this.completed,
});

String id;
String description;
bool completed;
}

class TodosNotifier extends ChangeNotifier {
final todos = <Todo>[];

// Let's allow the UI to add todos.
void addTodo(Todo todo) {
todos.add(todo);
notifyListeners();///必须手动通知更新
}

// Let's allow removing todos
void removeTodo(String todoId) {
todos.remove(todos.firstWhere((element) => element.id == todoId));
notifyListeners();///必须手动通知更新
}

// Let's mark a todo as completed
void toggle(String todoId) {
final todo = todos.firstWhere((todo) => todo.id == todoId);
todo.completed = !todo.completed;
notifyListeners();///必须手动通知更新
}
}

// Finally, we are using ChangeNotifierProvider to allow the UI to interact with
// our TodosNotifier class.
final todosProvider = ChangeNotifierProvider<TodosNotifier>((ref) {
return TodosNotifier();
});

StateNotifierProvider

【推荐使用NotifierProvider!以后可能被移除。】

// The state of our StateNotifier should be immutable.
// We could also use packages like Freezed to help with the implementation.
@immutable
class Todo {
const Todo({required this.id, required this.description, required this.completed});

// All properties should be `final` on our class.
final String id;
final String description;
final bool completed;

// Since Todo is immutable, we implement a method that allows cloning the
// Todo with slightly different content.
Todo copyWith({String? id, String? description, bool? completed}) {
return Todo(
id: id ?? this.id,
description: description ?? this.description,
completed: completed ?? this.completed,
);
}
}

// The StateNotifier class that will be passed to our StateNotifierProvider.
// This class should not expose state outside of its "state" property, which means
// no public getters/properties!
// The public methods on this class will be what allow the UI to modify the state.
class TodosNotifier extends StateNotifier<List<Todo>> {
// We initialize the list of todos to an empty list
TodosNotifier(): super([]);

// Let's allow the UI to add todos.
void addTodo(Todo todo) {
// Since our state is immutable, we are not allowed to do `state.add(todo)`.
// Instead, we should create a new list of todos which contains the previous
// items and the new one.
// Using Dart's spread operator here is helpful!
state = [...state, todo];
// No need to call "notifyListeners" or anything similar. Calling "state ="
// will automatically rebuild the UI when necessary.
}

// Let's allow removing todos
void removeTodo(String todoId) {
// Again, our state is immutable. So we're making a new list instead of
// changing the existing list.
state = [
for (final todo in state)
if (todo.id != todoId) todo,
];
}

// Let's mark a todo as completed
void toggle(String todoId) {
state = [
for (final todo in state)
// we're marking only the matching todo as completed
if (todo.id == todoId)
// Once more, since our state is immutable, we need to make a copy
// of the todo. We're using our `copyWith` method implemented before
// to help with that.
todo.copyWith(completed: !todo.completed)
else
// other todos are not modified
todo,
];
}
}

// Finally, we are using StateNotifierProvider to allow the UI to interact with
// our TodosNotifier class.
final todosProvider = StateNotifierProvider<TodosNotifier, List<Todo>>((ref) {
return TodosNotifier();
});

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Chris Scoot
Chris Scoot

Written by Chris Scoot

擅长C语言、C++、iOS开发(Objective-C\Swift)、Flutter开发(Dart语言)、GO语言、Python等,拥抱新技术,热爱AI领域,对OpenCV、Pytorch\Tensorflow充满热情。

No responses yet

What are your thoughts?