Flutter 框架调研报告

针对flutter的项目暂时有Bloc、Mobx、scoped_model、fish_redux、Provider 五种框架,下面我们介绍一下:

提出疑问:

1.flutter是什么?

2.这些框架是解决什么问题的,为什么会需要这种框架?/响应式状态管理器/响应式编程是一种面向数据流和变化传播的编程范式

3.每个框架有什么优缺点?

4.我们应该使用什么框架比较好?

问题1:flutter是什么?

    Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作、跨平台运行。

问题2:这些框架是解决什么问题的?

    这些框架都是来一种状态管理框架。响应式的编程框架中都会有一个永恒的主题——“状态(State)管理”,无论是在React/Vue(两者都是支持响应式编程的Web开发框架)还是Flutter中,他们讨论的问题和解决的思想都是一致的。一般的原则是:如果状态是组件私有的,则应该由组件自己管理;如果状态要跨组件共享,则该状态应该由各个组件共同的父元素来管理。

问题3:每个框架都是怎么运行的?

框架一:scoped_model

一种最单纯的状态管理工具。

image

1.创建model

数据刷新需要调用 notifyListeners方法

import 'package:scoped_model/scoped_model.dart';

class CountModel extends Model{

int _count = 0;

get count => _count;

void increment(){

_count++;

notifyListeners();

}

CountModel of(context) =>

ScopedModel.*of*<CountModel>(context);//使用ScopedModel.of方式

}

第二步:放入顶层

import 'package:flutter/material.dart';

import 'package:scoped_demo/top_screen.dart';

import 'package:scoped_demo/model/count_model.dart';

import 'package:scoped_model/scoped_model.dart';

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

class MyApp extends StatelessWidget {

//创建顶层状态

CountModel countModel = CountModel();

@override

Widget build(BuildContext context) {

return ScopedModel<CountModel>(

model: countModel,

child: new MaterialApp(

title: 'Flutter Demo',

theme: new ThemeData(

primarySwatch: Colors.*blue*,

),

home: TopScreen(),

),

);

}

}

第三步:页面使用

获取model分两种:

1. ScopedModelDescendant获取model

2.ScopedModel.of<T>(context)

import 'package:flutter/material.dart';

import 'package:scoped_demo/model/count_model.dart';

import 'package:scoped_model/scoped_model.dart';

import 'package:scoped_demo/under_screen.dart';

class TopScreen extends StatefulWidget {

@override

_TopScreenState createState() => _TopScreenState();

}

class _TopScreenState extends State<TopScreen> {

//静态获取model用法实例

Model getModel(BuildContext context){

//直接使用of

final countModel = ScopedModel.*of*<CountModel>(context);

//使用CountModel中重写的of

final countModel2 = CountModel().of(context);

countModel.increment();

countModel2.increment();

return countModel;

// return countMode2;

}

@override

Widget build(BuildContext context) {

return ScopedModelDescendant<CountModel>(

builder: (context,child,model){

return Scaffold(

appBar: AppBar(

title: Text('Top Screen'),

),

body: Center(

child: Text(

model.count.toString(),

style: TextStyle(fontSize: 48.0),

),

),

floatingActionButton: FloatingActionButton(

onPressed: () {

Navigator.*of*(context).push(MaterialPageRoute(builder: (BuildContext context){

return UnderScreen(title: "Under Screen",);

}));

},

child: Icon(Icons.*forward*),

),

);

},

);

}

}

框架二:Provider

第一步:创建数据 Model

import 'package:flutter/material.dart';

class CounterModel with ChangeNotifier {

int _count = 0;

int get value => _count;

void increment() {

_count++;

notifyListeners();

}

}

第二步:创建顶层共享数据

void main() {

final counter = CounterModel();

final textSize = 48;

runApp(

Provider<int>.value(

value: textSize,

child: ChangeNotifierProvider.value(

value: counter,

child: MyApp(),

),

),

);

}

第三步:在子页面中获取状态(Provider.of)

class FirstScreen extends StatelessWidget {

@override

Widget build(BuildContext context) {

final _counter = Provider.of<CounterModel>(context);

final textSize = Provider.of<int>(context).toDouble();

return Scaffold(

appBar: AppBar(

title: Text('FirstPage'),

),

body: Center(

child: Text(

'Value: ${_counter.value}',

style: TextStyle(fontSize: textSize),

),

),

floatingActionButton: FloatingActionButton(

onPressed: () => Navigator.of(context)

.push(MaterialPageRoute(builder: (context) => SecondPage())),

child: Icon(Icons.navigate_next),

),

);

}

}

方法二:

class SecondPage extends StatelessWidget {

@override

Widget build(BuildContext context) {

return Scaffold(

appBar: AppBar(

title: Text('Second Page'),

),

body: Consumer2<CounterModel,int>(

builder: (context, CounterModel counter, int textSize, _) => Center(

child: Text(

'Value: ${counter.value}',

style: TextStyle(

fontSize: textSize.toDouble(),

),

),

),

),

floatingActionButton: Consumer<CounterModel>(

builder: (context, CounterModel counter, child) => FloatingActionButton(

onPressed: counter.increment,

child: child,

),

child: Icon(Icons.add),

),

);

}

}

框架三:Bloc (https://pub.dev/packages/flutter_bloc

|

image

|

image

使用:

第一步,创建一个bloc:内部包含sink、stream

Sink作用:进行事件触发,进行业务处理获取数据流

Stream作用:数据监听,用于widget监听数据变化的Observable。

如下:

import 'dart:async';

class CountBLoC {

int _count = 0;

var _countController = StreamController<int>.broadcast();

Stream<int> get stream => _countController.stream;

Sink<int> get sink => _countController.sink;

int get value => _count;

increment() {

sink.add(++_count);

}

dispose() {

_countController.close();

}

}

第二步:Scoped模式创建Bloc

创建bloc分三种:下面是Scoped方式

  • 全局单例创建
  • 局部创建
  • scoped

import 'package:flutter/material.dart';

import 'package:bloc_demo/scoped/blocs/count_bloc.dart';

class BlocProvider extends InheritedWidget {

CountBLoC bLoC = CountBLoC();

BlocProvider({Key key, Widget child}) : super(key: key, child: child);

@override

bool updateShouldNotify(_) => true;

//这里真正项目中可以使用范性创建对象

static CountBLoC *of*(BuildContext context) =>

(context.inheritFromWidgetOfExactType(BlocProvider) as BlocProvider).bLoC;

}

第三步:页面中使用方式

import 'package:flutter/material.dart';

import 'blocs/bloc_provider.dart';

class UnderPage extends StatelessWidget {

@override

Widget build(BuildContext context) {

final bloc = BlocProvider.*of*(context);//创建被观察者对象

print('build');

return Scaffold(

appBar: AppBar(

title: Text('Under Page'),

),

body: Center(

child: StreamBuilder(

stream: bloc.stream, //赋值观察者

initialData: bloc.value,

builder: (context, snapshot) => Text(

"You hit me: ${snapshot.data} times",

style: Theme.*of*(context).textTheme.display1,

)),

),

floatingActionButton: FloatingActionButton(

onPressed: () => bloc.increment(),//触发调用逻辑,这里的触发会调用bloc中的sink

child: Icon(Icons.*add*),

),

);

}

}

StreamBuilder中stream参数代表了这个stream builder监听的流,我们这里监听的是countBloc的value(它是一个stream)。

initData代表初始的值,因为当这个控件首次渲染的时候,还未与用户产生交互,也就不会有事件从流中流出。所以需要给首次渲染一个初始值。

builder函数接收一个位置参数BuildContext 以及一个snapshot。snapshot就是这个流输出的数据的一个快照。我们可以通过snapshot.data访问快照中的数据。也可以通过snapshot.hasError判断是否有异常,并通过snapshot.error获取这个异常。

StreamBuilder中的builder是一个AsyncWidgetBuilder,它能够异步构建widget,当检测到有数据从流中流出时,将会重新构建。

总结:

1.页面触发事件,触发bloc中方法发起业务逻辑,并获取相关数据,

2.再由bloc中的sink进行数据返回,数据将发送到具体页面(page)下的stream的监听下

3.由AsyncSnapshot对象将数据状态下发到具体的控件,并触发UI的重绘

框架四:Mobx

第一步:创建可观察对象

import 'package:mobx/mobx.dart';

part 'counter.g.dart';

class Counter = CounterBase with _$Counter;

final Counter counter = Counter();

abstract class CounterBase implements Store {

@observable

int value = 0;

@action

void increment() {

value++;

}

@action

void decrement() {

value--;

}

@action

void set(int value) {

this.value = value;

}

}

mobx会根据

part 'counter.g.dart';

class Counter = CounterBase with _$Counter;

生成对应的.g.dart文件

第二步:页面使用:

import 'package:flutter/material.dart';

import 'package:flutter_mobx/flutter_mobx.dart';

import 'package:flutter_mobx_project/mobx/counter.dart';

class SecondPage extends StatefulWidget {

@override

_SecondPageState createState() => _SecondPageState();

}

class _SecondPageState extends State<SecondPage> {

@override

Widget build(BuildContext context) {

return Scaffold(

appBar: AppBar(

title: Text('second page'),

),

body: Center(

child: Observer(

builder: (_) => Text(

'count is ${counter.value}',

style: TextStyle(fontSize: 30),

),

),

),

floatingActionButton: FloatingActionButton(

child: Icon(Icons.*add*),

onPressed: counter.increment,

),

);

}

}

框架五:fish_redux(https://pub.dev/packages/fish_redux/install)(https://github.com/alibaba/fish-redux

理解fish redux的组成

无法复制加载中的内容

|

image

|

image

|

image

  1. 用户点击勾选框,GestureDetector的onTap会被回调
  2. 通过buildView传入的dispatch函数对doneAction进行分发,发现todo_component的effect中无法处理此doneAction,所以将其交给pageStore的dispatch继续进行分发
  3. pageStore的dispatch会将action交给reducer进行处理,故doneAction对应的_markDone会被执行,对state进行clone,并修改clone后的state的状态,然后将这个全新的state返回
  4. 然后pageStore的dispatch会通知所有的listeners,其中负责界面重绘的_viewUpdater发现state发生变化,通知界面进行重绘更新
image

https://zhuanlan.zhihu.com/p/62588540

4.我们应该使用什么框架比较好?

之前没有接触过flutter可以先使用bloc,熟悉了flutter的一套语法及开发速度可以切换到fish-redux中

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容