【问题标题】:Flutter How to refresh StreamBuilder?Flutter 如何刷新 StreamBuilder?
【发布时间】:2020-06-25 01:51:20
【问题描述】:

考虑以下代码:

StreamBuilder<QuerySnapshot> _createDataStream(){   
    return StreamBuilder<QuerySnapshot>(
          stream: Firestore.instance.collection("data").limit.(_myLimit).snapshots(),
          builder: (context, snapshot){
              return Text(_myLimit.toString);   
            }
        );
}

我希望 StreamBuilder 在 _myLimit 变量更改时刷新。 可以这样做:

void _incrementLimit(){
    setState(() =>_myLimit++);
}

我的问题是,除了setState((){}); 之外,是否还有另一种更快的方法。 因为当_myLimit 变量发生变化时,我不想回忆整个build() 方法。

我想出了另一种方式,但我觉得有一个更好的解决方案,因为我认为我没有使用 .periodic 功能并且我有一个嵌套流我不确定这是多么常见:

Stream<int> myStream = Stream.periodic(Duration(), (_) => _myLimit);
...
@override
Widget build(BuildContext context){
...
return StreamBuilder<int>(
                    stream: myStream,
                    builder: (context, snapshot){
                      return _createDataStream;
                    },
                  ),
...
}

解决方案

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

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

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new _MyAppState();
  }
}

class _MyAppState extends State<MyApp> {

  int myNum = 0;

  final StreamController _myStreamCtrl = StreamController.broadcast();
  Stream get onVariableChanged => _myStreamCtrl.stream;
  void updateMyUI() => _myStreamCtrl.sink.add(myNum);

  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    _myStreamCtrl.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child:
          Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
            StreamBuilder(
              stream: onVariableChanged,
              builder: (context, snapshot){
                if(snapshot.connectionState == ConnectionState.waiting){
                  updateMyUI();
                  return Text(". . .");
                }
                return Text(snapshot.data.toString());
              },
            ),
            RaisedButton(
              child: Text("Increment"),
              onPressed: (){
                myNum++;
                updateMyUI();
                },
        )
        ],
      ),
    )));
  }
}

其他一些想法,StreamBuilder 的样子:

StreamBuilder(
  stream: onVariableChanged,
  builder: (context, snapshot){
    if(snapshot.connectionState == ConnectionState.waiting){
      return Text(myNum.toString());
    }
    return Text(snapshot.data.toString());
  },
),
StreamBuilder(
  stream: onVariableChanged,
  initialData: myNum,
  builder: (BuildContext context, AsyncSnapshot snapshot){
    if(snapshot.data == null){
      return Text("...");
    }
    return Text(snapshot.data.toString());
  },
),

【问题讨论】:

    标签: flutter dart stream listener onchange


    【解决方案1】:

    broadcast声明一个StreamController,然后为这个StreamControllerStream设置一个友好的名称,然后每次你想重建包装的小部件(StreamBuilder的孩子只需使用@ StreamController 的 987654326@ 属性到 add 的新值将触发 StreamBuilder

    您可以使用StreamBuilderAsyncSnapshot 而无需设置类型。

    但是,如果您在键入 snapshot.data. 时使用 StreamBuilder&lt;UserModel&gt;AsyncSnapshot&lt;UserModel&gt;,您将看到来自 UserModel 的所有变量和方法。

    final StreamController<UserModel> _currentUserStreamCtrl = StreamController<UserModel>.broadcast();
    Stream<UserModel> get onCurrentUserChanged => _currentUserStreamCtrl.stream;
    void updateCurrentUserUI() => _currentUserStreamCtrl.sink.add(_currentUser);
    
    StreamBuilder<UserModel>(
      stream: onCurrentUserChanged,
      builder: (BuildContext context, AsyncSnapshot<UserModel> snapshot) {
        if (snapshot.data != null) {
          print('build signed screen, logged as: ' + snapshot.data.displayName);
          return blocs.pageView.pagesView; //pageView containing signed page 
        }
        print('build login screen');
        return LoginPage(); 
    
        //print('loading');
        //return Center(child: CircularProgressIndicator());
      },
    )
    

    这样您就可以使用StatelessWidgetrefresh just a single sub-widget(例如,不同颜色的图标)without using setState(重建整个页面)。

    就性能而言,流是最好的方法。

    编辑: 我正在使用BLoC architecture 方法,因此最好在 homePageBloc.dart 中声明变量(具有具有所有业务逻辑的普通控制器类)并在 homePage.dart 中创建 StreamBuilder(具有扩展的类无状态/有状态小部件,负责 UI)。

    编辑:我的 UserModel.dart,如果您使用 Firebase 中的 Cloud Firestore 数据库,您可以使用 DocumentSnapshot 而不是 Map&lt;String, dynamic&gt;

    class UserModel {
    
      /// Document ID of the user on database
      String _firebaseId = ""; 
      String get firebaseId => _firebaseId;
      set firebaseId(newValue) => _firebaseId = newValue;
    
      DateTime _creationDate = DateTime.now();
      DateTime get creationDate => _creationDate;
    
      DateTime _lastUpdate = DateTime.now();
      DateTime get lastUpdate => _lastUpdate;
    
      String _displayName = "";
      String get displayName => _displayName;
      set displayName(newValue) => _displayName = newValue;
    
      String _username = "";
      String get username => _username;
      set username(newValue) => _username  = newValue;
    
      String _photoUrl = "";
      String get photoUrl => _photoUrl;
      set photoUrl(newValue) => _photoUrl = newValue;
    
      String _phoneNumber = "";
      String get phoneNumber => _phoneNumber;
      set phoneNumber(newValue) => _phoneNumber = newValue;
    
      String _email = "";
      String get email => _email;
      set email(newValue) => _email = newValue;
    
      String _address = "";
      String get address => _address;
      set address(newValue) => _address = newValue;
    
      bool _isAdmin = false;
      bool get isAdmin => _isAdmin;
      set isAdmin(newValue) => _isAdmin = newValue;
    
      /// Used on first login
      UserModel.fromFirstLogin() {
        _creationDate     = DateTime.now();
        _lastUpdate       = DateTime.now();
        _username         = "";
        _address          = "";
        _isAdmin          = false;
      }
    
      /// Used on any login that isn't the first
      UserModel.fromDocument(Map<String, String> userDoc) {
        _firebaseId           = userDoc['firebaseId']  ?? '';
        _displayName          = userDoc['displayName'] ?? '';
        _photoUrl             = userDoc['photoUrl'] ?? '';
        _phoneNumber          = userDoc['phoneNumber'] ?? '';
        _email                = userDoc['email'] ?? '';
        _address              = userDoc['address'] ?? '';
        _isAdmin              = userDoc['isAdmin'] ?? false;
        _username             = userDoc['username'] ?? '';
        //_lastUpdate           = userDoc['lastUpdate'] != null ? userDoc['lastUpdate'].toDate() : DateTime.now();
        //_creationDate         = userDoc['creationDate'] != null ? userDoc['creationDate'].toDate() : DateTime.now();
      }
    
      void showOnConsole(String header) { 
    
        print('''
    
          $header
    
          currentUser.firebaseId: $_firebaseId
          currentUser.username: $_username
          currentUser.displayName: $_displayName
          currentUser.phoneNumber: $_phoneNumber
          currentUser.email: $_email
          currentUser.address: $_address
          currentUser.isAdmin: $_isAdmin
          '''
        );
      }
    
      String toReadableString() {
        return  
          "displayName: $_displayName; "
          "firebaseId: $_firebaseId; "
          "email: $_email; "
          "address: $_address; "
          "photoUrl: $_photoUrl; "
          "phoneNumber: $_phoneNumber; "
          "isAdmin: $_isAdmin; ";
      }
    }
    

    【讨论】:

    • 嗨古斯塔沃,感谢您的回答!我尝试实现它,但我做错了什么,请查看我的问题上的编辑 :)
    • 我认为那是因为连接状态等待是流的状态,直到它关闭处置。流将始终等待在接收器上添加某些内容。我不使用连接状态。比较数据是否为空,如果为空可能是正在加载的东西,所以渲染一个进度指示器,如果不是你渲染数据或其他一些小部件
    • 这并不能解决问题。无论如何,谢谢你的帮助!我现在找到了另一个解决方案,使用 ValueListenableBuilder 小部件。
    • 刚刚发现您的错误:您不需要在 initState 上调用 updateMyUI(),并且每次要重建 StreamBuilder 时都必须调用,在 onPressed 上的 RaisedButton 中您必须调用updateMyUI() 增加数字后。像这样: onPressed: () { myNum++; updateMyUI();并使用 snapshot.data != null 显示空文本
    • 是的,这行得通!但是由于某种原因,Stream 仍然会停留在他返回的 data == null 或 ConnectionState.waiting 中,需要触发 Stream。当我在 data==null 案例或 ConnectionState.waiting 中添加 updateMyUI() 时,它可以工作,并且 Stream 正在显示 myNum 的初始值。如果您正在检查 data == null,那么除了调用 updateMyUI() 来触发 Stream 之外,还有另一种方法,您可以使用 StreamBuilder 的“initialData”属性并用 myNum 填充它。我将编辑我的问题。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2020-06-12
    • 2019-02-07
    • 2023-04-05
    • 1970-01-01
    • 2020-10-15
    • 2021-02-21
    • 1970-01-01
    • 2019-10-16
    相关资源
    最近更新 更多