Flutter Provider 3.0實戰教程

背景

provider是Google I/O 2019大會宣佈的如今官方推薦的狀態管理方式, provider,語法糖是InheritedWidget,它容許在小部件樹中傳遞數據,容許咱們更加靈活地處理數據類型和數據。vue

項目地址

flutter_provider 本教程的項目源碼,歡迎stargit

爲何須要狀態管理

在進行項目的開發時,咱們每每須要管理不一樣頁面之間的數據共享,在頁面功能複雜,狀態達到幾十個上百個的時候,咱們會難以清楚的維護咱們的數據狀態,本文將以簡單計數器功能使用狀態管理來說解如何在Flutter中使用provider這個狀態管理框架github

爲何選擇Provider

上次爲你們介紹了provide,而後provide就被棄用了,不過要從provide轉provider學習成本也不高,要了解provide能夠轉Flutter UI使用Provide實現主題切換vuex

使用Provider訪問數據有兩種方式bash

  • 使用Provider.of(context),簡單易用,可是要數據發生變化時,會進行頁面級別rebuild,至關於stfulWidget
  • 使用Consumer,Consumer比Provider.of(context)複雜一點,可是對於app性能的提升卻有些很好的做用,當狀態發生變化時,widget樹會更新指定的節點,極小程度進行控件刷新,不會進行整顆widget樹的更新,詳細看下文分析。
  • Provider有泛型的優點,至關於namespace的特性,使用過vuex的應該知道namespace的重要性,它將咱們的狀態分離開來

項目地址

flutter-provider, 可參考項目中使用provider方法app

效果

FistPage

SecondPage

如何使用

添加依賴

查看 pub-install框架

  • 在pubspec.yaml中引入依賴
dependencies:
      provider: 3.0.0+1 #數據管理層
複製代碼
  • 執行
flutter packages get
複製代碼
  • 在須要使用的頁面中引入
import 'package:provider/provider.dart'
複製代碼

建立model (這才第一步)

新建 lib/store/object/CounterInfo.dart 文件less

新建 lib/store/object/UserInfo.dart 文件ide

數據模型,就不貼出代碼了post

新建 lib/store/model/CounterModel.dart 文件

import 'package:flutter/foundation.dart' show ChangeNotifier;
import '../object/CounterInfo.dart';
export '../object/CounterInfo.dart';

class Counter extends CounterInfo with ChangeNotifier {
  CounterInfo _counterInfo = CounterInfo(count: 0, totalInfo: TotalInfo(total: 2));

  int get count => _counterInfo.count;
  TotalInfo get totalInfo => _counterInfo.totalInfo;

  void increment () {
    _counterInfo.count++;
    notifyListeners();
  }

  void decrement () {
    _counterInfo.count--;
    notifyListeners();
  }
}
複製代碼

新建 lib/store/model/UserModelModel.dart 文件

import 'package:flutter/foundation.dart' show ChangeNotifier;
import '../object/UserInfo.dart';
export '../object/UserInfo.dart';

class UserModel extends UserInfo with ChangeNotifier {
  UserInfo _userInfo = UserInfo(name: '咕嚕貓不吃貓糧不吃魚');

  String get name => _userInfo.name;

  void setName (name) {
    _userInfo.name = name;
    notifyListeners();
  }
}
複製代碼

經過mixin混入ChangeNotifier,經過notifyListeners通知聽衆刷新

封裝Store (沒錯,到這裏已經要快完成全部步驟了)

新建 lib/store/index.dart 文件

import 'package:flutter/material.dart' show BuildContext;
import 'package:provider/provider.dart'
  show ChangeNotifierProvider, MultiProvider, Consumer, Provider;
import 'model/index.dart' show Counter, UserModel;
export 'model/index.dart';
export 'package:provider/provider.dart';

class Store {
  static BuildContext context;
  static BuildContext widgetCtx;

  // 咱們將會在main.dart中runAPP實例化init
  static init({context, child}) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(builder: (_) => Counter()),
        ChangeNotifierProvider(builder: (_) => UserModel(),)
      ],
      child: child,
    );
  }

  // 經過Provider.value<T>(context)獲取狀態數據
  static T value<T>(context) {
    return Provider.of(context);
  }

  // 經過Consumer獲取狀態數據
  static Consumer connect<T>({builder, child}) {
    return Consumer<T>(builder: builder, child: child);
  }
}

複製代碼

須要管理多個狀態只須要在providers添加對應的狀態

providers: [ ChangeNotifierProvider(builder: () => Counter()), ChangeNotifierProvider(builder: () => UserModel(),) ],

定義全局的Provide (倒數第二)

lib/main.dart 文件

import 'package:flutter/material.dart';
import 'package:flutter_provider/store/index.dart' show Store;
import 'package:flutter_provider/page/firstPage.dart' show FirstPage;

void main () {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('根部重建: $context');
    return Store.init(
      context: context,
      child: MaterialApp(
        title: 'Provider',
        home: Builder(
          builder: (context) {
            Store.widgetCtx = context;
            print('widgetCtx: $context');
            return FirstPage();
          },
        ),
      )
    );
  }
}
複製代碼

創建頁面 (完成)

新建 lib/page/firstPage.dart 文件

import 'package:flutter/material.dart';
import 'package:flutter_provider/store/index.dart' show Store, Counter, UserModel;
import 'package:flutter_provider/page/secondPage.dart' show SecondPage;

class FirstPage extends StatelessWidget {
  TextEditingController controller = TextEditingController();
  @override
  Widget build(BuildContext context) {
    print('first page rebuild');
    return Scaffold(
      appBar: AppBar(title: Text('FirstPage'),),
      body: Center(
        child: Column(
          children: <Widget>[
            Store.connect<Counter>(
              builder: (context, snapshot, child) {
                return RaisedButton(
                  child: Text('+'),
                  onPressed: () {
                    snapshot.increment();
                  },
                );
              }
            ),
            Store.connect<Counter>(
              builder: (context, snapshot, child) {
                print('first page counter widget rebuild');
                return Text(
                  '${snapshot.count}'
                );
              }
            ),
            Store.connect<Counter>(
              builder: (context, snapshot, child) {
                return RaisedButton(
                  child: Text('-'),
                  onPressed: () {
                    snapshot.decrement();
                  },
                );
              }
            ),
            Store.connect<UserModel>(
              builder: (context, snapshot, child) {
                print('first page name Widget rebuild');
                return Text(
                  '${Store.value<UserModel>(context).name}'
                );
              }
            ),
            TextField(
              controller: controller,
            ),
            Store.connect<UserModel>(
              builder: (context, snapshot, child) {
                return RaisedButton(
                  child: Text('change name'),
                  onPressed: () {
                    snapshot.setName(controller.text);
                  },
                );
              }
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Center(
          child: Icon(Icons.group_work)
        ),
        onPressed: () {
          Navigator.of(context)
            .push(MaterialPageRoute(builder: (BuildContext context) {
              return SecondPage();
          }));
          // Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) {
          // return SecondPage();
          // }));
        },
      ),
    );
  }
}
複製代碼

新建 lib/page/secondPage.dart 文件

import 'package:flutter/material.dart';
import 'package:flutter_provider/store/index.dart' show Store, Counter, UserModel;

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('second page rebuild');
    return Scaffold(
      appBar: AppBar(title: Text('SecondPage'),),
      body: Center(
        child: Column(
          children: <Widget>[
            RaisedButton(
              child: Text('+'),
              onPressed: () {
                Store.value<Counter>(context).increment();
              },
            ),
            Builder(
              builder: (context) {
                print('second page counter widget rebuild');
                return Text(
                  'second page: ${Store.value<Counter>(context).count}'
                );
              },
            ),
            RaisedButton(
              child: Text('-'),
              onPressed: () {
                Store.value<Counter>(context).decrement();
              },
            ),
          ],
        ),
      ),
    );
  }
}
複製代碼

細心的同窗能夠發現我在firstPage中使用獲取數據狀態所有都是經過Consumer來獲取的,在firstPage中使用了兩個store(Counter和UserModel)綁定了兩個不一樣的weiget,好處就在於:

  • 我經過+或-進行數據修改時,只會對使用Counter數據模型的widget進行更新,經過點擊change name按鈕時修改了UserModel中的name,也只會對使用了UserModel的weiget進行更新
    • firstPage中在build中進行了print('first page rebuild');
    • 在顯示數量的weiget中進行了print('first page counter widget rebuild');
    • 在顯示暱稱的weiget中進行了print('first page name Widget rebuild');

結果是first page rebuild只會在頁面初始化的時候進行打印,而操做數據增減和name修改只會從新渲染對應的weiget,下圖分別爲單獨進行一次數據修改和name修改後的控制檯輸出

firstPage的print

  • 在secondPage中對於數據的操做我經過Provider.value(context)獲取,使用較爲方便簡單,可是數據改變時,會發生頁面級別刷新
    • secondPage中build進行了print('second page rebuild');
    • 在顯示數量的weiget中進行了print('second page counter widget rebuild');

結果是second page rebuild會在頁面初始化的時候進行打印,但每次數據修改時一樣也會進行print

secondPage的print

綜上,使用Provider.value(context)會致使頁面刷新,雖然flutter會自動優化刷新,但仍是建議你們儘可能使用Consumer去獲取數據,能夠獲取最好app的性能提高

最後

歡迎更多學習flutter的小夥伴加入QQ羣 Flutter UI: 798874340

敬請關注咱們正在開發的:efoxTeam/flutter-ui

做者

相關文章
相關標籤/搜索