Флаттер Как обновить sh StreamBuilder? - PullRequest
0 голосов
/ 13 марта 2020

Рассмотрим следующий код:

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((){});. Потому что я не хочу вспоминать весь метод build(), когда меняется переменная _myLimit.

Я нашел другой путь, но я чувствую, что есть еще лучшее решение, потому что я думаю, что нет использовать функциональность .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());
  },
),

1 Ответ

1 голос
/ 13 марта 2020

Объявите StreamController с помощью broadcast, затем установите понятное имя для Stream этого StreamController, затем каждый раз, когда вы хотите перестроить виджет с переносом (потомок StreamBuilder просто использует sink свойство от StreamController до add нового значения, которое вызовет StreamBuilder.

. Вы можете использовать StreamBuilder и AsyncSnapshot без установки типа.

Но если вы используете StreamBuilder<UserModel> и AsyncSnapshot<UserModel> при вводе snapshot.data., вы увидите все переменные и методы из 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());
  },
)

Таким образом, вы можете использовать StatelessWidget и refresh just a single sub-widget (значок другого цвета, например) without using setState (который перестраивает всю страницу).

Для производительности лучше всего подходят потоки.

Редактировать: Я использую BLoC architecture подход, поэтому гораздо лучше объявить переменные в homePageBlo c .dart (который имеет нормальный класс контроллера со всеми бизнес-логиками c) и создать StreamBuilder в homePage .dart (класс, расширяющий виджет Stateless / Stateful и отвечающий за пользовательский интерфейс).

Редактировать: My UserModel.dart, вы можете использовать DocumentSnapshot вместо Map<String, dynamic>, если вы используете базу данных Cloud Firestore из 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; ";
  }
}
...