一个GetX的demo
关于Flutter的多个状态管理就不多说了,我自己也写过类似的状态管理。Flutter的状态管理原理都很简单,基本都是把数据放到上一个节点,或者搞成全局的类似EventBus然后手动控制删除。这次的GetX也一样,但是这次的GetX封装更强,功能更强,我用wanandroid写了个demo,可以方便的学习GetX常用功能。
Demo功能
这个例子的功能很简单,就一个首页,做好首页的流程其他功能都大同小异
第三方lib
- flutter_easyrefresh: ^1.2.7
- dio: ^3.0.10
- get: ^3.17.0
开发流程
创建一个base_lib,用来做基础类库
- 创建
DioCreator
,用来操作联网 - 创建
CommonApiService
,将我们要用到的所有的网络请求放到这里 - 创建
PagingListControl
,这个类我自认为我封装的比较巧妙,因为所有的app都会有列表展示的需求,我封装了一下GetxController
,简单来说就是我把获取首页,上拉加载,下拉刷新,重试按钮抽象了一下,该类有一个抽象方法loadPagingData
,该方法有两个参数int pageIndex,bool reset
,子类根据这两个参数返回对应的分页数据PagingData
,然后该类通过分页数据PagingData
来判断当前页面应该是什么显示状态 - 创建
PagingData
,自定义的分页数据封装类
结束,主要就这几个类
在lib包下面新建app相关包
这部分不重要,按照自己的开发习惯即可
network
,将网络相关类放到此包page
,将所有页面放入此包util
,将所有工具类放入此包constant
,将所有静态变量放入此包widget
,将公共Widget放入此包
开始开发app
- 创建
ApiService
,这个类封装DioCreator
,里面的方法是所有和业务相关的联网接口 - 创建
AppNet
,这个类保存和接口相关的Url - 创建自定义的网络日志打印类
LogInterceptor
- 创建
StatusCheckInterceptor
,检查业务接口是否报错 - 创建
HomeChangeControl
,继承自PagingListControl
,重写loadPagingData
方法将首页数据返回给父类 - 编写
main.dart
,创建App
要显示的页面
核心代码讲解
main.dart
void main() {
ApiService.init(
AppNet.BASE_URL,
interceptors: [
LogInterceptor(),
StatusCheckInterceptor(),
],
);
runApp(
App(),
);
}
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
initialBinding: BindingsBuilder(() {
Get.lazyPut<HomeChangeControl>(() => HomeChangeControl(), fenix: true);
Get.lazyPut<AppChangeControl>(() => AppChangeControl(), fenix: true);
}),
title: 'WanAndroid',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: AppPage(),
);
}
}
class AppPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('首页'),
),
body: Home(),
);
}
}
首先调用ApiService.init
初始化联网类,添加日志拦截器和返回状态拦截器,然后通过runApp
方法启动名为App
的Widget,在这个Widget的build
方法里我们返回GetMaterialApp
,为什么返回GetMaterialApp
,这个在GetX的文档里说了,如下
GetMaterialApp会创建路由,注入它们,注入翻译,注入你需要的一切路由导航。如果你只用Get来进行状态管理或依赖管理,就没有必要使用GetMaterialApp。GetMaterialApp对于路由、snackbar、国际化、bottomSheet、对话框以及与路由相关的高级apis和没有上下文(context)的情况下是必要的。
然后我们在GetMaterialApp
的initialBinding
方法里通过Get.lazyPut
方法传入我们自定义的GetxController
,顾名思义,Get.lazyPut
传入的GetxController
只有在使用的时候才会实例化,其中AppChangeControl()
我们可以用来保存全局的一些配置,在HomeChangeControl()
里我们保存首页数据,具体的这些GetX的参数请看文档。
然后我们看Home.dart
和paging_list.dart
这两个文件
Home.dart
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return PagingListWidget<Datas, HomeChangeControl>(
createItem: (BuildContext context, int index, Datas data) {
return Container(
padding: const EdgeInsets.all(10),
child: Text(data.title),
);
},
);
}
}
paging_list.dart
typedef ItemBuilder<T> = Widget Function(BuildContext context, int index, T data);
typedef RetryLoad = void Function(BuildContext context);
///继承该类的Widget就有了上拉加载下拉刷新的功能
class PagingListWidget<LIST_ITEM, C extends PagingListControl<LIST_ITEM>> extends StatelessWidget {
final ItemBuilder<LIST_ITEM> _createItem;
//是否可以下拉刷新
final bool _refreshEnable;
//是否可以上拉加载
final bool _loadMoreEnable;
PagingListWidget({
bool refreshEnable: true,
bool loadMoreEnable: true,
@required ItemBuilder<LIST_ITEM> createItem,
}) : this._createItem = createItem,
this._refreshEnable = refreshEnable,
this._loadMoreEnable = loadMoreEnable;
@override
Widget build(BuildContext context) {
return GetBuilder<C>(
builder: (control) {
return MultiStateList(
easyRefreshKey: control.easyRefreshKey,
headerKey: control.headerKey,
footerKey: control.footerKey,
listState: control.listState,
retry: control.onRetry,
onRefresh: _refreshEnable
? () async {
await control.onRefresh();
}
: null,
loadMore: _loadMoreEnable
? () async {
await control.loadMore();
}
: null,
child: ListView.separated(
separatorBuilder: (BuildContext context, int index) {
return Divider(height: 0, color: Colors.grey);
},
itemCount: control.dataList?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
Widget child = _createItem(context, index, control.dataList[index]);
return Column(
children: [
Row(
children: [
Text("$index"),
Expanded(child: child),
],
),
Divider(height: 0, color: Colors.grey),
],
);
},
),
);
},
);
}
}
enum ListState {
//第一次显示时的loading状态
LOADING,
//显示内容
CONTENT,
//显示空数据
EMPTY,
//显示错误信息
ERROR
}
/// 多状态列表,具有下拉刷新,上拉加载,无数据时显示无数据控件,发生错误时显示错误信息这4种功能
/// 临时封装了一个第三方具有下拉刷新,上拉加载功能的列表,后期替换为自定义的
class MultiStateList extends StatelessWidget {
static const TEXT_TIP = Color(0xFF666666); //提示文字的颜色
//第一次显示时的widget
final Widget _init;
//页面无数据时显示的widget
final Widget _empty;
//页面发生错误时显示的widget
final Widget _error;
//点击无数据或者错误信息的重试按钮时回调的事件
final RetryLoad _retry;
final OnRefresh _onRefresh;
final LoadMore _loadMore;
final GlobalKey<EasyRefreshState> _easyRefreshKey;
final GlobalKey<RefreshHeaderState> _headerKey;
final GlobalKey<RefreshFooterState> _footerKey;
//列表
final Widget _child;
//列表状态
final ListState _listState;
MultiStateList(
{Key key,
GlobalKey<EasyRefreshState> easyRefreshKey,
GlobalKey<RefreshHeaderState> headerKey,
GlobalKey<RefreshFooterState> footerKey,
Widget init,
Widget empty,
Widget error,
ListState listState,
OnRefresh onRefresh,
LoadMore loadMore,
@required RetryLoad retry,
@required Widget child})
: assert(retry != null),
assert(child != null),
_init = init,
_empty = empty,
_error = error,
_listState = listState ?? ListState.LOADING,
_retry = retry,
_onRefresh = onRefresh,
_loadMore = loadMore,
_easyRefreshKey = easyRefreshKey,
_headerKey = headerKey,
_footerKey = footerKey,
_child = child,
super(key: key);
Widget getDefaultInitView() {
return Center(
child: Container(
width: 50,
height: 50,
child: CircularProgressIndicator(),
),
);
}
Widget getDefaultEmpty(BuildContext context) {
return Center(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
_retry(context);
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Image(
image: AssetImage("assets/images/empty.png", package: "base_lib"),
width: 80,
height: 80,
),
Text("暂无数据"),
Text(
"点击重试",
style: TextStyle(
color: TEXT_TIP,
),
),
],
),
),
),
);
}
Widget getDefaultError(BuildContext context) {
return Center(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
_retry(context);
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Image(
image: AssetImage("assets/images/error.png", package: "base_lib"),
width: 100,
height: 80,
),
Text("加载失败"),
Text(
"点击重试",
style: TextStyle(
color: TEXT_TIP,
),
),
],
),
),
),
);
}
@override
Widget build(BuildContext context) {
switch (_listState) {
case ListState.LOADING:
return _init ?? getDefaultInitView();
case ListState.EMPTY:
return _empty ?? getDefaultEmpty(context);
case ListState.ERROR:
return _error ?? getDefaultError(context);
case ListState.CONTENT:
return EasyRefresh(
key: _easyRefreshKey,
behavior: ScrollOverBehavior(),
refreshHeader: ClassicsHeader(
key: _headerKey,
refreshText: '下拉刷新',
refreshReadyText: '释放刷新',
refreshingText: '正在刷新...',
refreshedText: '刷新结束',
moreInfo: '更新于 %T',
bgColor: Colors.transparent,
textColor: Colors.black87,
moreInfoColor: Colors.black54,
showMore: true,
),
refreshFooter: ClassicsFooter(
key: _footerKey,
loadText: '上拉加载',
loadReadyText: '释放加载',
loadingText: '正在加载...',
loadedText: '加载结束',
noMoreText: '没有更多数据',
moreInfo: '更新于 %T',
bgColor: Colors.transparent,
textColor: Colors.black87,
moreInfoColor: Colors.black54,
showMore: true,
),
child: _child,
onRefresh: _onRefresh,
loadMore: _loadMore,
);
default:
return null;
}
}
}
在Home
这个Widget的build
方法里,我们返回了一个PagingListWidget
,这个是我自己封装的一个公共列表类,可以下拉刷新和上拉加载,这个不详细解释,在这里把它当成一个普通列表Widget即可。重点是PagingListWidget
的build
方法里我们返回了一个GetBuilder
,它是在GetX里做状态管理的,关于GetX的状态管理有4种,这些都可以在GetX的文档里看到,在文章的最后我也会将我学习到的总结一下,这里记住它是做状态管理的即可,到这里,这篇文章已经结束了,我们通过GetBuilder
拿到App
这个Widget里GetMaterialApp
的initialBinding
参数配置的HomeChangeControl
,然后通过这个HomeChangeControl
去取数据即可,而HomeChangeControl
继承自PagingListControl
,在PagingListControl
这个类的onInit()
方法在调用的时候会去调用loadFirstPage()
方法获取首页数据,而PagingListControl
类里有一个listState
属性,该属性默认值为ListState.LOADING
,也就是说第一次进入页面的时候列表页默认是Loading状态,在获取到数据后PagingListControl
会调用update()
方法,然后刷新列表显示数据。
文章结束,这个GetX的demo也就写完了,核心内容就是如上所述。有一些和框架没什么关系的细节我没有说,比如如何封装的PagingListControl
和PagingListWidget
,这些有兴趣可以去看我的源码。连接我都放到下面了。
wanAndroid的demo(注意切换到getX分支)
然后我发一下我学习GetX总结的一些东西,仅供参考
状态管理(4个Widget):
Obx=>
GetX<? extends GetxController>与? extends GetxController+T.obs/Rx<T>/Rx...
GetBuilder<? extends GetxController>与? extends GetxController+普通变量+update()
MixinBuilder,既可以通过改变".obs "变量进行响应式改变,也可以通过update()进行手动更新。然而,在4个widget中,他是消耗资源最多的一个
注意:
上面的1和2在被观察的值改变后widiget可以自动更新,而3需要手动调用update()方法,但是update可以更精细的更新指定了id的GetBuilder
效率排行:Obx>GetX>GetBuilder
依赖管理:
注册:
Get.put<S>(),可以设置是否永久保存,是否设置唯一tag用来区分相同类型的类等。只有调用此方法的widget被销毁后put的实例才会销毁
Get.lazyPut,懒加载,可以设置保存的类在被第一次调用时执行某方法,设置唯一tag,还有类似于“永久”的设置。在find该实例的widget销毁后就会销毁lazyPut保存的实例,如果fenix为false并且"smartManagement "不是 "keepFactory",那么再次进入find该实例的widget报错说找不到;如果如果fenix为true或者"smartManagement "是 "keepFactory",则会再次调用懒加载方法创建实例。
Get.putAsync,注册异步实例
Get.create
获取:
final controller = Get.find<Controller>();
// 或者
Controller controller = Get.find();
删除:
Get.delete<Controller>(); //通常你不需要这样做,因为GetX已经删除了未使用的控制器。
智能管理:
如果你想改变GetX控制类的销毁方式,你可以用SmartManagement类设置不同的行为。
使用:
SmartManagement.full:这是默认的。
SmartManagement.onlyBuilders:只有在init:中启动的控制器或用Get.lazyPut()加载到Binding中的控制器才会被销毁
SmartManagement.keepFactory:它被设计成在没有Bindings的情况下使用,或者在GetMaterialApp的初始Binding中链接一个Binding。
注释:
如果你使用多个Bindings,不要使用SmartManagement.keepFactory。它被设计成在没有Bindings的情况下使用,或者在GetMaterialApp的初始Binding中链接一个Binding。
使用Bindings是完全可选的,你也可以在使用给定控制器的类上使用Get.put()和Get.find()。 然而,如果你使用Services或任何其他抽象,我建议使用Bindings来更好地组织。
转载自:https://juejin.cn/post/6911204685206519815