[toc]

FishRedux completes a play android client

preface

Unconsciously, I have been in touch with Flutter intermittently for 18 years until now. It is said that I have been playing with Flutter all the time, but in fact, I don’t know much about Flutter at all. Although I have written a few apps on and off: Play Android Steel straight Man version project address has been set up on the company app as a single page of Flutter home page, but to be honest I don’t want to play it myself, so rubbish ~

Therefore, I would like to make an app that I would like to install on my mobile phone at the end of the year when I am free, at least… Useful app for me, hence this project.

I hope I can always have the perseverance to improve:

Has been completed

  • Home Page Article List
  • banner
  • List of wechat public accounts
  • Popular project
  • search
  • My Collection (websites, articles)
  • Add & Delete & Edit favorites
  • system
  • navigation
  • Points (Revenue details & Ranking list)
  • share
  • Topics in the skin

unfinished

  • Todo module, hoping to complete a todo prompt,
  • Take a date pill and add something fun to it. If you’re tired of reading blogs, you can read something fun
  • Relax, relax, ditto
  • Utility tools, (at least I will add a kilocalorie to kilojoule, kilojoule to large CAL calculation tool)
  • Switch webViewPlugs and flutter with webView (actually tried, plug the whole fit on the flutter page, but the experience wasn’t really that great, many of the controls weren’t self-defining; Built-in WebView performance is mediocre)
  • Switch fonts
  • and so on

The basic frame

One of the great things about Flutter development is that it has no idea what to do with code (fog). The biggest pain point of Flutter is also this. You will often find yourself writing code with a lot of zapping:

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>[],), ); }}Copy the code

Wow!!!!! In one go! All over! Take a closer look:

),),),),),),),),),),); }}Copy the code

And this is just the code of v, let alone and MC code, a slightly more complicated point page, easily on hundreds of lines of code, let alone does not provide page preview function (the new as has provided), it to modify the interface modification, and business in the future are more difficult, it is actually a lot of people is the direct reason for stop. Is there a solution? In fact, there are, page split is a good way, the business level of a page split, multiple cells composed of a page, a single cell can be independent, in fact, is the idea of componentization, but! Still trouble!! And I wasn’t satisfied with the original method, because the big guy in the group was already crazy about Amway FishRedux, and I thought, it’s 2019 anyway, so I’ll just go crazy and use fisnRedex.

### Summarize ahead of time

Code explosion! But cool !!!! It’s so cool you can drink and sing while writing code! Pit !!!! So many pits!! Document thief less!! Most potholes are solvable and enjoyable

If you don’t know much about fishRedux you can go to the fishRedux address and create a login page with fishRedux

Page preview

Route definition:

/// Create the root Widget for the application
1. Create a simple route and register the page
/// 2. Connect the required page to AppStore
/// 3. AOP enhancements for the required pages

class AppRoute {
  static AbstractRoutes _global;

  static AbstractRoutes get global {
    if (_global == null) {
      _global = PageRoutes(
        pages: <String, Page<Object.dynamic> > {/ / / the splash screen page
          'splash': SplashPage(),

          / / / home page
          'home': MainPage(),

          /// Login page
          'login': LoginPage(),

          /// Register page
          'register': RegisterPage(),

          // the second TAB of the home page
          'second': SecondPage(),

          /// the first TAB on the home page
          'index': IndexPage(),

          /// Project directory
          'project_list': ProjectListPage(),

          /// project subdirectory
          'project_child_list': ProjectChildPage(),

          / / / webView page
          'webView': WebLoadPage(),

          /// wechat official account list page
          'wechat_author': AuthorPage(),

          /// Wechat official account article list page
          'wechat_author_article': AuthorArticlePage(),

          /// user credits
          'user_point': UserPointPage(),

          /// User ranking
          'user_rank': UserRankPage(),

          ///
          'web_collection': WebCollectionPage(),

          ///
          'article_collection': ArticleCollectionPage(),

          /// System list
          'system': SystemPage(),

          /// system list of subordinate articles
          'system_child': SystemChildPage(),

          /// navigation system
          'navi': NaviPage(),

          /// slide the page
          'draw': DrawPage(),

          /// Change the theme color
          'theme_change': ThemeChangePage(),

          /// search page
          'search': SearchPage(),
        },
        visitor: (String path, Page<Object.dynamic> page) {
          /// Only certain ranges of pages need to be connected to the AppStore
          / / / content Page<T>, T is a subclass of GlobalBaseState
          if (page.isTypeof<GlobalBaseState>()) {
            // set up a one-way data connection for AppStore driver PageStore
            /// 1. Parameter 1 AppStore
            /// 2. Parameter 2 How should pagestore. state change when AppStore
            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('Modify -- make a copy');
                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
          /// Pages can have some proprietary AOP enhancements, but there will often be some AOP that will be available to all pages throughout the application.
          /// These common general-purpose AOP are added uniformly by traversing the routing page.
          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: 'Play Android',
    debugShowCheckedModeBanner: false,
    theme: ThemeData(
      indicatorColor: ColorConf.ColorFFFFFF,
      primarySwatch: ColorConf.themeColor,
    ),
    home: routes.buildPage('splash'.null),
    onGenerateRoute: (RouteSettings settings) {
      return MaterialPageRoute<Object>(builder: (BuildContext context) {
        returnroutes.buildPage(settings.name, settings.arguments); }); }); }/// Simple Effect AOP
// print only for the life cycle of the page
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()} ');
        }
        returneffect? .call(action, ctx); }; }; }; }Copy the code

Home page

Based on FishRedux’s idea, we defined the home page structure as a single large page (MainPage) with two large pages (SecondPage&IndexPage) in a pageView.

view

Widget buildView(MainState state, Dispatch dispatch, ViewService viewService) {
  / / / render 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)); }Copy the code

and so on

There doesn’t seem to be anything else to pay attention to except TabController and how the page needs to be kept alive:

Define your own TabController

Refer to the previous article: Using TabController in fishRedux

Page to keep alive

In the ordinary course of STF page, we need to keep the page, only need to implement AutomaticKeepAliveClientMixin * * * * :

class _TestPageState extends State<testPage> with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    // implement the super method
    super.build(context);
    return Container();
  }

  / / / return true
  @override
  bool get wantKeepAlive => true;
}
Copy the code

In fishRedux, we need to wrap the page in a keepWidget:

import 'package:flutter/material.dart';
/// the package class that holds the state
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)
Copy the code

Adapter writing

If we look at the layout of the home page, it is clear that it consists of several cells:

  • banner
  • Public id classification gridView
  • Top recommended
  • Project recommend
  • Home page chapter pages

If in Android, it is obviously a RecyclerView+itemType composition; In the Flutter native, it’s obviously a ListView+ItemBuilder divided by item. In FishRedux, we split the page. The page consists of a SingleScrollView, And the bannerComponent, classifyComponent, projectComponent is a cell of it, The main articleComponent, which comes with loadMore and Refresh from the parent component (the entire page can be made up of a ListView), takes a look at the layout hierarchy:

Where Index_view is:

child: CustomScrollView(
      slivers: <Widget>[
        SliverToBoxAdapter(
          child: viewService.buildComponent('banner'),
        ),
        SliverToBoxAdapter(
          child: viewService.buildComponent('classify'),
        ),
        SliverToBoxAdapter(
          child: viewService.buildComponent('hotArticle'),),),Copy the code

adapter

On the front page we need to pay attention to the Adapter of the front page article, which belongs to the DynamicFlowAdapter, and other things

  • StaticFlowAdapter
  • CustomAdapter
  • 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); }}Copy the code

Let’s analyze this a little bit:

  1. We are inpoolDefines the route to the Component
  2. In the get method of _ArticleAdapterConnector we return a List of ItemBeans whose type is the component we defined in advance. Data is the state of each component (the state of each component should be a subset of page)
  3. over

Personal page & login page

I was going to write code for other pages, but in fact they are all extensions of the code for the personal home page. There are no difficulties, but the only awkward point is the explosion of code. There is another point that I forgot how to use fishRedux at the beginning, for example:

  1. How do I write action?
  2. Write logic in Effect or Reducer?
  3. What’s the best way to write my pages?
  4. Oh, my God, my tabController
  5. . Here my sunflower treasure book on, I put the following text into a TXT, on the desktop, forget to open a look:
Action Defines the actions that take place on the page, such as logging in, clearing input fields, changing verification codes, and so on. The payload parameter can also be used to transmit values that cannot be transmitted by state. The dart file is defined in fish_redux to handle side effects, such as pop-ups, network requests, and database queries. This dart file is used to register routes and register effects, Reducer, Component, and Adapter. Reducer This DART file is used to update the View, i.e. directly manipulate the View state. State The state defines the data on the page and stores the page status and data. View view is clearly the page displayed inside a flutter to the user.Copy the code

conclusion

This app is still very rough, welcome to issue, I will continue to improve.