# 状态管理方案-ScopedModel
# 核心概念
ScopedModel是由3个类组成:
Model
Model是一个保存数据和与数据相关的业务逻辑的类,它是以Listenable的形式实现的,并且可以通知(
notifyListeners()
)任何可能有兴趣知道变化的人。ScopedModel
ScopedModel是一个Widget,类似于一个Provider,它 "持有 "Model,并允许:
- 通过
ScopedModel.of<Model>(context)
调用来检索Model。 - 当请求时,将上下文注册为底层InheritedWidget的依赖关系。
ScopedModel基于AnimatedBuilder (opens new window),它监听Model发送的通知,然后重建一个InheritedWidget (opens new window),而InheritedWidget又会请求重建所有的依赖关系。
- 通过
ScopedModelDescendant
ScopedModelDescendant是一个Widget,它对Model的变化做出反应;当Model通知发生变化时,它会重建。
# 计数器应用
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:scoped_model_example/child.dart';
// 1.创建ScopedModel -> state: _count -> increment
class CountModel extends Model {
int _count = 10;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
void decrement() {
_count--;
notifyListeners();
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
return ScopedModel<CountModel>(
model: CountModel(),
// 2.ScopedModelDescendant -> builder -> model
child: ScopedModelDescendant<CountModel>(
builder: (context, child, model) => Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'${model.count}',
style: Theme.of(context).textTheme.headline4,
),
// ignore: prefer_const_constructors
ChildPage(),
],
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
// 操作model
onPressed: model.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
FloatingActionButton(
onPressed: model.decrement,
tooltip: 'Decrement',
child: const Icon(Icons.remove),
),
],
),
),
),
);
}
}
Tips: 当回调函数的形参与调用的函数的形参一致的时候,可以使用省略的写法:
onPressed: () { return model.increment(); }
等价于:
onPressed: () => model.increment()
等价于:
onPressed: model.increment
在子页面中的使用model中的state:
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:scoped_model_example/main.dart';
class ChildPage extends StatelessWidget {
const ChildPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
// ScopedModel.of -> 取state,操作model
int count = ScopedModel.of<CountModel>(context).count;
return Container(
child: Text(
count.toString(),
),
);
}
}
# 封装getModel方法
import 'package:scoped_model/scoped_model.dart';
T getModel<T extends Model>(context) {
return ScopedModel.of<T>(context, rebuildOnChange: true);
}
注意:Dart是跟TypeScript一样的有类型的语言,
<>
在这里代表着类型入参。T代表着泛型,可以类比着TypeScript中的泛型学习。
使用:
修改child.dart
文件:
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:scoped_model_example/main.dart';
import 'package:scoped_model_example/utils/get_model.dart';
class ChildPage extends StatelessWidget {
const ChildPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
// 方法二:通过rebuildOnChange来设置获取最新的model中的数据
// ScopedModel.of<CountModel>(context, rebuildOnChange: true) -> GetModel<T>(context)
int count = getModel<CountModel>(context).count;
// int count =
// ScopedModel.of<CountModel>(context, rebuildOnChange: true).count;
return Container(
child: Text(
count.toString(),
),
);
}
}
# 全局状态共享
目标:
- 学习页面跳转,父子页面跳转;
- 学习scoped_model的单例方案,借助get_it实现;
- 学习get_it包的作用及常见的API;
- 实现跨页面的状态共享;
目前scoped_model,只能做到父子状态共享,无法实现跨页面的状态共享,大家可以自行尝试。
scoped_model如何实现页面间状态共享?
步骤:
- 创建一个base_view.dart的类,封装scoped_model;
- 使用get_it,并创建一个单例;
- 使用BaseView来使用model;
创建lib/setup_get_it.dart
文件:
import 'package:get_it/get_it.dart';
import 'package:scoped_model_example/scoped_model/count_model.dart';
final getIt = GetIt.instance;
void setupGetIt() {
getIt.registerSingleton<CountModel>(CountModel());
// todo
// getIt.registerLazySingleton<CountModel>(() => CountModel());
}
区别:
getIt.registerSingleton<CountModel>(CountModel());
初始化即注册;getIt.registerLazySingleton<CountModel>(() => CountModel());
第一次使用即注册;
然后,在main.dart
中进行调用:
import 'package:scoped_model_example/setup_get_it.dart';
void main() {
setupGetIt();
runApp(const MyApp());
}
创建lib/views/base_view.dart
文件:
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:scoped_model_example/setup_get_it.dart';
class BaseView<T extends Model> extends StatelessWidget {
final Widget Function(BuildContext context, Widget? child, T model) builder;
const BaseView(this.builder, {Key? key}) : super(key: key);
Widget build(BuildContext context) {
return ScopedModel<T>(
model: getIt<T>(),
child: ScopedModelDescendant<T>(
builder: builder,
),
);
}
}
创建子页面lib/second.dart
:
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:scoped_model_example/scoped_model/count_model.dart';
import 'package:scoped_model_example/setup_get_it.dart';
import 'package:scoped_model_example/views/base_view.dart';
class Second extends StatefulWidget {
Second({Key? key}) : super(key: key);
_SecondState createState() => _SecondState();
}
class _SecondState extends State<Second> {
Widget build(BuildContext context) {
// 在这里我跟我特scoped_model
// 思考:这里为什么要传CountModel? 答:如果不写,那么BaseView不知道去使用哪个Model!
return BaseView<CountModel>(
(context, child, model) => Scaffold(
appBar: AppBar(
title: const Text('second page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'${model.count}',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
heroTag: 'btn1',
onPressed: model.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
FloatingActionButton(
heroTag: 'btn2',
onPressed: model.decrement,
tooltip: 'Decrement',
child: const Icon(Icons.remove),
),
],
),
),
);
}
}
首页,组件封装与状态共享:
封装CounterModel:
import 'package:scoped_model/scoped_model.dart';
class CountModel extends Model {
int _count = 10;
int get count => _count;
void increment() {
_count++;
// 必须要调用,否则页面不更新
notifyListeners();
}
void decrement() {
_count--;
// 必须要调用,否则页面不更新
notifyListeners();
}
}
修改首页,使用BaseView:
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:scoped_model_example/child.dart';
import 'package:scoped_model_example/scoped_model/count_model.dart';
import 'package:scoped_model_example/second.dart';
import 'package:scoped_model_example/setup_get_it.dart';
import 'package:scoped_model_example/views/base_view.dart';
// 1.创建ScopedModel -> state: _count -> increment
// 2.ScopedModelDescendant -> builder -> model
// ScopedModel.of -> 取state,操作model
// 1.抽离Model的逻辑
// 2.共享所有的Model状态
// 3.封装一个BaseView的组件 -> 减少书写 ScopedModelDescendant
void main() {
setupGetIt();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
return BaseView<CountModel>(newMethod);
}
Widget newMethod(context, child, model) => Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'${model.count}',
style: Theme.of(context).textTheme.headline4,
),
// 方法一:配置ignore: prefer_const_constructors去除警告的
const ChildPage(),
ElevatedButton(
onPressed: () {
// 跳转到Second页面
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => Second(),
));
},
child: const Text('跳转Second'))
],
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
// 多个FloatingActionButton,需要设置heroTag
heroTag: 'btn1',
onPressed: model.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
FloatingActionButton(
// 多个FloatingActionButton,需要设置heroTag
heroTag: 'btn2',
onPressed: model.decrement,
tooltip: 'Decrement',
child: const Icon(Icons.remove),
),
],
),
);
}
PS: 由于scoped_model的逻辑清晰且简单,待办清单的扩展就当成是作业,自行尝试完成。