FishRedux完成一个玩安卓客户端

[toc]android

FishRedux完成一个玩安卓客户端

前言

不知不觉从18年接触Flutter断断续续到如今,说是一直在玩,其实接触得也都很浅~ 实际提及来,貌似本身一点都不懂... 虽然本身断断续续也写了一些app: 玩安卓 钢铁直男版 也在公司app上集成了一个单页面的flutter首页 [捂脸] 可是说实话我本身都不想去玩,好垃圾~git

因此才会想在年末比较闲的时候,作出一个至少我愿意装在我手机上的app,至少是...对我有用的app,因此才有了这个项目。github

但愿本身能够一直有恒心完善下去:web

已完成数据库

  • 首页文章列表
  • banner
  • 微信公众号列表
  • 热门项目
  • 搜索
  • 个人收藏(网站,文章)
  • 添加&删除&编辑收藏
  • 体系
  • 导航
  • 积分(收益详情&排名列表)
  • 分享
  • 主题换肤

未完成redux

  • todo模块,但愿能够完成一个todo提示,
  • 吃枣药丸,加入一些比较好玩的东西,看博客腻了能够看点好玩的
  • 放松放松,同上
  • 实用工具,(至少我要加入一个千卡转千焦,千焦转大卡的计算工具)
  • webViewPlugs和flutter自带webView的切换(实际上试过,plugs是整个覆盖在flutter页面上,实际上体验通常,不少控件不能本身定义;自带的webview性能通常)
  • 切换字体
  • and so on

基本架子

Flutter开发的一个爽点是:无脑堆代码(大雾),而最大的痛点也是这个,不少时候你会发现本身哼哧哼哧一通代码写下来:微信

class _TestPageState extends State<TestPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: null,
        title: null,
        actions: <Widget>[],
      ),
      body: Column(children: <Widget>[],),
      bottomNavigationBar: Row(children: <Widget>[],),
    );
  }
}
复制代码

哇!! 一鼓作气! 浑身通透! 再仔细一看:markdown

),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ),
          )
        ],
      ),
    );
  }
}
复制代码

卧槽!!!!!!!!!!!!

并且这只是v的代码,更别说还有mc的代码,一个稍微复杂点的页面,垂手可得就上了几百行代码,更别说没有提供页面预览功能(新版as已经提供了),这给往后的界面修改和业务修改都增长了难度,这其实就是不少人被劝退的直接缘由了。 有没有解决办法呢? 实际上是有的,页面拆分就是一个不错的办法,把一个页面进行业务级的拆分,多个cell组成一个页面,单个cell能够独立,其实就是组件化的思想,可是!仍是麻烦!!! 并且我也不知足于原生的方法,由于群里大佬已经在疯狂安利FishRedux了,而我想着说,反正是个2019的句号,索性我也画得疯狂一点,就用fisnRedex了。网络

###提早总结架构

代码量爆炸! 可是爽!!!! 爽得能够边写代码边喝酒边唱歌! 有坑!!!! 坑巨多!! 文档贼少!!! 大部份坑都是能够解决的,并且很爽

若是不是很了解fishRedux的能够去看下 fishRedux地址 用FishRedux完成一个登陆页面

页面预览

Screenshot_2020-01-07-23-39-11-008_com.lht.flutter_android_fun.png

Screenshot_2020-01-07-23-39-18-750_com.lht.flutter_android_fun.png

Screenshot_2020-01-07-23-39-21-059_com.lht.flutter_android_fun.png

Screenshot_2020-01-07-23-39-23-285_com.lht.flutter_android_fun.png

Screenshot_2020-01-07-23-39-30-113_com.lht.flutter_android_fun.png

Screenshot_2020-01-07-23-39-36-264_com.lht.flutter_android_fun.png

路由定义:

/// 建立应用的根 Widget
/// 1. 建立一个简单的路由,并注册页面
/// 2. 对所需的页面进行和 AppStore 的链接
/// 3. 对所需的页面进行 AOP 的加强

class AppRoute {
  static AbstractRoutes _global;

  static AbstractRoutes get global {
    if (_global == null) {
      _global = PageRoutes(
        pages: <String, Page<Object, dynamic>>{
          /// 闪屏页
          'splash': SplashPage(),

          /// 首页
          'home': MainPage(),

          /// 登陆页面
          'login': LoginPage(),

          /// 注册页面
          'register': RegisterPage(),

          /// 首页的第二个tab
          'second': SecondPage(),

          /// 首页的第一个tab
          'index': IndexPage(),

          ///项目目录
          'project_list': ProjectListPage(),

          /// 项目子目录
          'project_child_list': ProjectChildPage(),

          /// webView页面
          'webView': WebLoadPage(),

          /// 微信公众号列表页面
          'wechat_author': AuthorPage(),

          /// 微信公众号文章列表页面
          'wechat_author_article': AuthorArticlePage(),

          /// 用户积分
          'user_point': UserPointPage(),

          /// 用户排名
          'user_rank': UserRankPage(),

          /// 网址收藏
          'web_collection': WebCollectionPage(),

          ///文章收藏
          'article_collection': ArticleCollectionPage(),

          /// 体系列表
          'system': SystemPage(),

          /// 体系列表下属文章
          'system_child': SystemChildPage(),

          /// 导航体系
          'navi': NaviPage(),

          /// 侧滑页面
          'draw': DrawPage(),

          /// 主题颜色修改
          'theme_change': ThemeChangePage(),

          /// 搜索页面
          'search': SearchPage(),
        },
        visitor: (String path, Page<Object, dynamic> page) {
          /// 只有特定的范围的 Page 才须要创建和 AppStore 的链接关系
          /// 知足 Page<T> ,T 是 GlobalBaseState 的子类
          if (page.isTypeof<GlobalBaseState>()) {
            /// 创建 AppStore 驱动 PageStore 的单向数据链接
            /// 1. 参数1 AppStore
            /// 2. 参数2 当 AppStore.state 变化时, PageStore.state 该如何变化
            page.connectExtraStore<GlobalState>(GlobalStore.store,
                (Object pageState, GlobalState appState) {
              final GlobalBaseState p = pageState;
// if (p.themeColor != appState.themeColor &&
// p.ifLogin != appState.ifLogin) {
              if (pageState is Cloneable) {
                print('修改--进行复制');
                final Object copy = pageState.clone();
                final GlobalBaseState newState = copy;
                newState.themeColor = appState.themeColor;
                newState.ifLogin = appState.ifLogin;
                newState.screenH = appState.screenH;
                newState.screenW = appState.screenW;
                newState.userPoint = appState.userPoint;
                return newState;
// }
              }
              return pageState;
            });
          }

          /// AOP
          /// 页面能够有一些私有的 AOP 的加强, 但每每会有一些 AOP 是整个应用下,全部页面都会有的。
          /// 这些公共的通用 AOP ,经过遍历路由页面的形式统一加入。
          page.enhancer.append(
            /// View AOP
            viewMiddleware: <ViewMiddleware<dynamic>>[
              safetyView<dynamic>(),
            ],

            /// Adapter AOP
            adapterMiddleware: <AdapterMiddleware<dynamic>>[
              safetyAdapter<dynamic>()
            ],

            /// Effect AOP
            effectMiddleware: <EffectMiddleware<dynamic>>[
              _pageAnalyticsMiddleware<dynamic>(),
            ],

            /// Store AOP
            middleware: <Middleware<dynamic>>[
              logMiddleware<dynamic>(tag: page.runtimeType.toString()),
            ],
          );
        },
      );
    }
    return _global;
  }
}

Widget createApp() {
  final AbstractRoutes routes = AppRoute.global;

  return MaterialApp(
    title: '玩安卓',
    debugShowCheckedModeBanner: false,
    theme: ThemeData(
      indicatorColor: ColorConf.ColorFFFFFF,
      primarySwatch: ColorConf.themeColor,
    ),
    home: routes.buildPage('splash', null),
    onGenerateRoute: (RouteSettings settings) {
      return MaterialPageRoute<Object>(builder: (BuildContext context) {
        return routes.buildPage(settings.name, settings.arguments);
      });
    },
  );
}

/// 简单的 Effect AOP
/// 只针对页面的生命周期进行打印
EffectMiddleware<T> _pageAnalyticsMiddleware<T>({String tag = 'redux'}) {
  return (AbstractLogic<dynamic> logic, Store<T> store) {
    return (Effect<dynamic> effect) {
      return (Action action, Context<dynamic> ctx) {
        if (logic is Page<dynamic, dynamic> && action.type is Lifecycle) {
          print('${logic.runtimeType} ${action.type.toString()} ');
        }
        return effect?.call(action, ctx);
      };
    };
  };
}

复制代码

首页

根据FishRedux的思想,咱们把首页架构定义为: 一个大的page(MainPage),里面用pageView装载了两个大的page(SecondPage&IndexPage),

view

Widget buildView(MainState state, Dispatch dispatch, ViewService viewService) {
  /// 渲染appBar
  AppBar _renderAppBar() {
    return AppBar(
      backgroundColor: state.themeColor,
      centerTitle: true,
      titleSpacing: 60,
      title: TabBar(
        tabs: state.menuList
            .map((e) => Tab(
                  text: e,
                ))
            .toList(),
        labelColor: Colors.white,
        controller: state.tabControllerForMenu,
        labelPadding: const EdgeInsets.all(0),
        labelStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
        unselectedLabelStyle: TextStyle(fontSize: 14),
        indicatorPadding: const EdgeInsets.all(0),
        indicatorSize: TabBarIndicatorSize.label,
      ),
      leading: Builder(builder: (ctx) {
        return IconButton(
          onPressed: () {
            dispatch(MainActionCreator.onOpenDraw(ctx));
          },
          icon: Image.asset(
            'images/icon_more.png',
            color: Colors.white,
            height: 24,
          ),
        );
      }),
      actions: <Widget>[
        IconButton(
          onPressed: () {
            dispatch(MainActionCreator.onToSearch());
          },
          icon: Icon(Icons.search),
        )
      ],
    );
  }

  return Scaffold(
    primary: true,
    appBar: _renderAppBar(),
    body: TabBarView(
      controller: state.tabControllerForMenu,
      children: <Widget>[
        KeepAliveWidget(AppRoute.global.buildPage('second', null)),
        KeepAliveWidget(AppRoute.global.buildPage('index', null)),
      ],
    ),
    drawer: AppRoute.global.buildPage('draw', null),
  );
}
复制代码

and so on

好像也没有其余什么须要注意的了,只有一个难点是TabController,以及page页面须要如何保活

定义本身的TabController

这个能够参考下以前的文章:在fishRedux中使用TabController

页面保活

在普通的stf页面中,咱们须要页面保持,只须要实现**AutomaticKeepAliveClientMixin **:

class _TestPageState extends State<testPage> with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    /// 实现super方法
    super.build(context);
    return Container();
  }

  /// 返回true
  @override
  bool get wantKeepAlive => true;
}
复制代码

而在fishRedux中就比较麻烦,咱们须要把这个page用keepWidget包裹起来:

import 'package:flutter/material.dart';
/// 保持状态的包裹类
class KeepAliveWidget extends StatefulWidget {
  final Widget child;

  const KeepAliveWidget(this.child);

  @override
  State<StatefulWidget> createState() => _KeepAliveState();
}

class _KeepAliveState extends State<KeepAliveWidget> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return widget.child;
  }
}

Widget keepAliveWrapper(Widget child) => KeepAliveWidget(child)
复制代码

Adapter写法

咱们看下首页的布局,很明显由几个cell组成:

  • banner
  • 公众号分类gridView
  • 置顶推荐
  • 项目推荐
  • 首页分章分页

若是在Android里面,那很明显就是一个RecyclerView+itemType组成; 若是是在Flutter原生里面,那很明显就是一个ListView+ItemBuilder里面按item划分 而咱们在FishRedux里面,咱们把页面作了一个拆分,页面是由一个SingleScrollView组成,而不管bannerComponent,classifyComponent,projectComponent,都是它的一个cell,而重头戏是articleComponent,它带有了父组件带来的loadMore和Refresh(其实整个页面均可以由一个ListView组成,当时不是很熟就用了上面的方法),咱们来看看布局层级:

code

其中的Index_view为:

child: CustomScrollView(
      slivers: <Widget>[
        SliverToBoxAdapter(
          child: viewService.buildComponent('banner'),
        ),
        SliverToBoxAdapter(
          child: viewService.buildComponent('classify'),
        ),
        SliverToBoxAdapter(
          child: viewService.buildComponent('hotArticle'),
        ),
      ],
    ),
复制代码

adapter

首页咱们须要关注的是首页文章的Adapter,它隶属于DynamicFlowAdapter,其余的还有

class ArticleAdapter extends DynamicFlowAdapter<HotArticleState> {
  ArticleAdapter()
      : super(
          pool: <String, Component<Object>>{
            "article_cell": ArticleCellComponent(),
            "comm_article_cell": CommArticleCellComponent(),
            "hot_project_cell": ProjectComponent(),
          },
          connector: _ArticleAdapterConnector(),
          reducer: buildReducer(),
        );
}

class _ArticleAdapterConnector extends ConnOp<HotArticleState, List<ItemBean>> {
  @override
  List<ItemBean> get(HotArticleState state) {
    List<ItemBean> _tempList = [];
    _tempList.addAll(state.hotArticleDataSource
        .map((e) => ItemBean(
            "article_cell", ArticleCellState()..hotArticleCellBean = e))
        .toList());
    _tempList.add(ItemBean(
        "hot_project_cell",
        ProjectState()
          ..projectListDataSource = state.projectDataSource
          ..screenW = state.size?.width
          ..screenH = state.size?.height));
    _tempList.addAll(state.commArticleDataSource
        .map((e) =>
            ItemBean("comm_article_cell", CommArticleCellState()..cellBean = e))
        .toList());
    return _tempList;
  }

  @override
  void set(HotArticleState state, List<ItemBean> items) {}

  @override
  subReducer(reducer) {
    return super.subReducer(reducer);
  }
}
复制代码

咱们稍微分析下:

  1. 咱们在pool中定义了component的路由
  2. 咱们在_ArticleAdapterConnector的get方法中返回了一个ItemBean的List,其type为咱们提早定义好的component,而data为各个component的state(各个component的state应该为page的子集)
  3. over

我的页面&登陆页面

原本还想写写其余页面的代码的,可是其实都是我的主页页面的代码的拓展,说难点其实没有,惟一的尴尬点就是代码量爆炸,还有一点是一开始用fishRedux会忘记使用方法,好比:

  1. action怎么写?
  2. 在effect仍是reducer里面写逻辑??
  3. 个人分页要怎么写比较好?
  4. 卧槽,个人tabController咋写
  5. ... 这里把个人葵花宝典奉上,我把下面这段文字写成了一个txt,放在桌面,忘记了就打开看看:
action
用来定义在这个页面中发生的动做,例如:登陆,清理输入框,更换验证码框等。
同时能够经过payload参数传值,传递一些不能经过state传递的值。

effect
这个dart文件在fish_redux中是定义来处理反作用操做的,好比显示弹窗,网络请求,数据库查询等操做。

page
这个dart文件在用来在路由注册,同时完成注册effect,reducer,component,adapter的功能。

reducer
这个dart文件是用来更新View,即直接操做View状态。

state
state用来定义页面中的数据,用来保存页面状态和数据。

view
view很明显,就是flutter里面当中展现给用户看到的页面。
复制代码

结语

这个app还很粗糙,欢迎提issue,我会持续改进的。

密码是123456
相关文章
相关标签/搜索