本文就是一个简单的学习Riverpod的Demo,然后做了些常用列表界面的通用性封装,如果大家发现有什么错误多谢指正哈👻
提前准备
使用了jsonplaceholder mock 数据
数据model
import 'package:freezed_annotation/freezed_annotation.dart';
//http://jsonplaceholder.typicode.com/photos?_start=101&_limit=5
part 'product_model.freezed.dart';
part 'product_model.g.dart';
@freezed
class ProductModel with _$ProductModel {
factory ProductModel({
int? albumId,
int? id,
String? title,
String? url,
String? thumbnailUrl,
}) = _ProductModel;
factory ProductModel.fromJson(Map<String, dynamic> json) =>
_$ProductModelFromJson(json);
}
界面状态
参考一般的列表界面,状态可以分为:
loading
:第一次进来loading状态ready
:加载成功,显示数据empty
:数据为空,显示空界面error
:错误界面,一般会带有刷新按钮
抽离State
import 'package:freezed_annotation/freezed_annotation.dart';
part 'list_view_state.freezed.dart';
@freezed
class ListViewState<T> with _$ListViewState<T> {
const factory ListViewState.loading() = Loading;
const factory ListViewState.empty() = Empty;
const factory ListViewState.ready(T data) = Ready<T>;
const factory ListViewState.error({required String error}) = Error;
}
列表Notifier封装
ListViewStateNotifier
列表加载的通用封装
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'list_view_state.dart';
typedef ToastFunction = void Function(String error, {Error? e});
/*
1. 首次进来显示loadding,第二次就系那是refresh的动画
2. 错误也是首次进来发生错误才显示,第二次就toast
*/
class ListViewStateNotifier<T> extends StateNotifier<ListViewState<List<T>>> {
ListViewStateNotifier({
required this.fetchItems,
this.pageSize = 10,
this.pageIndex = 1,
}) : super(const ListViewState.loading()) {
init();
}
final EasyRefreshController _refreshController = EasyRefreshController();
EasyRefreshController get refreshController => _refreshController;
final Future<List<T>> Function(int pageIndex, int pageSize) fetchItems;
int _page = 0;
int pageSize = 10;
int pageIndex = 1;
List<T> _items = [];
void init() {
firstLoadPage();
}
Future<void> firstLoadPage() async {
_page = pageIndex;
try {
final List<T> list = await fetchItems(_page, pageSize);
if (list.isEmpty) {
state = const ListViewState.empty();
} else {
_items = list;
state = ListViewState.ready(list);
}
_refreshController.finishRefresh(
success: true, noMore: list.length < pageSize);
_refreshController.resetLoadState();
_page += 1;
} catch (e) {
state = ListViewState.error(error: e.toString());
}
}
Future<void> refreshData({ToastFunction? fnToast}) async {
_page = pageIndex;
try {
final List<T> list = await fetchItems(_page, pageSize);
if (list.isEmpty) {
state = const ListViewState.empty();
} else {
_items = list;
// state = ListViewState.ready(list);
state = const ListViewState.error(error: "发生错误啦");
}
_refreshController.finishRefresh(
success: true, noMore: list.length < pageSize);
_refreshController.resetLoadState();
_page += 1;
} catch (e) {
_refreshController.finishRefresh(success: false);
if (fnToast != null) {
fnToast(e.toString());
}
// state = ListViewState.error(error: e.toString());
}
}
Future<void> loadMore({ToastFunction? fnToast}) async {
try {
final List<T> list = await fetchItems(_page, pageSize);
if (list.isNotEmpty) {
_items.addAll(list);
state = ListViewState.ready(_items);
}
_refreshController.finishLoad(
success: true, noMore: list.length < pageSize);
_page += 1;
} catch (e) {
_refreshController.finishLoad(success: false);
// state = ListViewState.error(error: e.toString());
if (fnToast != null) {
fnToast(e.toString());
}
}
}
@override
void dispose() {
_refreshController.dispose();
super.dispose();
}
}
下拉刷新和上拉加载更多发生错误的时候需要toast,这块暴露了fnToast
一个参数方便界面处理
Provider
productListProvider做界面的逻辑处理
final productListProvider = StateNotifierProvider<
ListViewStateNotifier<ProductModel>,
ListViewState<List<ProductModel>>>((ref) {
final api = ref.watch(apiService);
return ListViewStateNotifier<ProductModel>(
pageIndex: 1,
pageSize: 10,
fetchItems: (int pageIndex, int pageSize) {
return api.fetchProducts(pageIndex: pageIndex, pageSize: pageSize);
});
});
UI显示
UI显示会非常清爽,再也不用各种setState了
Consumer(
builder: (context, ref, child) {
final state = ref.watch(productListProvider);
return state.when(
empty: () => const Text(
'empty',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.normal,
fontSize: 14,
),
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
ready: (data) => _buildContent(context, ref, data),
error: (String error) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(error),
),
TextButton(
child: const Text("重试下"),
onPressed: () {
ref.refresh(productListProvider);
},
),
],
),
);
},
loading: () => const Center(child: Text("loading..")),
);
},
)
总结
使用了Riverpod,感觉就是逻辑和UI更独立了,UI处理UI的体验,逻辑处理数据,非常灵活,个人感觉哈。