在Flutter中,状态管理是一个核心主题,因为它直接影响到应用的性能和开发的复杂性。
常见的状态管理(更细界面)
-
使用
setState()方法:setState()是在StatefulWidget中用来更新界面的基本方式。当你调用setState()时,Flutter会标记这个widget为"dirty"(脏的),并且在下一帧时重建它。适用于局部状态管理,即状态仅在单个Widget内部使用。
-
使用
ValueNotifier和ValueListenableBuilder:ValueNotifier是一个简单的状态管理工具,它可以保存任何类型的值。当值改变时,所有使用ValueListenableBuilder的widget都会更新。
-
使用
StreamBuilder:- 如果你的数据是基于流(Stream)的,可以使用
StreamBuilder。它会监听流的变化,并且根据流的最新数据重建界面。
- 如果你的数据是基于流(Stream)的,可以使用
-
使用
FutureBuilder:- 对于基于Future的异步操作,可以使用
FutureBuilder来构建UI。它会在Future完成时更新UI。
- 对于基于Future的异步操作,可以使用
-
使用更复杂的状态管理解决方案:
如Provider、Bloc、MobX等,这些都是为了更大规模的应用提供的更高级的状态管理框架。
-
1、 Provider: 这是最受推荐的一种轻量级状态管理方式。Provider不仅可以帮助你管理跨多个屏幕或组件的共享状态,还提供了一个相对简单的实现方式。
ChangeNotifierProvider是Provider包中的一个组件,用于将ChangeNotifier类与Flutter UI绑定。 -
Bloc/Cubit: Bloc是一种较为复杂的状态管理库,基于Reactive Programming(响应式编程),主要用于更复杂的应用中。它通过分离业务逻辑和视图层来帮助实现更可维护的代码结构。
-
Riverpod: 由Provider的作者开发,可以看作是Provider的改进版。Riverpod提供了更灵活的依赖重写机制,更好的性能和更少的限制。
-
Redux: 受到Web开发中JavaScript的Redux库的启发,Flutter的Redux库用于管理应用的所有状态。它使用单一的全局状态存储,通过派发actions和reducers来更新状态。
-
GetX: 是一个轻量级且功能丰富的库,用于状态管理、依赖注入和路由管理。GetX易于学习,并且提供了非常简洁的API来处理状态管理。
-
MobX: 另一个响应式状态管理库,通过actions、reactions和observables的概念来管理状态。MobX的核心思想是自动追踪状态变化,并触发相应的响应。
说说 ChangeNotifierProvider
使用ChangeNotifierProvider的步骤
- 定义ChangeNotifier类:首先,你需要定义一个类,继承自
ChangeNotifier。这个类将持有你想要在应用中共享的数据,并在数据发生变化时通知其监听者。 - 创建Provider:在应用的顶层或需要的层级,使用
ChangeNotifierProvider来创建你的ChangeNotifier类的实例,并使其下的widget树能够访问到这个实例。 - 使用数据:在widget中,你可以使用
Provider.of<T>(context)或Consumer<T>widget来读取和响应数据变化。
示例代码
下面是一个简单的计数器应用示例,展示了如何使用ChangeNotifierProvider来实现状态管理。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// 步骤1: 定义一个继承自ChangeNotifier的Counter类
class Counter extends ChangeNotifier {
int _count = 0; // 私有变量,存储计数值
int get count => _count; // 公开的getter方法,用于读取计数值
void increment() {
_count++; // 计数值加1
notifyListeners(); // 通知监听器数据已更新,触发界面重建
}
}
void main() {
runApp(ProviderApp());
}
// 步骤2: 使用ChangeNotifierProvider在应用顶层提供Counter实例
class ProviderApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => Counter(),
child: MaterialApp(
home: HomePage(),
),
);
}
}
// 主页面
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 步骤3: 使用Provider.of来访问Counter实例
final counter = Provider.of<Counter>(context, listen: true);
return Scaffold(
appBar: AppBar(title: Text("Provider Example")),
body: Center(
child: Text('Count: ${counter.count}', style: TextStyle(fontSize: 24)),
),
floatingActionButton: FloatingActionButton(
onPressed: counter.increment, // 调用Counter中的increment方法
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
解析
- Counter类:这个类继承自
ChangeNotifier,管理计数器的状态。当调用increment()方法时,计数器的值会增加,并通过notifyListeners()通知依赖这个状态的widget进行重建。 - ProviderApp类:这是一个包装了
ChangeNotifierProvider的widget,它在应用的顶层提供了Counter类的实例。所有子widget都可以访问到这个实例。 - HomePage类:这个类使用
Provider.of<Counter>(context)来获取Counter实例,并在界面中显示计数值。FloatingActionButton被点击时会触发increment()方法,从而更新界面上显示的计数值。
说说 MultiProvider
在实际开发中,一个应用通常需要管理多个状态对象。为了避免多个 Provider 嵌套过深导致代码臃肿,Flutter 提供了 MultiProvider,它可以在同一个 widget 树上一次性注入多个状态管理对象,从而使代码结构更加清晰易维护。
MultiProvider 的主要作用
- 简化代码结构:使用
MultiProvider可以避免嵌套多个ChangeNotifierProvider或其他 Provider,从而减少层级,使得代码更易读。 - 集中管理:将所有需要注入的状态对象集中在一个地方进行管理,便于维护和扩展。
- 灵活组合:除了
ChangeNotifierProvider外,MultiProvider同时支持其他 Provider 类型,如StreamProvider、FutureProvider等。
使用步骤
- 定义多个状态管理类
如需要管理计数和消息等状态,可以分别定义继承自ChangeNotifier的类。 - 使用 MultiProvider 注入状态
在应用的顶层或某个页面中,使用MultiProvider一次性注入多个 Provider。 - 在子 widget 中访问状态
使用Provider.of<T>(context)、Consumer<T>或其他方式,在 widget 树中随时获取并响应状态变化。
示例代码
下面是一个简单的示例,展示如何使用 MultiProvider 同时管理两个状态:一个计数器和一个消息状态。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// 定义第一个状态管理类:计数器
class Counter extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
// 定义第二个状态管理类:消息
class MessageNotifier extends ChangeNotifier {
String _message = "Hello";
String get message => _message;
void updateMessage(String newMessage) {
_message = newMessage;
notifyListeners();
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// 提供计数器状态
ChangeNotifierProvider(create: (_) => Counter()),
// 提供消息状态
ChangeNotifierProvider(create: (_) => MessageNotifier()),
],
child: MaterialApp(
home: HomeScreen(),
),
);
}
}
// 示例页面,分别读取计数器和消息状态
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counter = Provider.of<Counter>(context);
final messageNotifier = Provider.of<MessageNotifier>(context);
return Scaffold(
appBar: AppBar(
title: Text("MultiProvider 示例"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('计数: ${counter.count}', style: TextStyle(fontSize: 24)),
SizedBox(height: 16),
Text('消息: ${messageNotifier.message}', style: TextStyle(fontSize: 24)),
],
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
heroTag: 'btn1',
onPressed: counter.increment,
tooltip: '增加计数',
child: Icon(Icons.add),
),
SizedBox(height: 16),
FloatingActionButton(
heroTag: 'btn2',
onPressed: () => messageNotifier.updateMessage("Hello Provider"),
tooltip: '更新消息',
child: Icon(Icons.message),
),
],
),
);
}
}
.
以上代码,可以换一种写法
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// 定义第一个状态管理类:计数器
class CounterNotifier extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
// 定义第二个状态管理类:消息
class MessageNotifier extends ChangeNotifier {
String _message = "Hello Provider";
String get message => _message;
void updateMessage(String newMsg) {
_message = newMsg;
notifyListeners();
}
}
// 这里提前创建好状态对象
final counterNotifier = CounterNotifier();
final messageNotifier = MessageNotifier();
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// 使用 .value 提供已存在的 counterNotifier 实例
ChangeNotifierProvider<CounterNotifier>.value(
value: counterNotifier,
),
// 使用 .value 提供已存在的 messageNotifier 实例
ChangeNotifierProvider<MessageNotifier>.value(
value: messageNotifier,
),
],
child: MaterialApp(
home: HomeScreen(),
),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 获取状态对象
final counter = Provider.of<CounterNotifier>(context);
final message = Provider.of<MessageNotifier>(context);
return Scaffold(
appBar: AppBar(
title: Text("MultiProvider with .value 示例"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
"Counter: ${counter.count}",
style: TextStyle(fontSize: 24),
),
SizedBox(height: 20),
Text(
"Message: ${message.message}",
style: TextStyle(fontSize: 24),
),
],
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
heroTag: "counterBtn",
onPressed: counter.increment,
tooltip: "Increment",
child: Icon(Icons.add),
),
SizedBox(height: 20),
FloatingActionButton(
heroTag: "messageBtn",
onPressed: () => message.updateMessage("Updated Message"),
tooltip: "Update Message",
child: Icon(Icons.message),
),
],
),
);
}
}
提前创建的状态对象 在示例中,我们先通过全局变量创建了 counterNotifier 和 messageNotifier 两个状态对象,这样在整个应用中这两个对象都是唯一且共享的。
使用 .value 构造函数 在 MultiProvider 中,我们采用 ChangeNotifierProvider.value 来传入已经存在的实例。这样做能确保当 widget 重建时,不会意外销毁或重新创建状态对象。 集中管理多个状态
通过 MultiProvider,你可以在同一个入口处一次性注入多个状态对象,这样在子 widget 中就能方便地通过 Provider.of(context) 或 Consumer 来获取对应的状态,而不必在多个层级中嵌套 Provider。
这种方式适合用于状态对象已经由外部创建、管理其生命周期的场景,可以更灵活地共享状态并避免不必要的重复创建。
解析
-
状态类定义:
Counter类管理计数器状态,每次调用increment()时,计数值增加并调用notifyListeners()通知依赖者更新。MessageNotifier类则管理一个简单的文本消息,通过updateMessage()方法更新消息内容。
-
MultiProvider 的使用:
在MyApp的build方法中,我们使用MultiProvider同时注入了Counter和MessageNotifier。这样一来,应用中所有子 widget 都可以通过Provider.of<T>(context)或Consumer<T>来获取这两个状态。 -
子 widget 中的数据消费:
在HomeScreen中,我们分别获取了Counter和MessageNotifier的实例,并根据其状态展示计数和消息,同时提供两个按钮分别更新这两个状态。
通过这种方式,你可以方便地管理和组合多个状态对象,使得应用状态管理更加模块化和可维护。 MultiProvider 在构建大型应用时尤为实用,因为它能帮助你避免嵌套过深,同时提高代码的可读性和扩展性。
说说 StreamController
在Flutter中,StreamController 是一种管理事件流和数据流的核心工具,特别适用于实现应用的响应式编程。它提供了一种方式来异步处理数据,并且在应用的不同部分之间传递信息,这在动态UI更新和复杂事件处理中非常有用。
基本概念
StreamController 允许你创建一个Stream,你可以向这个Stream发送数据,而Stream的订阅者则可以接收并处理这些数据。StreamController 通常包括以下几个主要部分:
- Stream:一个数据流,订阅者可以监听这个流中的数据。
- Sink:用于向流中添加数据的接口。
- builder: 这是一个函数,它根据
stream的最新数据动态构建widget。它接收的参数是BuildContext和AsyncSnapshot,后者持有最新的数据和流的状态。
创建和使用 StreamController
在Flutter中,你通常会这样使用StreamController:
-
创建 StreamController:你可以创建一个标准的或广播类型的
StreamController。标准的StreamController只允许单个监听者,而广播类型的可以有多个监听者。StreamController<int> controller = StreamController<int>();如果需要多个订阅者,你应该使用广播类型:
StreamController<int> controller = StreamController<int>.broadcast(); -
向 Stream 添加数据:使用
Sink接口,通常是controller.sink.add(value)方法来向流中添加数据。controller.sink.add(10); -
监听数据:使用
Stream.listen方法来订阅数据流,并定义处理数据的方式。controller.stream.listen((data) { print("Received: $data"); }); -
关闭 StreamController:当完成流的使用后,你应该关闭
StreamController以释放资源。controller.close();
来点代码吧
常见的一个例子
import 'dart:async';
class CounterController {
final StreamController<int> _controller = StreamController<int>();
int _counter = 0;
// 外部订阅的 Stream
Stream<int> get stream => _controller.stream;
// 递增计数器并添加数据到流
void incrementCounter() {
_counter++;
_controller.sink.add(_counter); // 发送新的计数值到流
}
// 关闭 StreamController 防止内存泄漏
void dispose() {
_controller.close();
}
}
import 'package:flutter/material.dart';
import 'counter_controller.dart'; // 确保正确导入控制器
class CounterPage extends StatefulWidget {
@override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
final CounterController _controller = CounterController();
@override
void dispose() {
_controller.dispose(); // 适当时机关闭控制器
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Stream Counter Example')),
body: Center(
child: StreamBuilder<int>(
stream: _controller.stream,
initialData: 0,
builder: (context, snapshot) {
// 检查是否有数据
if (snapshot.hasData) {
return Text('Button tapped ${snapshot.data} times',
style: TextStyle(fontSize: 20));
}
return CircularProgressIndicator();
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: _controller.incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
如果是一个stream的变化,page多个widget都需要更新?
假设我们想在页面上添加两个文本:一个显示点击次数,另一个显示点击次数的平方值。这两个文本都会响应同一个计数器值的变化。
如果多个widget需要根据同一个Stream的变化进行更新,你可以使用多个StreamBuilder来监听相同的Stream。这种方式非常灵活,因为你可以在不同的部分UI中分别处理数据,从而让每个部分根据Stream的当前状态独立更新。这样做的好处是,它允许你为每个StreamBuilder提供不同的builder逻辑,可以根据具体的UI需求定制展示方式。
import 'dart:async';
class CounterController {
final StreamController<int> _controller = StreamController<int>();
int _counter = 0;
// 外部订阅的 Stream
Stream<int> get stream => _controller.stream;
// 递增计数器并添加数据到流
void incrementCounter() {
_counter++;
_controller.sink.add(_counter); // 发送新的计数值到流
}
// 关闭 StreamController 防止内存泄漏
void dispose() {
_controller.close();
}
}
import 'package:flutter/material.dart';
import 'counter_controller.dart'; // 确保正确导入控制器
class CounterPage extends StatefulWidget {
@override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
final CounterController _controller = CounterController();
@override
void dispose() {
_controller.dispose(); // 适当时机关闭控制器
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Stream Counter Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
StreamBuilder<int>(
stream: _controller.stream,
initialData: 0,
builder: (context, snapshot) {
// 检查是否有数据
if (snapshot.hasData) {
return Text('Button tapped ${snapshot.data} times',
style: TextStyle(fontSize: 20));
}
return CircularProgressIndicator();
},
),
StreamBuilder<int>(
stream: _controller.stream,
initialData: 0,
builder: (context, snapshot) {
// 检查是否有数据
if (snapshot.hasData) {
int squared = snapshot.data! * snapshot.data!;
return Text('Square of taps: $squared',
style: TextStyle(fontSize: 20));
}
return CircularProgressIndicator();
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _controller.incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
我们使用了两个StreamBuilder来响应同一个Stream的数据更新,每个StreamBuilder处理数据的方式不同。这种方法让你能够构建更复杂的响应式UI,而且每个组件可以根据相同数据的不同视图进行更新。