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

Joker_Fu發表於2019-09-19

直接開始幹,沒有為什麼~


上一篇看了官方的預設的計數 Demo ,在 2019 Google I/O 大會中, Provider 代替 Provide 成為官方推薦的狀態管理方式之一。

Provider 可以幹什麼?引用官方的一段話:

By using widgets for state management, provider can guarantee:

maintainability, through a forced uni-directional data-flow testability/composability, since it is always possible to mock/override a value robustness, as it is harder to forget to handle the update scenario of a model/widget

個人總結:提高專案可維護性,使結構更加清晰,重要的是提供單向資料流,實現更小單元的重新整理,提升效能。

接下來就用 Provider 改造下預設的技術Demo。

新增 Provider 依賴

在 pubspec.yaml 中增加 provider: ^3.1.0 ,然後點選 右上角 Packages get 就可以了,這裡說明下:

^ 表示適配和當前大版本一致的版本,~ 表示適配和當前小版本一致的版本,所以這裡的 ^ 可以不要。

dependencies:
  flutter:
    sdk: flutter

  ...
  
  # Provider https://github.com/rrousselGit/provider
  provider: ^3.1.0
複製程式碼

建立 ChangeNotifier

import 'package:flutter/foundation.dart';

class CounterNotifier with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  increment() {
    _count++;
    notifyListeners();
  }
}
複製程式碼

使用 Provider.of 實現

  • 使用 ChangeNotifierProvider.value 建立 Widget 持有 value (CounterNotifier())
  • 使用 Provider.of 獲取 value (CounterNotifier())
import 'package:flutter/material.dart';
import 'package:flutter_provider_demo/CounterNotifier.dart';
import 'package:provider/provider.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 Demo Home Page'),
      ),
    );
  }
}

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(
              'You have pushed the button this many times:',
            ),
            Text(
              '${counter.count}',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counter.increment();
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

複製程式碼

這樣就完成了使用 Provider 對預設計數 Demo 改造,使用 Provider.of 當ChangeNotifier 中呼叫 notifyListeners 時每次會重新呼叫 Widget 中的 build

示例1

使用 Consumer 實現

這裡實現個稍微複雜點的,建立 Page1 顯示 current count,然後建立 Page2 實現官方預設點選計數,看看 Provider 狀態管理效果。

構建 MyApp

  1. 這裡使用 MultiProvider 新增多個 Provider,當然也可以使用註釋掉的巢狀方式,願意的話
  2. Provider.value(value: 36) 用作字型大小
  3. ChangeNotifierProvider.value(value: CounterNotifier()) 用作 count 計數
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
//    return Provider.value(
//      value: 36,
//      child: ChangeNotifierProvider.value(
//        value: CounterNotifier(),
//        child: MaterialApp(
//          title: 'Privoder Demo',
//          theme: ThemeData(
//            primarySwatch: Colors.blue,
//          ),
//          home: Page1(),
//        ),
//      ),
//    );
    return MultiProvider(
      providers: [
        Provider.value(value: 36),
        ChangeNotifierProvider.value(value: CounterNotifier())
      ],
      child: MaterialApp(
        title: 'Privoder Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Page1(),
      ),
    );
  }
}
複製程式碼

構建 Page1

  1. 使用 Provider.of(context).toDouble() 獲取文字大小
  2. 使用 Provider.of(context) 獲取計數
  3. 使用 Colum 列表方式展示計數和跳轉頁面按鈕
  4. 當 build的時候 print('rebuild page 1');
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'),
            ),
          ],
        ),
      ),
    );
  }
}
複製程式碼

構建 Page2

  1. 使用 Consumer2 獲取計數物件和文字大小
  2. 分別使用 print('rebuild page 2') 和 print('rebuild page 2 refresh count') 輸出呼叫相應 build 日誌
  3. 使用 Consumer 獲取計數物件,在點選 button 時呼叫 CounterNotifier 的 increment 方法
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),
      ),
    );
  }
}
複製程式碼

示例2

2019-09-14 22:21:31.393 14373-14467/com.joker.flutter_provider_demo I/flutter: rebuild page 1 2019-09-14 22:21:31.406 14373-14467/com.joker.flutter_provider_demo I/flutter: rebuild page 2 refresh count

通過這個日誌可以看出,使用 Consumer 可以實現區域性重新整理,所以它的效能更優,應該優先選用。但是 Consumer 只有 Consumer ~ Consumer6,沒有多的是實現。如果需要則只有自己實現。

一點建議:不要吝嗇逗號,這會讓你的程式碼結構看起來更爽

provider 提供了幾種不同型別得 XXProvider ,使用方法都大同小異

name description
Provider 最基本得 provider. 攜帶一個 value 並暴露它.
ListenableProvider Listenable 物件的特定 Provider 。ListenableProvider將監聽物件,以便在呼叫偵聽器時重建依賴它的 Widgets。
ChangeNotifierProvider 一個具體的 ListenableProvider 監聽 ChangeNotifier 。它會在需要時自動呼叫ChangeNotifier.dispose。
ValueListenableProvider 監聽 ValueListenable 並僅暴露 ValueListenable.value。
StreamProvider 監聽 Stream 並暴露最新發出的值。
FutureProvider 攜帶一個 Future,並在 Future 完成時更新依賴

以上就是 Provider 使用,最後奉上Demo地址

相關文章