Flutter 如何刷新 StreamBuilder?
Flutter How to refresh StreamBuilder?
考虑以下代码:
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());
},
),
用 broadcast
声明一个 StreamController
,然后为这个 StreamController
的 Stream
设置一个友好的名称,然后每次你想重建包装的小部件( StreamBuilder
的 child 只需使用 StreamController
的 sink
属性 到 add
将触发 StreamBuilder
的新值.
您可以在不设置类型的情况下使用 StreamBuilder
和 AsyncSnapshot
。
但是如果您在键入 snapshot.data.
时使用 StreamBuilder<UserModel>
和 AsyncSnapshot<UserModel>
,您将看到 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
方法,所以最好在 homePageBloc.dart 中声明变量(它有一个带有所有业务逻辑的普通控制器 class )并在 homePage.dart(有一个 class 扩展 Stateless/Stateful 小部件并负责 UI)。
编辑: 我的 UserModel.dart
,如果您使用来自 Firebase 的 Cloud Firestore 数据库,您可以使用 DocumentSnapshot
而不是 Map<String, dynamic>
。
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; ";
}
}
考虑以下代码:
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());
},
),
用 broadcast
声明一个 StreamController
,然后为这个 StreamController
的 Stream
设置一个友好的名称,然后每次你想重建包装的小部件( StreamBuilder
的 child 只需使用 StreamController
的 sink
属性 到 add
将触发 StreamBuilder
的新值.
您可以在不设置类型的情况下使用 StreamBuilder
和 AsyncSnapshot
。
但是如果您在键入 snapshot.data.
时使用 StreamBuilder<UserModel>
和 AsyncSnapshot<UserModel>
,您将看到 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
方法,所以最好在 homePageBloc.dart 中声明变量(它有一个带有所有业务逻辑的普通控制器 class )并在 homePage.dart(有一个 class 扩展 Stateless/Stateful 小部件并负责 UI)。
编辑: 我的 UserModel.dart
,如果您使用来自 Firebase 的 Cloud Firestore 数据库,您可以使用 DocumentSnapshot
而不是 Map<String, dynamic>
。
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; ";
}
}