Flutter狀態管理之Provider的理解使用

夏天舒服發表於2019-09-29

首先,當然可以看看,官方文件翻譯參考

一、為什麼需要Provider管理狀態

資料變化,資料共享,需要Provider

  • Flutter的程式碼,是響應式/宣告式的。以前安卓/iOS的程式碼,是命令式的。
  • 響應式的程式碼,基本都需要進行狀態管理,也可以理解為資料共享
  • 介面、資料是變化的,就需要管理的,簡單的直接在StatefulWidget進行管理就好,複雜的就是用Provider之類來管理

簡單和複雜資料的例子

資料的變化,怎麼算簡單的呢?——比如一個 PageView 元件中的當前頁面、一個複雜動畫中當前進度、一個 BottomNavigationBar 中當前被選中的 tab。這些在widget 樹,其他部分不需要訪問這種狀態。不需要去序列化這種狀態,這種狀態也不會以複雜的方式改變。

什麼資料變化需要Privider來管理呢?舉例子,比如,使用者選項、登入資訊、一個社交應用中的通知、一個電商應用中的購物車、一個新聞應用中的文章已讀/未讀狀態

.
.

Flutter的狀態管理有ReduxRxhooksScopedModel, 和Provider等,其中Provider是官方推薦的。

如果你不瞭解其他的,那肯定是官方推薦的優先。

二、Provider的使用

嗯,Provider是官方推薦的。不熟悉的,可以看看這兩篇先。

Provider官方文章

使用 Provider 管理 Flutter 應用狀態


二.1 使用步驟

引入 provider

dependencies:
  provider: ^3.1.0
複製程式碼

關於 provider 的使用可以簡單理解為3步:

  • 建立繼承自 ChangeNotifier 的共享類
  • 設定資料
  • 獲取資料,2種方式,分別是Provider.of(context) 和 Consumer

很簡單吧~

.
.
.

二.2 最簡單的例子 Provider.of(context) 方式

以下程式碼:

  • 建立共享類,新增一個增長資料的方法,呼叫就重新整理
  • 呼叫資料
  • build重新呼叫,重新整理ui

import 'package:flutter/material.dart';

import 'package:provider/provider.dart';
import 'package:flutter/foundation.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 建立 Widget 持有 CounterNotifier 資料
    return ChangeNotifierProvider.value(
      value: CounterNotifier(),
      child: MaterialApp(
        title: 'Privoder Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: ProvidePage(title: 'Provider 測試頁面'),
      ),
    );
  }
}

class ProvidePage extends StatelessWidget {
  final String title;

  ProvidePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {

    // 獲取 CounterNotifier 資料 (最簡單的方式)
    final counter = Provider.of<CounterNotifier>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '按下按鈕,使數字增長:',
            ),
            Text(
              '${counter.count}',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counter.increment();
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}


//  核心:繼承自ChangeNotifier
// 這種檔案本來應該單獨放在一個類檔案連的
class CounterNotifier with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  increment() {
    _count++;
    // 核心方法,通知重新整理UI,呼叫build方法
    notifyListeners();
  }
}
複製程式碼

.
.

效果:

Flutter狀態管理之Provider的理解使用

注意

  • 使用 Provider.of 當ChangeNotifier 中呼叫 notifyListeners 時每次會重新呼叫 Widget 中的 build

大概如此了。你可能誰說,直接 setState 就好了。不,這肯定不一樣,setState太不靈活了。

.
.
.

二.3 例子 Consumer 方式

例子:在頁面一設定1個值,然後在頁面2顯示出來 (不要問為什麼不直接用Navigator,演示演示,只為演示)

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

main() {
  runApp(ChangeNotifierProvider<CounterNotifier>.value(
    value: CounterNotifier(),
    child: MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MultiProvider(
      providers: [
        Provider.value(value: 36),
        ChangeNotifierProvider.value(value: CounterNotifier())
      ],
      child: MaterialApp(
        title: 'Privoder Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Page1(),
      ),
    );

 }
}

class Page1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //獲取文字大小
    final size = Provider.of<int>(context).toDouble();
    // 獲取計數
    final counter = Provider.of<CounterNotifier>(context);
    // 呼叫 build 時輸出
    print('rebuild page 1');
    return Scaffold(
      appBar: AppBar(
        title: Text('Page1'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // 顯示計數
            Text(
              'Current count: ${counter.count}',
              // 設定文字大小
              style: TextStyle(
                fontSize: size,
              ),
            ),
            SizedBox(
              height: 50,
            ),
            // 跳轉 Page2
            RaisedButton(
              onPressed: () => Navigator.of(context).push(
                MaterialPageRoute(builder: (context) => Page2()),
              ),
              child: Text('Next'),
            ),
          ],
        ),
      ),
    );
  }
}


class Page2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('rebuild page 2');
    return Scaffold(
      appBar: AppBar(
        title: Text('Page2'),
      ),
      body: Center(
        child: Consumer2<CounterNotifier, int>(
            builder: (context, counter, size, _) {
              print('rebuild page 2 refresh count');

              return Column(
                mainAxisSize: MainAxisSize.min,
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text(
                    'You have pushed the button this many times:',
                  ),
                  Text(
                    '${counter.count}',
                    style: TextStyle(
                      fontSize: size.toDouble(),
                    ),
                  ),
                ],
              );
            }),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 不需要監聽改變(listen: false 不會重新呼叫build)
          Provider.of<CounterNotifier>(context, listen: false).increment();
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}


class CounterNotifier with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  increment() {
    _count++;
    notifyListeners();
  }
}


複製程式碼

.

注意

  • 1、在Page2中,使用了Consumer
  • 2、Provider.of的listen如果為false,不會重新呼叫build

.
頁面2設定數值,返回頁面1時,頁面1顯示的是頁面2的數值

Flutter狀態管理之Provider的理解使用

.
.

Flutter狀態管理之Provider的理解使用

.

對比
  • 觸發者(Provider.of):如果只是需要獲取到資料model,不需要監聽變化(例如點選按鈕),推薦使用Provider.of(context, listen: false)來獲取資料model。
  • 監聽者(推薦使用Consumer):推薦使用Consumer。

.
.

END

參考: Flutter開始干係列-狀態管理Provider3


相關文章