Flutter Redux详细基础使用教程

Flutter Redux使用教程

网上的一些教程就是现从官方文档的最基础例子搬运过来的,无法给偏向基础的新手讲清楚Flutter_Redux,也无法让新人设置自己的项目结构。博主结合官网例子,和一些大神的项目,总结了一些使用经验,希望能帮助到你。

依赖配置

​ 在项目的根路径,找到pubspec.yaml文件。这个文件用来定义我们Flutter项目的依赖,下图是还没有配置redux的原始文件。

在这里插入图片描述

dependencies下配置生产环境(也就是项目上线的环境)所需的依赖。dev_dependencies配置我们开发环境所需的依赖。显然,我们所要使用的redux必须要装在生产环境下。

​ 将pubspec.yaml文件的dependencies修改成如下:

dependencies:
  flutter:
    sdk: flutter
  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  #下面两个就是我们所需要配的依赖
  redux: ^3.0.0
  flutter_redux: ^0.5.3

修改完成后,我们需要安装这些依赖。

在项目根目录执行flutter pub get命令安装。

输出下面这段话,则安装成功。

Running "flutter pub get" in flutter_redux... 0.6s

这里说一个题外话,一般我们使用flutter是从国外的pub仓库下载依赖,这会导致有的是时候下载巨慢。但是根据官方说的,我们将下载源替换为https://pub.flutter-io.cn的时候也不见得多快,这就会导致我们在使用flutter upgrade更新flutter,或者使用flutter channel stable更换分支的时候速度贼慢。

所以我们把这些地址通通替换为清华大学的镜像源。

Flutter国内镜像

windows 使用方式

​ 右键我的电脑->属性->高级->环境变量。在系统变量中新建两个项目。

变量
FLUTTER_STORAGE_BASE_URL https://mirrors.tuna.tsinghua.edu.cn/flutter
PUB_HOSTED_URL https://mirrors.tuna.tsinghua.edu.cn/dart-pub/

保存。重新开启cmd,即可飞速体验~

linux使用方式

下面两句命令分别执行一下,我这里写到了.bashrc文件里面,如果你用的是zsh的话,写到相应的~/.zshrc文件中即可。

echo 'export FLUTTER_STORAGE_BASE_URL="https://mirrors.tuna.tsinghua.edu.cn/flutter"' >> ~/.bashrc

echo 'export PUB_HOSTED_URL="https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"' >> ~/.bashrc

Mac使用方式

我没有Mac所以这里不写了

思路都是一样的修改Mac的环境变量即可。

开始

​ 惯例啰嗦一下。为了大家能更快上手,所以我只会完成两种功能实现:主题的更换ListTitl的添加。虽然例子比较少,但是也都属于比较典型的问题。

Redux的一些概念

​ 如果从来没接触过Redux的flutter初学者可能一上来就懵逼了,什么storereduceractionStatemodel这些概念直接就把我们干懵逼了,所以这里我们最好了解一下这些概念,努力看,如果看不懂也只是我讲的不到位,一会看到代码我相信大家都能豁然开朗~

store:全局状态管理者,一个程序应该只有一个。

state:状态,状态是复杂的数据结构。比如下面这种。

[
    {
        "id":0,
        "content":"1233213",
        "title":"haha"
    },
    {
        "id":1,
        "content":"1233213",
        "title":"xixi"
    }
]

action:从应用层发出的,通向stroe的唯一来源。一般承载着要更新的数据

model:实际页面数据的抽象。

reducer:指定了应用状态的变化如何响应action。我们在这里定义更新操作。

​ 我们这里可以不求甚解。往下看代码我们自然明白这些概念的实际意义。

创建ThemeModel

​ 在项目的lib/文件夹下面创建文件夹model。这里面存放我们以后项目中所有的model。在model中创建theme_model.dart文件,并写以下代码。

import 'package:flutter/material.dart';

class ThemeModel{
  ThemeData themeData;

  ThemeModel({
    this.themeData,
  });
}

创建全局State文件

​ 在项目lib/文件夹下,新建app_state.dart文件,并写以下代码。

import 'package:flutter/material.dart';

import 'model/theme_model.dart';

class AppState {
  ThemeModel themeState; //保存我们的主题状态
  AppState({this.themeState});
  /*
   * 命名的构造方法
   * 这里用来初始化
   */
  AppState.initialState() {
    themeState = ThemeModel(themeData: ThemeData.dark());
  }
}

创建Action

​ 在lib/文件夹下新建actions文件夹,进入actions文件夹,并新建theme_action.dart文件,输入以下代码。

import 'package:flutter/material.dart';

import '../model/theme_model.dart';

class SetThemeDataAction {
  ThemeData themeData;

  SetThemeDataAction({this.themeData}) : super();
  /**
   * 设置themedata主题
   */
  static ThemeModel setTheme(ThemeModel theme, SetThemeDataAction action) {
    theme?.themeData = action?.themeData;
    return theme;
  }
}

设置Reducer

​ 这一步可能不好理解,我来解释一下,Action是由组件发出,并有store接受的信号,但是我们必须规定store接收到Action后应该干什么,这一层操作就是由Reducer完成的。

​ 首先,修改我们的theme_action.dart文件。

import 'package:flutter/material.dart';
import 'package:redux/redux.dart';

import '../model/theme_model.dart';

class SetThemeDataAction {
  ThemeData themeData;

  SetThemeDataAction({this.themeData}) : super();
  /**
   * 设置themedata主题
   */
  static ThemeModel setTheme(ThemeModel theme, SetThemeDataAction action) {
    theme?.themeData = action?.themeData;
    return theme;
  }
}
//下面的代码加上
/*
 * 绑定Action与动作
 */
final ThemeReducer = combineReducers<ThemeModel>([
  TypedReducer<ThemeModel, SetThemeDataAction>(SetThemeDataAction.setTheme),
]);

​ 然后修改app_state.dart文件

import 'package:flutter/material.dart';

import 'actions/theme_action.dart';
import 'model/theme_model.dart';

class AppState {
  ThemeModel themeState; //保存我们的主题状态
  AppState({this.themeState});
  /*
   * 命名的构造方法
   * 这里用来初始化
   */
  AppState.initialState() {
    themeState = ThemeModel(themeData: ThemeData.dark());
  }
}
/**
 * 定义Reducer
 */
AppState appReducer(AppState state, action) {
  return AppState(themeState: ThemeReducer(state.themeState, action));
}

​ 我们分了两个文件来定义Reducer,这样做的好处是高内聚,低耦合ThemeReducer全部交由theme_action.dart文件管理,我们再增加Theme的Action的时候,只需要去修改theme_action.dart文件,并定义相应的Reducer即可。


ok,到这里我们的框架,项目结构已经是完成了。现在开始在flutter组件中使用Redux。

开始在组件中使用

redux在flutter中使用,一般使用下面这几个组件

Store:创建store必不可少的组件。

StoreProvider:用来存放store,供子孙元素获取store来的。

StoreBuilder:从StoreProvider获取store并将其传递给Widget builder函数的后代Widget。一般用在StoreProvider的正下方。

StoreConnector :从最近的StoreProvider祖先元素获取store,并将store转换为ViewModel,来使用。这点我一会讲。


​ 所以看了上面的介绍,我们的思路应该是这样的,直接将flutter的根元素替换为StoreProvider,当我们需要发出Action的时候或者需要使用store的时候,我们对这个组件进行StoreConnector包裹。

​ 拷贝下面的代码到你的main.dart文件中,你会发现,已经可以获取到store中的状态了。因为我这里启动的是flutter自带的例子,里面有很多初始代码,我也不删了,我们redux的核心代码全在MyAppclass的build方法中了。

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:my_flutter_redux/app_state.dart';
import 'package:redux/redux.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final store =
        Store<AppState>(appReducer, initialState: AppState.initialState());//创建 store
    return StoreProvider(//使用StoreProvider 包裹根元素,使其提供store
      store: store,//我们的store
      child: StoreBuilder<AppState>(//为了能直接在child使用store,我们这里要继续包裹一层StoreBuilder
        builder: (context, store) {
          return MaterialApp(
            title: 'Flutter Demo',
            theme: store.state.themeState.themeData,//这里使用我们的stroe
            home: MyHomePage(title: 'Flutter Demo Home Page'),
          );
        },
      ),
    );
  }
}
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

效果:
在这里插入图片描述
​ 现在启动flutter程序,我们的例子已经换成dark主题了,现在我们设计一个功能,在点击右下角的按钮时,不断切换主题。

切换主题

​ 先说思路,我们肯定要在按钮的点击事件中获取store,并且发出Action。好的,思路有了,开始干。

​ 在组件中想要获取store的话,我们要用StoreConnector 包裹需要获取stroe的组件。我们这里要实现点击按钮切换主题,那肯定需要包裹FloatingActionButton啦。

floatingActionButton: StoreConnector<AppState, _ViewModel>(
    converter: (store) => _ViewModel.create(store),
    builder: (context, viewModel) {
        return FloatingActionButton(
            onPressed: () {
                _incrementCounter();
                viewModel.onSetThemeData(viewModel.themeData == ThemeData.dark()
                                         ? ThemeData.light()
                                         : ThemeData.dark());
            },
            tooltip: 'Increment',
            child: Icon(Icons.add),
        );
    },
)
//----其他代码
//在最后 写上
class _ViewModel {
  ThemeData themeData;
  Function(ThemeData) onSetThemeData;
  _ViewModel({
    this.themeData,
    this.onSetThemeData,
  });
  factory _ViewModel.create(Store<AppState> store) {
    _onSetThemeData(ThemeData themeData) {
      store.dispatch(SetThemeDataAction(themeData: themeData));
    }
    return _ViewModel(
      themeData: store.state.themeState.themeData,
      onSetThemeData: _onSetThemeData,
    );
  }
}

拒绝弹射起步,我们一句一句讲解。

StoreConnector<AppState, _ViewModel>这句话看到StoreConnector接收两个泛型,<AppState, _ViewModel>第一个参数是state的类型,一般我们这种使用全局State那么填我们定义的AppState就行。

第二个参数,_ViewModelbuilder属性接收的类型,一般由我们自己定义。把它理解为中间视图层。

converter: (store) => _ViewModel.create(store),

converter需要返回StoreConnector<AppState, _ViewModel>第二个泛型的类型,这里我们返回_ViewModel的一个实例。

builder: (context, viewModel)第一个返回上下文,这里一般就写context就可以,关键是第二个viewModel这里接受converter返回的值,也就是一个_ViewModel实例。

并且builder需要返回Widget,这个Widget可以使用viewModel,这个实参。

最后,在点击事件中加入我们的事件就成了。

viewModel.onSetThemeData(viewModel.themeData == ThemeData.dark()
                                         ? ThemeData.light()
                                         : ThemeData.dark());

第二个例子:动态添加Titl

创建Model

​ 每次引入新的、复杂的要管理的状态的时候,最好是创建一个Model类,这样与后端获取数据后,Json转对象,查看数据类型,都会非常方便。

​ 当然,你如果只是接受一个bool值,或者num类型的值得话,就没有必要创建Model。

我们在lib/model文件夹中创建article_model.dart文件,用来定义我们的ArticleModel类。

class ArticleModel {
  String author;

  num id;

  String title;

  ArticleModel({this.id, this.title, this.author});
}

​ 这个类的数据结构,大家可以根据自己的需要随意改,而且一般在这个类,我们会放Json序列化方法。这里我只是简单规定了一下数据结构,id用来表示从后端获取的主键,一般用这个跳转路由,获取数据。anthor用来标识作者。title表示文章名字。

​ 不要问我为什么没有content字段,编不下去。

创建Action与Reducer

​ 定义完Model后我们要来定义这个状态的ActionReducer。一般我是习惯这两个东西放到一个文件里面,比较方便管理。

​ 在lib/actions中创建article_action.dart文件。并写以下代码。

import 'package:my_flutter_redux/model/article_model.dart';
import 'package:redux/redux.dart';

class AddArticleItemAction {
  ArticleModel item;
  AddArticleItemAction({this.item});

  static List<ArticleModel> addArticleItem(
      List<ArticleModel> list, AddArticleItemAction action) {
    list?.add(action?.item);
    return list;
  }
}

class RemoveArticleItemAction {
  ArticleModel item;
  RemoveArticleItemAction({this.item});

  static List<ArticleModel> removeArticleItem(
      List<ArticleModel> list, RemoveArticleItemAction action) {
    list.remove(action.item);
    return list;
  }
}
/*
 * 绑定Action与动作
 */
final ArticlesReducer = combineReducers<List<ArticleModel>>([
  TypedReducer<List<ArticleModel>, AddArticleItemAction>(
      AddArticleItemAction.addArticleItem),
  TypedReducer<List<ArticleModel>, RemoveArticleItemAction>(
      RemoveArticleItemAction.removeArticleItem),
]);

​ 我们定义了两个Action类,一个是AddArticleItemAction,它对应addArticleItem方法,这个Action主要是向我们的List<ArticleModel>状态中添加成员。而RemoveArticleItemAction中的removeArticleItem则是删除成员。相信大家都可以看懂。

​ 定义完Action后,我们一定不要忘记用ArticlesReducer,将Action与所要做的动作联系到一起,不然的话,从组件发出Action但却没有动作回应。

在全局State中设置状态

​ 上面这些步骤都整完后,还差最后关键的一步。没错,就是我们从始至终都没有去全局State中定义我们的文章状态

​ 找到lib/app_state.dart文件。

import 'package:flutter/material.dart';
import 'package:my_flutter_redux/actions/article_action.dart';
import 'package:my_flutter_redux/model/article_model.dart';

import 'actions/theme_action.dart';
import 'model/theme_model.dart';

class AppState {
  ThemeModel themeState; //保存我们的主题状态
  List<ArticleModel> articleListState; //保存文章list状态
  AppState({this.themeState, this.articleListState});
  /*
   * 命名的构造方法
   * 这里用来初始化
   */
  AppState.initialState() {
    themeState = ThemeModel(themeData: ThemeData.dark());
    articleListState = List<ArticleModel>();
  }
}

/**
 * 定义Reducer
 */
AppState appReducer(AppState state, action) {
  return AppState(
    themeState: ThemeReducer(state.themeState, action),
    articleListState: ArticlesReducer(state.articleListState, action),
  );
}

​ 大家来找茬。

创建页面

​ ok,到这里已经是万事俱备只欠东风了。我们只需要使用State就行了。我这里是在主页添加了一个跳转页面按钮,通过设置好的路由跳转到新页面。

​ 所以我们改一下main.dart文件吧。

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:my_flutter_redux/app_state.dart';
import 'package:my_flutter_redux/model/theme_model.dart';
import 'package:redux/redux.dart';

import 'actions/theme_action.dart';
import 'pages/articles_page.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final store =
        Store<AppState>(appReducer, initialState: AppState.initialState());
    return StoreProvider(
      store: store,
      child: StoreBuilder<AppState>(
        builder: (context, store) {
          return MaterialApp(
            title: 'Flutter Demo',
            theme: store.state.themeState.themeData,
            routes:<String, WidgetBuilder>{//++++++++++++++++++++++新增了路由
              "/article":(BuildContext context) => ArticlePage(),
            } ,
            home: MyHomePage(title: 'Flutter Demo Home Page'),
          );
        },
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'You have pushed the button this many times:',
              ),
              Text(
                '$_counter',
                style: Theme.of(context).textTheme.display1,
              ),
              RaisedButton(child: Text("切换页面"),onPressed: (){//++++++++++++新增了跳转页面按钮
                Navigator.of(context).pushNamed('/article');
              },)
            ],
          ),
        ),
        floatingActionButton: StoreConnector<AppState, _ViewModel>(
          converter: (store) => _ViewModel.create(store),
          builder: (context, viewModel) {
            return FloatingActionButton(
              onPressed: () {
                _incrementCounter();
                viewModel.onSetThemeData(viewModel.themeData == ThemeData.dark()
                    ? ThemeData.light()
                    : ThemeData.dark());
              },
              tooltip: 'Increment',
              child: Icon(Icons.add),
            );
          },
        ));
  }
}

class _ViewModel {
  ThemeData themeData;
  Function(ThemeData) onSetThemeData;
  _ViewModel({
    this.themeData,
    this.onSetThemeData,
  });
  factory _ViewModel.create(Store<AppState> store) {
    _onSetThemeData(ThemeData themeData) {
      store.dispatch(SetThemeDataAction(themeData: themeData));
    }


    return _ViewModel(
      themeData: store.state.themeState.themeData,
      onSetThemeData: _onSetThemeData,
    );
  }
}

​ 然后,我们创建新的页面,在lib/pages中新建articles_page.dart文件。并写以下代码。

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:my_flutter_redux/actions/article_action.dart';
import 'package:my_flutter_redux/app_state.dart';
import 'package:my_flutter_redux/model/article_model.dart';
import 'package:redux/redux.dart';

class ArticlePage extends StatefulWidget {
  State<StatefulWidget> createState() => _ArticlePageState();
}

class _ArticlePageState extends State<ArticlePage> {
  @override
  Widget build(BuildContext context) {
    List<num> list = List(5);
    return Scaffold(
      appBar: AppBar(
        title: Text("添加titl"),
        actions: <Widget>[
          StoreConnector<AppState, _ViewModel>(
            converter: (store) => _ViewModel.create(store),
            builder: (context, viewModel) {
              return IconButton(
                icon: Icon(
                  Icons.add,
                  size: 30.0,
                ),
                onPressed: () {
                  viewModel.onAddItem(
                      ArticleModel(id: viewModel.articleList.length, title: "哈哈哈", author: "脑瘫码农"));
                },
              );
            },
          )
        ],
      ),
      body: StoreConnector<AppState, _ViewModel>(
        converter: (store) => _ViewModel.create(store),
        builder: (context, viewModel) {
          return ListView.builder(
            itemBuilder: (contentx, index) {
              return ListTile(
                  subtitle: Text((viewModel.articleList[index].id).toString()),
                  title: Text(viewModel.articleList[index].title),
                  leading: Text(viewModel.articleList[index].author),
                  trailing: IconButton(
                    icon: Icon(Icons.delete),
                    onPressed: (){
                      viewModel.onRemoveItem(viewModel.articleList[index]);
                    },
                  ));
            },
            itemCount: viewModel.articleList.length,
          );
        },
      ),
    );
  }
}

class _ViewModel {
  List<ArticleModel> articleList;

  Function(ArticleModel) onAddItem;

  Function(ArticleModel) onRemoveItem;

  _ViewModel({this.articleList, this.onAddItem, this.onRemoveItem});

  factory _ViewModel.create(Store<AppState> store) {
    _onAddItem(ArticleModel item) {
      store.dispatch(AddArticleItemAction(item: item));
    }

    _onRemoveItem(ArticleModel item) {
      store.dispatch(RemoveArticleItemAction(item: item));
    }

    return _ViewModel(
        articleList: store.state.articleListState,
        onAddItem: _onAddItem,
        onRemoveItem: _onRemoveItem);
  }
}

结尾

​ 我虽然是刚开始学习Flutter的菜鸟,但是也走不少弯路,我希望把我的经验分享给大家。但是人的力量是有限的,文章难免不出现错误。如果看到这篇文章有错误,请务必给我评论,我会第一时间查证修改,谢谢大家了。

声明

脑瘫码农 纯属自学 如有错误 望请指正 共同学习 不胜感激

发布了31 篇原创文章 · 获赞 38 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/yhy1315/article/details/102471140
今日推荐