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

  • createDioCreatorFor operating networking
  • createCommonApiServicePut all the network requests we need here
  • createPagingListControlI think I encapsulated this class skillfully, because all apps will have the requirements of list presentation, SO I encapsulated itGetxControllerBasically, 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 resetThe subclass returns the corresponding paging data based on these two parametersPagingData, and then the class passes through the paging dataPagingDataTo determine what display state the current page should be
  • createPagingData, 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

  • networkPut the network-related classes into this package
  • page, put all pages into this package
  • util, put all the utility classes into this package
  • constantPut all static variables into this package
  • widgetTo put public widgets into this package

Start developing app

  1. createApiService, this class encapsulatesDioCreatorThe method inside is all the business-related networking interfaces
  2. createAppNetThis class holds the Url associated with the interface
  3. Create a custom log printing classLogInterceptor
  4. createStatusCheckInterceptorTo check whether an error occurs on the service interface
  5. createHomeChangeControlTo inherit fromPagingListControl, the rewriteloadPagingDataMethod returns the home page data to the parent class
  6. writemain.dartTo createAppThe 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