Flutter ——狀態(tài)管理 | StreamBuild

1.什么是stream?

StreamBuild從字面意思來講是數據流構建,是一種基于數據流的訂閱管理。Stream可以接受任何類型的數據,值、事件、對象、集合、映射、錯誤、甚至是另一個Stream,通過StreamController中的sink作為入口,往Stream中插入數據,然后通過你的自定義監(jiān)聽StreamSubscription對象,接受數據變化的通知。如果你需要對輸出數據進行處理,可以使用StreamTransformer,它可以對輸出數據進行過濾、重組、修改、將數據注入其他流等等任何類型的數據操作。

2.stream都有哪些類型

Stream有兩種類型:單訂閱Stream和廣播Stream。單訂閱Stream只允許在該Stream的整個生命周期內使用單個監(jiān)聽器,即使第一個subscription被取消了,你也沒法在這個流上監(jiān)聽到第二次事件;而廣播Stream允許任意個數的subscription,你可以隨時隨地給它添加subscription,只要新的監(jiān)聽開始工作流,它就能收到新的事件。

2.1 單訂閱類型實例

import 'dart:async';

void main() {
  // 初始化一個單訂閱的Stream controller
  final StreamController ctrl = StreamController();
  
  // 初始化一個監(jiān)聽
  final StreamSubscription subscription = ctrl.stream.listen((data) => print('$data'));

  // 往Stream中添加數據
  ctrl.sink.add('my name');
  ctrl.sink.add(1234);
  ctrl.sink.add({'a': 'element A', 'b': 'element B'});
  ctrl.sink.add(123.45);
  
  // StreamController用完后需要釋放
  ctrl.close();
}

2.2廣播類stream

import 'dart:async';

void main() {
  // 初始化一個int類型的廣播Stream controller
  final StreamController<int> ctrl = StreamController<int>.broadcast();
  
  // 初始化一個監(jiān)聽,同時通過transform對數據進行簡單處理
  final StreamSubscription subscription = ctrl.stream
                          .where((value) => (value % 2 == 0))
                          .listen((value) => print('$value'));

  // 往Stream中添加數據
  for(int i=1; i<11; i++){
    ctrl.sink.add(i);
  }
  
  // StreamController用完后需要釋放
  ctrl.close();
}

3.stream有哪些好處?

3.1.隨意操作數據流。

剛才在stream定義那里已經說過了,stream是基于數據流的,從skin管道入口到StreamController提供stream屬性作為數據的出口之間,可以對數據做任何操作,包括過濾、重組、修改等等。

3.2 當數據流變化時,可以刷新小部件。

Stream是一種訂閱者模式,當數據發(fā)生變化時,通知訂閱者發(fā)生改變,重新構建小部件,刷新UI。

4.如何使用streamBuild?

  StreamBuilder<T>(
    key: ...可選...
    stream: ...需要監(jiān)聽的stream...
    initialData: ...初始數據,盡量不要填null...
    builder: (BuildContext context, AsyncSnapshot<T> snapshot){
        if (snapshot.hasData){
            return ...基于snapshot.hasData返回的控件
        }
        return ...沒有數據的時候返回的控件
    },
)

下面是一個模仿官方自帶demo“計數器”的一個例子,使用了StreamBuilder,而不需要任何setState:
我在代碼里注釋了步驟(四步):

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

class CounterPage extends StatefulWidget {
  @override
  _CounterPageState createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _counter = 0;
  //步驟1:初始化一個StreamController<任何數據> 簡單的可以扔一個int,string,開發(fā)中經常扔一個網絡請求的model進去,具體看你使用場景了。
  final StreamController<int> _streamController = StreamController<int>();

  @override
  void dispose(){
  //步驟2.關流,不管流會消耗資源,同時會引起內存泄漏
    _streamController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Stream version of the Counter App')),
      body: Center(
      //步驟3.使用StreamBuilder構造器
        child: StreamBuilder<int>(  // 監(jiān)聽Stream,每次值改變的時候,更新Text中的內容
          stream: _streamController.stream,
          initialData: _counter,
          builder: (BuildContext context, AsyncSnapshot<int> snapshot){
            return Text('You hit me: ${snapshot.data} times');
          }
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: (){
          // 每次點擊按鈕,更加_counter的值,同時通過Sink將它發(fā)送給Stream;
          // 每注入一個值,都會引起StreamBuilder的監(jiān)聽,StreamBuilder重建并刷新counter
          //步驟4.往StreamBuilder里添加流,數據變了,就用通知小部件
          _streamController.sink.add(++_counter);
        },
      ),
    );
  }
}

5.具體如何使用?

剛剛介紹了stream的如何使用,是不是感覺還是懵的狀態(tài),實例代碼僅僅是實例,如何應用到項目中呢?我們的項目不僅僅是一個簡單的計數器,接下來我將結合項目,簡單講述一下如何使用streamBuild。這是我司的一張UI。

UI.png

要求點擊“關注”變?yōu)椤耙殃P注”
如何去實現(xiàn)的?實現(xiàn)的方法有好多種。
1.這個item是StatefulWidget,點擊“關注”,然后setstate(){}
2.使用其他的狀態(tài)管理去實現(xiàn)。如 【 ScopedModel 】 【 Provide 】 【Bloc】
3.使用 StreamBuild 實現(xiàn)。

我選擇使用StreamBuild去實現(xiàn),稍后我會解釋為何要用streambuild 去實現(xiàn)。

import 'dart:async';
import 'package:easy_alert/easy_alert.dart';
import 'package:flutter/material.dart';
import 'package:hongka_flutter/app/Manager/IO/hk_request.dart';
import 'package:hongka_flutter/app/Manager/api/ConfigApi.dart';
import 'package:hongka_flutter/app/Modules/basemodel/focuseItemModel.dart';
import 'package:hongka_flutter/app/Modules/home/info_organization.dart';
//我會省略部分代碼,并且注釋使用步驟
//步驟一:使用 StatefulWidget,為何要用StatefulWidget?待會解釋
class FollowsItem extends StatefulWidget {
  FocuseItemModel focusItemModel;
  String focusType; //不為空就是關注界面,隱藏關注按鈕
  int index;
  String studentId;

  FollowsItem(
      {Key key,
      this.focusItemModel,
      this.focusType,
      this.index,
      this.studentId});

  @override
  _FollowsItemState createState() => _FollowsItemState();
}

class _FollowsItemState extends State<FollowsItem> {
  FocuseItemModel focusItemModel;
  String focusType; //不為空就是關注界面,隱藏關注按鈕
  String headerImage;
  double screenWidth, marginLeft = 15.0, marginRight = 15.0, marginAll = 30.0;
  int index;
  String studentId;
  //步驟二:聲明StreamController
  StreamController<FocuseItemModel> _streamController;

  @override
  void initState() {
    super.initState();
    this.focusItemModel = this.widget.focusItemModel;
    this.focusType = this.widget.focusType;
    this.index = this.widget.index;
    this.studentId = this.widget.studentId;
    //步驟三實現(xiàn) StreamController<FocuseItemModel>,FocuseItemModel是我的實體類
    _streamController = StreamController<FocuseItemModel>.broadcast();
    //步驟四將數據添加到 _streamController
    _streamController.sink.add(focusItemModel);
  }

  @override
  void dispose() {
  //步驟五:關流
    _streamController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        _clickItem(context);
      },
      child: Container(
        .....省略無關UI代碼
        child: Stack(
          children: <Widget>[
            .....省略無關UI代碼
            Positioned(
              right: 0,
              top: 0,
              child: Offstage(
                  offstage: !(focusType == null),
                  child: GestureDetector(
                    onTap: () {
                    //已關注return
                      if (focusItemModel.focusStatus == 1) {
                        return;
                      }
                      //未關注進行網絡請求
                      focusOrganizationRequest(focusItemModel.positionId);
                    },
                    //步驟五:使用StreamBuilder構建
                    child: StreamBuilder<FocuseItemModel>(
                        stream: _streamController.stream,//數據流
                        initialData: focusItemModel,//初始值
                        builder: (BuildContext context,
                            AsyncSnapshot<FocuseItemModel> snapshot) {
                          return Image.asset(
                            (snapshot?.data?.focusStatus ?? 0) == 1
                                ? 'images/view/recommend_flowered.png'
                                : 'images/view/recommend_flower.png',
                            width: 54,
                            height: 40,
                          );
                        }),
                  )),
            )
          ],
        ),
      ),
    );
  }

  ///關注紅廣號
  Future focusOrganizationRequest(String positionId) async {
    String url = 'xxxx';
    var data = {
      'studentId': studentId,
      'positionId': positionId,
    };
    var options = await HK_Request().getRequestOptions();
    var response = await HK_Request()
        .post(url, context, isShowLoading: true, data: data, options: options);
    print(response.toString());
    if (response['status'] == 200) {
    //步驟六:改變model值,并將model 扔到_streamController里,此時數據改變,通知小部件,重新構建
      focusItemModel.focusStatus = 1;
      _streamController.sink.add(focusItemModel);
      Alert.toast(context, '關注成功!',
          position: ToastPosition.center, duration: ToastDuration.short);
      print('關注紅廣號成功' + response.toString());
    
    } else {
      print('關注紅廣號錯誤');
      Alert.toast(context, response['msg'],
          position: ToastPosition.center, duration: ToastDuration.short);
    }
  }

model值改變,streamBuild 通知小部件,并刷新小部件。

問題1 為何選擇使用streamBuild

1.方法一使用StatefulWidget,刷新時使用setstate(){},使用setstate(){}刷新,會將整個item 進行重新構建,整個item 僅僅只有“關注”需要改變,其它控件都刷新,會造成資源浪費。
2.方法二使用狀態(tài)管理bloc,如果使用了bloc,streamBuild中的stream 就因該傳bloc< AModel >的數據,如果我其它地方使用也使用了這個item,那么這個stream就應該傳bloc< BMode >,此時streamBuild中的stream 類型就不匹配了,這個item 就無法復用了,所以我放棄使用bloc等狀態(tài)管理
3.為何item 最外層使用StatefulWidget?不是使用streamBuild 就可以不用使用StatefulWidget了嗎?
的確使用streamBuild,就可以不使用StatefulWidget。但是 不用StatefulWidget,如何關流? StatelessWidget 沒有dispose()方法,不能關流,所以此時還需要使用StatefulWidget。

 @override
  void dispose() {
  //步驟五:關流
    _streamController.close();
    super.dispose();
  }

有群友提出,可以將“關注”的屬性提取出來,單獨一個bloc去管理,我覺得為了一個按鈕的改變,去做很多操作,有點不值得了。當然有興趣的可以去實現(xiàn)一下。

問題2.怎樣才能不使用StatefulWidget?

bloc+streamBuild,此時的stream是bloc里的,不需要在dispose()方法中去關流,這樣就可以放棄使用StatefulWidget了。

6. bloc結合streamBuild 實現(xiàn)狀態(tài)管理會在下一篇內容中講解。

本人對于 streamBuild 理解的也不是很深刻,沒有往太細節(jié)去講解,只是結合自己的項目去講解了開發(fā)中遇到的問題,希望大家提提意見,共同進步。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容