Flutter How to refresh StreamBuilder?

馋奶兔 提交于 2021-02-07 13:32:17

问题


Consider the following code:

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

I want that the StreamBuilder refreshes when the _myLimit Variable changes. It's possible doing it like this:

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

My Question is if there is another, faster way, except the setState((){}); one. Because I don't want to recall the whole build() method when the _myLimit Variable changes.

I figured out another Way but I feel like there is a even better solution because I think I don't make use of the .periodic functionality and I got a nested Stream I'm not sure how usual this is:

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

Solution(s)

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();
                },
        )
        ],
      ),
    )));
  }
}

Some other ideas, how the StreamBuilder also could look like:

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());
  },
),

回答1:


Declare a StreamController with broadcast, then set a friendly name to the Stream of this StreamController, then everytime you want to rebuild the wraped widget (the child of the StreamBuilder just use the sink property of the StreamController to add a new value that will trigger the StreamBuilder.

You can use StreamBuilder and AsyncSnapshot without setting the type.

But if you use StreamBuilder<UserModel> and AsyncSnapshot<UserModel> when you type snapshot.data. you will see all variables and methods from the 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());
  },
)

This way you can use a StatelessWidget and refresh just a single sub-widget (an icon with a different color, for example) without using setState (that rebuilds the entire page).

For performance, streams are the best approach.

Edit: I'm using BLoC architecture approach, so it's much better to declare the variables in a homePageBloc.dart (that has a normal controller class with all business logic) and create the StreamBuilder in the homePage.dart (that has a class that extends Stateless/Stateful widget and is responsible for the UI).

Edit: My UserModel.dart, you can use DocumentSnapshot instead of Map<String, dynamic> if you are using Cloud Firestore database from Firebase.

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; ";
  }
}


来源:https://stackoverflow.com/questions/60663818/flutter-how-to-refresh-streambuilder

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!