删除这个操作比较简单,我们只需要实现之前预留的删除动态方法 removeWithId
即可。这个方法首先向后端请求删除接口,删除成功后将元素从列表数据_dynamics
移除即可。
void removeWithId(String id) async {
var response = await DynamicService.delete(id);
if (response?.statusCode == 200) {
_dynamics.removeWhere((element) => element.id == id);
notifyListeners();
} else {
EasyLoading.showError(response?.statusMessage ?? '删除失败');
}
}
复制代码
这里我们用到了Dart 数组的 removeWhere
方法来删除 id
匹配的元素。捎带讲一下 Dart 的数组操作,Dart提供了丰富的数组操作方法,方法定义在List<E>
类中,常见的有:
generate
:使用指定的长度产生固定的数组,非常适用于 Mock 数据。forEach
:按元素展开,分布处理每一个元素。map
:将元素展开转换为另一个可迭代对象。add
,insert
,remove
,removeXX
:数据的添加,插入、删除指定元素和按XX条件删除元素。indexWhere
:按回调函数的条件超找满足条件的元素的位置。fisrtWhere
:找到符合条件的第一个元素,如果找不到会返回符合第二个条件的首个元素,第二个参数如果没设置也没找到会抛异常。retainWhere
:只保留符合条件的元素。sort
:按给定的回调比较函数排序。- 其他请参考
List<E>
类中数组操作方法的定义。
动态详情
动态详情的改造有点麻烦,这其中有两个原因:
- 动态详情并不是列表页面的子组件,也就没法直接和列表共享状态管理,意味着状态管理需要从更高层级定义。
- 动态详情需要请求网络数据后才能够显示,而且还需要更新阅读数。这就意味着动态详情存在着时序操作,这种操作
StatelessWidget
满足不了,因此需要使用StatefulWidget
,在对应的State
类的生命周期完成相应操作。
这个情况有点类似我们之前的购物车应用(参考:Flutter 入门与实战(四十):以购物车为例初探状态管理),商品列表页和购物车页共享了部分数据,但是并不存在上下级关系。之前购物车的例子我们是将状态定义在了 main.dart
的 runApp
方法中,使得购物车的状态处于了最顶级组件中。目前看来我们的动态状态管理也需要定义在 main.dart
的 runApp
方法里。可是已经有了一个状态管理组件了,这个时候怎么办?
这个时候 Provider
的 MultiProvider
就派上用场了!MultiProvider
专门为了解决多状态共存的情况而设计,用法如下:
MultiProvider({
Key? key,
providers: List<SingleChildWidget>,
Widget? child,
TransitionBuilder? builder
})
复制代码
providers
:一组Provider
对象,用于下级组件依赖多个状态的情况。child
:依赖状态管理的下级子组件。builder
:用于直接获取状态数据的语法糖。
我们改造一下 runApp
方法的调用:
runApp(MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => CartModel()),
ChangeNotifierProvider(create: (context) => DynamicModel()),
],
child: MyApp(),
));
复制代码
这样在顶级组件 MyApp
上就有了两个状态了,这些状态可以给所有下级的组件使用。然后来改造动态详情代码 DynamicDetailPage
类。上面讲过,这个类还需要保留为 StatefulWidget
,只是我们不再使用 setState
方法更新界面了。
在 initState
的方法中我们需要请求详情数据,从而保证只请求一次数据。请求成功后状态管理会自动刷新界面。同时请求成功后我们还需要更新浏览数量。这个我们使用了Future
的 then
方法,在获取动态成功后(方法返回 true
)才更新浏览数量。请求详情数据和更新浏览数都属于业务代码,我们放入到 DynamicModel
里,同时我们在 DynamicModel
增加了一个_currentDynamic
用于详情页或后续的编辑页面。
Future<bool> getDynamic(String id) async {
EasyLoading.showInfo('加载中...', maskType: EasyLoadingMaskType.black);
if (_currentDynamic?.id != id) {
_currentDynamic = null;
}
var response = await DynamicService.get(id);
if (response != null && response.statusCode == 200) {
_currentDynamic = DynamicEntity.fromJson(response.data);
notifyListeners();
EasyLoading.dismiss();
return true;
} else {
EasyLoading.showInfo(response.statusMessage);
return false;
}
}
void updateViewCount(String id) async {
var response = await DynamicService.updateViewCount(id);
if (response != null && response.statusCode == 200) {
_currentDynamic.viewCount = response.data['viewCount'];
// 如果元素在列表中,则更新
int currentIndex =
dynamics.indexWhere((element) => element.id == _currentDynamic.id);
if (currentIndex != -1) {
_dynamics[currentIndex] = _currentDynamic;
}
notifyListeners();
}
}
复制代码
详情页面就比较简单了,我们在initState
调用 DynamicModel
的getDynamic
方法获取详情,如果成功更新界面,并且更新浏览数,这里我们只贴出部分代码:
class DynamicDetailPage extends StatefulWidget {
final String id;
DynamicDetailPage(this.id, {Key key}) : super(key: key);
_DynamicDetailState createState() => _DynamicDetailState();
}
class _DynamicDetailState extends State<DynamicDetailPage> {
@override
void initState() {
super.initState();
context.read<DynamicModel>().getDynamic(widget.id).then((success) {
if (success) {
context.read<DynamicModel>().updateViewCount(widget.id);
}
});
}
@override
Widget build(BuildContext context) {
DynamicEntity currentDynamic = context.watch<DynamicModel>().currentDynamic;
return Scaffold(
appBar: AppBar(
title: Text('动态详情'),
brightness: Brightness.dark,
),
body: currentDynamic == null
? Center(
child: Text('请稍候...'),
)
: _getDetailWidget(currentDynamic),
);
}
//..
}
复制代码
改造完,整个代码也缩减了几十行,业务上也更加清晰了。