对 InheritedWidget 组件的上层封装,使其更易用,更易复用。地址→ provider
一.引入Provider
在 pubspec.yaml 文件下新增 provider(注意空格问题)
dependencies:
provider: ^6.0.2
二. 创建MVVM架构文件
MVVM架构文件可以分为四个:
- Model 数据模型
- View 界面
- ViewModel model和view的数据绑定
- Repository 网络访问
1. ViewModel 的基类
- 页面状态的枚举
enum ViewState {
idle, //空闲的(执行完毕)
error, //出错了
empty, //返回空值
busy, //繁忙的
}
- 错误的枚举类型
///页面错误类型
enum StateErrorType {
defaultError,
networkTimeoutError, //网络错误
unauthorizedError, //授权错误
responseException, //响应异常
}
- BaseViewModel 的封装
///基础的ViewModel
class BaseViewModel extends ChangeNotifier {
///页面状态
ViewState _state;
///是否被销毁
bool _disposed = false;
///页面错误详细信息
ViewStateError? _viewStateError;
BaseViewModel({ViewState? state}) : _state = state ?? ViewState.busy;
ViewState get viewState => _state;
ViewStateError? get viewStateError => _viewStateError;
set viewState(ViewState state) {
_viewStateError = null;
_state = state;
notifyListeners();
}
//判断是否是忙碌状态
bool isBusy() => _state == ViewState.busy;
//判断是否是获取到的是空
bool isEmpty() => _state == ViewState.empty;
//判断是否是出错
bool isError() => _state == ViewState.error;
//判断是否加载完成
bool isIdly() => _state == ViewState.idle;
//设置空闲状态
void setIdle() => viewState = ViewState.idle;
//设置为空状态
void setEmpty() => viewState = ViewState.empty;
//设置出错状态(主要是由Dio 接口返回的DioErrorType)
void setError(e, stackTrace, {String? message}) {
StateErrorType errorType =
FormatUtil.dioErrorFormat(e).errorType ?? StateErrorType.defaultError;
message = FormatUtil.dioErrorFormat(e).message ?? "出错鸟~~~";
debugPrint(" errorType: $errorType, errorMsg: $message, errorInfo: ${e.toString()}");
viewState = ViewState.error;
_viewStateError = ViewStateError(
errorType: errorType, errorMsg: message, errorInfo: e.toString());
}
//设置繁忙状态
void setBusy() => viewState = ViewState.busy;
@override
void notifyListeners() {
if (!_disposed) {
super.notifyListeners();
}
}
@override
void dispose() {
_disposed = true;
super.dispose();
}
}
- 带列表的BaseViewModel
// T为Model的泛型 M是Model中的List的类型
abstract class BaseRefreshListViewModel<T, M> extends BaseViewModel {
// 设定当前页面角标
int pageIndex = 1;
// 设置页面大小
int pageSize = SystemConfig.pageSize;
//获取到的Model
late T model;
Model中的list
List<M> lists = [];
//当前是否正在加载
bool isLoading = false;
//滚动控制器 用来实现上拉加载
ScrollController controller = ScrollController();
//添加监听
addScrollListener() {
controller.addListener(() {
double fis =
controller.position.maxScrollExtent - controller.position.pixels;
//当前没有加载 且上拉一定距离并当列表高度不满屏幕高度时不执行加载更多
if (fis < 300 && !isLoading && controller.position.maxScrollExtent != 0) {
loadMore(loadMore: true);
}
});
}
scrollDispose() {
controller.dispose();
}
initData() async {
setBusy();
await loadMore(loadMore: false);
}
loadMore({bool loadMore = false}) async {
if (isLoading) {
return;
}
isLoading = true;
try {
//获取当前model
model=(await loadData(loadMore: loadMore)) as T;
//获取当前List
List<M> data = getList();
if (data.isEmpty) {
if (loadMore) {
ToastUtil().showBottomToast("没有更多数据了");
} else {
lists.clear();
setEmpty();
}
} else {
if (loadMore) {
lists .addAll(data);
pageIndex++;
} else {
lists.clear();
lists = List.of(data);
}
isLoading = false;
setIdle();
}
} catch (e,s) {
if (!loadMore) {
lists.clear();
setError(e, s);
}
ToastUtil().showCenterToast(e.toString());
}
}
Future<T>? loadData({loadMore = false});
List<M> getList();
}
2. View 的基类
class ProviderWidget<T extends BaseViewModel> extends StatefulWidget {
// ChangeNotifierProvider需要的model
final T model;
// Consumer需要的builder
final ValueWidgetBuilder<T> builder;
final Function(T model) onModelReady;
//Consumer需要的child 这里chil是指不需要notify的组件
final Widget? child;
final bool autoDispose;
const ProviderWidget(
{Key? key,
required this.model,
required this.onModelReady,
required this.builder,
this.child,
this.autoDispose = false})
: super(key: key);
@override
_ProviderWidgetState createState() => _ProviderWidgetState<T>();
}
class _ProviderWidgetState<T extends BaseViewModel>
extends State<ProviderWidget<T>> {
late final T model;
@override
void initState() {
model = widget.model;
widget.onModelReady(model);
super.initState();
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => model,
child: Consumer(
builder: widget.builder,
child: widget.child,
),
);
}
@override
void dispose() {
super.dispose();
}
}
3.model的定义
class UserModel {
///昵称
String? nikeName;
///性别
int? sex;
///年龄
int? age;
///真实姓名
String? realName;
///邮箱
String? email;
///手机
String? mobile;
///头像
String? imageUrl;
///用户token
String? userToken;
///学校姓名
String? school;
///uid
String? uid;
///签名
String? signature;
///过期时间
int? expirationDate;
//构造方法
UserModel({
this.nikeName,
this.sex,
this.age,
this.realName,
this.email,
this.mobile,
this.imageUrl,
this.userToken,
this.school,
this.uid,
this.signature,
this.expirationDate,
});
//将Model转成Json 的方法
Map<String, dynamic> toJson() {
Map<String, dynamic> data = {};
data["realName"] = realName;
data["nikeName"] = nikeName;
data["sex"] = sex;
data["age"] = age;
data["email"] = email;
data["mobile"] = mobile;
data["imageUrl"] = imageUrl;
data["userToken"] = userToken;
data["school"] = school;
data["uid"] = uid;
data["signature"] = signature;
data["expirationDate"] = expirationDate;
return data;
}
//将Json转成Model的方法
UserModel.fromJson(Map<String, dynamic> json) {
realName = json["realName"];
nikeName = json["nikeName"];
sex = json["sex"];
age = json["age"];
email = json["email"];
mobile = json["imageUrl"];
email = json["imageUrl"];
userToken = json["userToken"];
school = json["school"];
uid = json["uid"];
signature = json["signature"];
expirationDate = json["expirationDate"];
}
}
4. Repository
我一般写的时候会把网络请求单独放在一个文件中,这样到时候修改接口也不至于动到其他地方...
//定义一个User的网络访问文件,存储登录 登录需要用的方法
class UserRepository {
Future<dynamic> login(Map<String, dynamic> param) async {
Response<dynamic> response =
await DioClient().doPost(Api.login, queryParameters: param);
return response;
}
Future<void> logout() async {
Response<dynamic> response = await DioClient().doGet(Api.logout);
}
}
网友评论