likes
comments
collection
share

一个GetX的demo

作者站长头像
站长
· 阅读数 42

关于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

  1. 创建ApiService,这个类封装DioCreator,里面的方法是所有和业务相关的联网接口
  2. 创建AppNet,这个类保存和接口相关的Url
  3. 创建自定义的网络日志打印类LogInterceptor
  4. 创建StatusCheckInterceptor,检查业务接口是否报错
  5. 创建HomeChangeControl,继承自PagingListControl,重写loadPagingData方法将首页数据返回给父类
  6. 编写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)的情况下是必要的。

然后我们在GetMaterialAppinitialBinding方法里通过Get.lazyPut方法传入我们自定义的GetxController,顾名思义,Get.lazyPut传入的GetxController只有在使用的时候才会实例化,其中AppChangeControl()我们可以用来保存全局的一些配置,在HomeChangeControl()里我们保存首页数据,具体的这些GetX的参数请看文档。

然后我们看Home.dartpaging_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即可。重点是PagingListWidgetbuild方法里我们返回了一个GetBuilder,它是在GetX里做状态管理的,关于GetX的状态管理有4种,这些都可以在GetX的文档里看到,在文章的最后我也会将我学习到的总结一下,这里记住它是做状态管理的即可,到这里,这篇文章已经结束了,我们通过GetBuilder拿到App这个Widget里GetMaterialAppinitialBinding参数配置的HomeChangeControl,然后通过这个HomeChangeControl去取数据即可,而HomeChangeControl继承自PagingListControl,在PagingListControl这个类的onInit()方法在调用的时候会去调用loadFirstPage()方法获取首页数据,而PagingListControl类里有一个listState属性,该属性默认值为ListState.LOADING,也就是说第一次进入页面的时候列表页默认是Loading状态,在获取到数据后PagingListControl会调用update()方法,然后刷新列表显示数据。


文章结束,这个GetX的demo也就写完了,核心内容就是如上所述。有一些和框架没什么关系的细节我没有说,比如如何封装的PagingListControlPagingListWidget,这些有兴趣可以去看我的源码。连接我都放到下面了。

GetX的中文官方文档

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中,他是消耗资源最多的一个
注意:
上面的12在被观察的值改变后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
评论
请登录