Without further ado about the multiple state management of Flutter, I have written about similar state management myself. The principle of Flutter state management is very simple. Basically, Flutter data is placed on a previous node, or created as a global EventBus and deleted manually. This time GetX is the same, but this time GetX package is stronger, more powerful, I wrote a demo with WanAndroid, can conveniently learn GetX common functions.
The Demo function
The function of this example is very simple, just a home page, do the process of home page and other functions are similar
The third party lib
- Flutter_easyrefresh: ^ 1.2.7
- Dio: ^ 3.0.10
- The get: ^ 3.17.0
The development process
Create a base_lib for the base library
- create
DioCreator
For operating networking - create
CommonApiService
Put all the network requests we need here - create
PagingListControl
I think I encapsulated this class skillfully, because all apps will have the requirements of list presentation, SO I encapsulated itGetxController
Basically, I’ve abstracted the get home page, pull-up load, pull-down refresh, and retry buttons. This class has an abstract methodloadPagingData
, the method takes two parametersint pageIndex,bool reset
The subclass returns the corresponding paging data based on these two parametersPagingData
, and then the class passes through the paging dataPagingData
To determine what display state the current page should be - create
PagingData
, custom paged data encapsulation class
That’s it. Those are the main ones
Create a new app-related package under the lib package
This part is not important, just follow your own development habits
network
Put the network-related classes into this packagepage
, put all pages into this packageutil
, put all the utility classes into this packageconstant
Put all static variables into this packagewidget
To put public widgets into this package
Start developing app
- create
ApiService
, this class encapsulatesDioCreator
The method inside is all the business-related networking interfaces - create
AppNet
This class holds the Url associated with the interface - Create a custom log printing class
LogInterceptor
- create
StatusCheckInterceptor
To check whether an error occurs on the service interface - create
HomeChangeControl
To inherit fromPagingListControl
, the rewriteloadPagingData
Method returns the home page data to the parent class - write
main.dart
To createApp
The page to display
Core code explanation
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('home'), ), body: Home(), ); }}Copy the code
Init initializes the networking class by calling apiservice. init, adding a log interceptor and a return state interceptor, and then launching the Widget named App through the runApp method. In the build method of this Widget we return GetMaterialApp. Why return GetMaterialApp, which is described in the GetX documentation, as follows
GetMaterialApp will create the routes, inject them, inject the translations, inject whatever route navigation you need. There is no need to use GetMaterialApp if you only use Get for state management or dependency management. GetMaterialApp is necessary for routing, snackBar, internationalization, bottomSheet, dialogs, and high-level apis related to routing and without context.
We then pass in our custom GetxController in the GetMaterialApp initialBinding method via the get. lazyPut method, as the name suggests, The GetxController passed in by get.lazyput is instantiated only when it is used. AppChangeControl() is used to store some global configuration, and HomeChangeControl() is used to store home page data, See the documentation for details of these GetX parameters.
Dart and 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), ); }); }}Copy the code
paging_list.dart
typedef ItemBuilder<T> = Widget Function(BuildContext context, int index, T data);
typedef RetryLoad = void Function(BuildContext context);
///Widgets that inherit this class have pull-up load and pull-down refresh capabilities
class PagingListWidget<LIST_ITEM.C extends PagingListControl<LIST_ITEM>> extends StatelessWidget {
final ItemBuilder<LIST_ITEM> _createItem;
// Whether the refresh can be pulled down
final bool _refreshEnable;
// Whether the load can be pulled up
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) {
returnMultiStateList( easyRefreshKey: control.easyRefreshKey, headerKey: control.headerKey, footerKey: control.footerKey, listState: control.listState, retry: control.onRetry, onRefresh: _refreshEnable ? (a)async {
await control.onRefresh();
}
: null, loadMore: _loadMoreEnable ? (a)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 {
// The loading state was displayed for the first time
LOADING,
// Display the content
CONTENT,
// Display null data
EMPTY,
// Error information is displayed
ERROR
}
/// Multi-state list, with drop-down refresh, pull-up loading, no data display no data control, error display error message when the occurrence of these four functions
/// Temporary encapsulation of a third party with drop-down refresh, drop-down load function of the list, later replaced by custom
class MultiStateList extends StatelessWidget {
static const TEXT_TIP = Color(0xFF666666); // The color of the prompt text
// The widget when first displayed
final Widget _init;
// The widget that displays when the page has no data
final Widget _empty;
// The widget that displays when the page has an error
final Widget _error;
// The event that is called back when the retry button is clicked with no data or error message
final RetryLoad _retry;
final OnRefresh _onRefresh;
final LoadMore _loadMore;
final GlobalKey<EasyRefreshState> _easyRefreshKey;
final GlobalKey<RefreshHeaderState> _headerKey;
final GlobalKey<RefreshFooterState> _footerKey;
/ / list
final Widget _child;
// List status
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("No data at present"),
Text(
"Hit Retry",
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("Load failed"),
Text(
"Hit Retry",
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: 'Pull refresh',
refreshReadyText: 'Release refresh',
refreshingText: 'Refreshing... ',
refreshedText: 'Refresh end',
moreInfo: 'Updated at %T',
bgColor: Colors.transparent,
textColor: Colors.black87,
moreInfoColor: Colors.black54,
showMore: true,
),
refreshFooter: ClassicsFooter(
key: _footerKey,
loadText: 'Pull-up load',
loadReadyText: 'Release load',
loadingText: 'Loading... ',
loadedText: 'Load finished',
noMoreText: 'No more data',
moreInfo: 'Updated at %T',
bgColor: Colors.transparent,
textColor: Colors.black87,
moreInfoColor: Colors.black54,
showMore: true,
),
child: _child,
onRefresh: _onRefresh,
loadMore: _loadMore,
);
default:
return null; }}}Copy the code
In the build method of the Home Widget, we return a PagingListWidget. This is a public list class that I’ve wrapped myself that can be pulled down to refresh and pulled up to load. We won’t explain this in detail, but treat it as a normal list Widget. In the build method of PagingListWidget, we return a GetBuilder, which does state management in GetX. There are four types of state management in GetX, which can be found in the documentation of GetX. I will summarize what I have learned at the end of this article. This is the end of the article. We get the HomeChangeControl configured for the initialBinding parameter of GetMaterialApp in the Widget App using GetBuilder. And I’m just going to fetch the data through this HomeChangeControl, which inherits from PagingListControl, The onInit() method of the PagingListControl class calls the loadFirstPage() method to get the first page data, and the PagingListControl class has a listState property, The default value of this property is liststate.loading, which means that the LOADING state of the list is loaded by default when the page is loaded for the first time. After data is obtained, PagingListControl calls update() and then refreshes the list to display data.
With this article over, the GetX demo is done, and the core content is as described above. There are some details that have nothing to do with the framework that I haven’t mentioned, such as how to encapsulate PagingListControl and PagingListWidget. If you are interested, check out my source code. I’ve put all the links down here.
GetX official Chinese document
WanAndroid demo(note the switch to the getX branch)
Then I’ll post some of the things I learned from GetX, just for your reference
Status Management (4 widgets) : Obx=> GetX<? Extends GetxController > with? extends GetxController+T.obs/Rx<T>/Rx... GetBuilder<? Extends GetxController > with? Extends GetxController+ normal variable + Update () MixinBuilder, which can be changed either in response by changing the ".obs "variable or manually via update(). Note, however, that of the four widgets, it is the most resource-consuming one: Widiget updates 1 and 2 automatically after the observed value changes, while 3 requires a manual call to the update() method, but update can more subtly update the GetBuilder efficiency ranking with the specified ID: Obx>GetX>GetBuilder dependency management: register: get.put <S>(), you can set whether to save permanently, whether to set a unique tag to distinguish the same type of class, etc. Get. LazyPut instances are destroyed only after the widget calling this method is destroyed. Lazy loading allows you to set the saved class to execute a method the first time it is called, set a unique tag, and things like "forever". If fenix is false and "smartManagement "is not "keepFactory", then the widget that re-entered the find instance reported an error saying it was not found. If fenix is true or "smartManagement "is "keepFactory", the lazy loading method is called again to create the instance. Get. Create Get: Final Controller = get.find < controller >(); // Controller Controller = get.find (); Delete: Get the delete < Controller > (); // Usually you don't need to do this because GetX already removes unused controllers. SmartManagement: if you want to change the way the GetX control class is destroyed, you can use the SmartManagement class to set a different behavior. Use: SmartManagement. Full: This is the default. SmartManagement. OnlyBuilders: only in the init: start the controller, or use the Get the lazyPut () loaded into the controller will be destroyed in the Binding SmartManagement. KeepFactory: It is designed to be used without Bindings, or to link a Binding in the initial Binding of GetMaterialApp. Note: if you use multiple Bindings, do not use SmartManagement. KeepFactory. It is designed to be used without Bindings, or to link a Binding in the initial Binding of GetMaterialApp. Bindings use is completely optional, you can also use get-.put () and get-.find () on classes that use a given controller. However, if you use Services or any other abstraction, I recommend using Bindings for better organization.Copy the code