移动到 OnGenerateRoute 后,Flutter Streambuilder 快照为空
Flutter Streambuilder snapshot is null after moving to OnGenerateRoute
又回来了。我根据此线程中的建议重构了我的代码:
但是,自从将我的 bloc 从主 material 应用程序树移动到路由器页面后,由于快照为空,数据未加载。
路由器:
class AppRouter {
final _centresBloc = CentresBloc();
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case routes.CentreSelectScreenRoute:
return MaterialPageRoute(
builder: (_) => BlocProvider(
bloc: _centresBloc,
child: CentreSelectScreen(),
),
);
default:
return MaterialPageRoute(builder: (context) => HomeScreen());
}
和 CentreSelectScreen class 本身
class _CentreSelectScreenState extends State<CentreSelectScreen> {
@override
Widget build(BuildContext context) {
final _centresBloc = BlocProvider.of<CentresBloc>(context);
return Scaffold(
body: Container(
child: StreamBuilder<List<ClimbingCentre>>(
stream: _centresBloc.centres,
builder: (context, snapshot) {
print('snapshot == ${snapshot.data}'); //is always null now
if (snapshot.hasData) {
// If there are no centres (data), display this message.
if (snapshot.data.length == 0) {
return Text('No Centres listed');
}...
blocprovider 最初位于中心选择屏幕 class 中,一切正常,但自从移动它后,它就不起作用了,我似乎无法弄清楚为什么。
当应用首次加载时,博客本身似乎已正确初始化,因为它正在打印所有正确的信息。来自 CentresBloc:
void getCentres() async {
// Retrieve all the centres from the database
List<ClimbingCentre> centres = await ClimbDB.db.getCentres();
// Add all of the centres to the stream so we can grab them later from our pages
_inCentres.add(centres);
print('BLOC incentres is $centres'); //this works and prints all centres when the app first loads...
}
非常感谢任何帮助。
编辑 添加 CentresBloc Class
import 'dart:async';
import 'package:flutterapp/data/database.dart';
import 'package:flutterapp/models/centre_model.dart';
import 'bloc_provider.dart';
class CentresBloc implements BlocBase {
final _centresController = StreamController<List<ClimbingCentre>>.broadcast();
// Input stream. Add centres to the stream using this variable.
StreamSink<List<ClimbingCentre>> get _inCentres => _centresController.sink;
// Output stream. This one will be used within our pages to display the centres.
Stream<List<ClimbingCentre>> get centres => _centresController.stream;
CentresBloc() {
// Retrieve all the climbing centres on initialization
getCentres();
}
@override
void dispose() {
_centresController.close();
}
void getCentres() async {
// Retrieve all the centres from the database
List<ClimbingCentre> centres = await ClimbDB.db.getCentres();
// Add all of the centres to the stream so we can grab them later from our pages
_inCentres.add(centres);
print('CentreBloc _incentres is: $centres'); //this prints the correct centres when the app is first loaded
}
推送新路由后,StreamBuilder 订阅同一流 _centresBloc.centres
,但此流上不会发出新事件,因为它只发生一次 -在 bloc 在构造函数中的初始化期间。这是因为 Dart 的默认 StreamController 不会将之前的 events/values(包括最后一个)发送到流的新订阅者。
但是,您可以使用基于 StreamController
的 BehaviorSubject from rxdart 库,但它还会存储发出的最后一个值并将其发送给任何新订阅者。主题也始终是广播流,并且可以具有初始(种子)值。
只需替换为:
final _centresController = StreamController<List<ClimbingCentre>>.broadcast();
有了这个:
final _centresController = BehaviorSubject<List<ClimbingCentre>>();
完整的工作代码:
import 'dart:async';
import 'package:bloc_provider/bloc_provider.dart';
import 'package:flutter/material.dart';
import 'package:rxdart/subjects.dart';
void main() => runApp(MyApp2());
class CentresBloc implements Bloc {
final _centresController = BehaviorSubject<List<String>>();
// Input stream. Add centres to the stream using this variable.
StreamSink<List<String>> get _inCentres => _centresController.sink;
// Output stream. This one will be used within our pages to display the centres.
Stream<List<String>> get centres => _centresController.stream;
CentresBloc() {
// Retrieve all the climbing centres on initialization
getCentres();
}
void dispose() {
_centresController.close();
}
void getCentres() async {
// Retrieve all the centres from the database
List<String> centres = ['LIST'];
// Add all of the centres to the stream so we can grab them later from our pages
_inCentres.add(centres);
print('CentreBloc _incentres is: $centres'); //this prints the correct centres when the app is first loaded
}
}
class AppRouter {
final _centresBloc = CentresBloc();
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case 'test':
return MaterialPageRoute(
builder: (_) => BlocProvider<CentresBloc>.fromBloc(
bloc: _centresBloc,
child: CentreSelectScreen(),
),
);
default:
return MaterialPageRoute(builder: (context) => HomeScreen());
}
}
void dispose() {
_centresBloc.dispose();
}
}
class MyApp2 extends StatefulWidget {
@override
_MyApp2State createState() => _MyApp2State();
}
class _MyApp2State extends State<MyApp2> {
final _router = AppRouter();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
onGenerateRoute: _router.generateRoute,
);
}
@override
void dispose() {
_router.dispose();
super.dispose();
}
}
class CentreSelectScreen extends StatefulWidget {
@override
State<StatefulWidget> createState() => _CentreSelectScreenState();
}
class _CentreSelectScreenState extends State<CentreSelectScreen> {
@override
Widget build(BuildContext context) {
final _centresBloc = BlocProvider.of<CentresBloc>(context);
return Scaffold(
body: Container(
child: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
MaterialButton(
child: Text('push index'),
onPressed: () {
Navigator.pushReplacementNamed(context, '/');
},
),
StreamBuilder<List<String>>(
stream: _centresBloc.centres,
builder: (context, snapshot) {
print('snapshot == ${snapshot.data}'); //is always null now
if (snapshot.hasData) {
// If there are no centres (data), display this message.
if (snapshot.data.length == 0) {
return Text('No Centres listed');
} else {
return Text(snapshot.data.toString());
}
}
return Container();
}
),
],
),
),
),
),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: MaterialButton(
onPressed: () => Navigator.pushReplacementNamed(context, 'test'),
child: Text('push test')
)
)
)
);
}
}
又回来了。我根据此线程中的建议重构了我的代码:
但是,自从将我的 bloc 从主 material 应用程序树移动到路由器页面后,由于快照为空,数据未加载。
路由器:
class AppRouter {
final _centresBloc = CentresBloc();
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case routes.CentreSelectScreenRoute:
return MaterialPageRoute(
builder: (_) => BlocProvider(
bloc: _centresBloc,
child: CentreSelectScreen(),
),
);
default:
return MaterialPageRoute(builder: (context) => HomeScreen());
}
和 CentreSelectScreen class 本身
class _CentreSelectScreenState extends State<CentreSelectScreen> {
@override
Widget build(BuildContext context) {
final _centresBloc = BlocProvider.of<CentresBloc>(context);
return Scaffold(
body: Container(
child: StreamBuilder<List<ClimbingCentre>>(
stream: _centresBloc.centres,
builder: (context, snapshot) {
print('snapshot == ${snapshot.data}'); //is always null now
if (snapshot.hasData) {
// If there are no centres (data), display this message.
if (snapshot.data.length == 0) {
return Text('No Centres listed');
}...
blocprovider 最初位于中心选择屏幕 class 中,一切正常,但自从移动它后,它就不起作用了,我似乎无法弄清楚为什么。
当应用首次加载时,博客本身似乎已正确初始化,因为它正在打印所有正确的信息。来自 CentresBloc:
void getCentres() async {
// Retrieve all the centres from the database
List<ClimbingCentre> centres = await ClimbDB.db.getCentres();
// Add all of the centres to the stream so we can grab them later from our pages
_inCentres.add(centres);
print('BLOC incentres is $centres'); //this works and prints all centres when the app first loads...
}
非常感谢任何帮助。
编辑 添加 CentresBloc Class
import 'dart:async';
import 'package:flutterapp/data/database.dart';
import 'package:flutterapp/models/centre_model.dart';
import 'bloc_provider.dart';
class CentresBloc implements BlocBase {
final _centresController = StreamController<List<ClimbingCentre>>.broadcast();
// Input stream. Add centres to the stream using this variable.
StreamSink<List<ClimbingCentre>> get _inCentres => _centresController.sink;
// Output stream. This one will be used within our pages to display the centres.
Stream<List<ClimbingCentre>> get centres => _centresController.stream;
CentresBloc() {
// Retrieve all the climbing centres on initialization
getCentres();
}
@override
void dispose() {
_centresController.close();
}
void getCentres() async {
// Retrieve all the centres from the database
List<ClimbingCentre> centres = await ClimbDB.db.getCentres();
// Add all of the centres to the stream so we can grab them later from our pages
_inCentres.add(centres);
print('CentreBloc _incentres is: $centres'); //this prints the correct centres when the app is first loaded
}
推送新路由后,StreamBuilder 订阅同一流 _centresBloc.centres
,但此流上不会发出新事件,因为它只发生一次 -在 bloc 在构造函数中的初始化期间。这是因为 Dart 的默认 StreamController 不会将之前的 events/values(包括最后一个)发送到流的新订阅者。
但是,您可以使用基于 StreamController
的 BehaviorSubject from rxdart 库,但它还会存储发出的最后一个值并将其发送给任何新订阅者。主题也始终是广播流,并且可以具有初始(种子)值。
只需替换为:
final _centresController = StreamController<List<ClimbingCentre>>.broadcast();
有了这个:
final _centresController = BehaviorSubject<List<ClimbingCentre>>();
完整的工作代码:
import 'dart:async';
import 'package:bloc_provider/bloc_provider.dart';
import 'package:flutter/material.dart';
import 'package:rxdart/subjects.dart';
void main() => runApp(MyApp2());
class CentresBloc implements Bloc {
final _centresController = BehaviorSubject<List<String>>();
// Input stream. Add centres to the stream using this variable.
StreamSink<List<String>> get _inCentres => _centresController.sink;
// Output stream. This one will be used within our pages to display the centres.
Stream<List<String>> get centres => _centresController.stream;
CentresBloc() {
// Retrieve all the climbing centres on initialization
getCentres();
}
void dispose() {
_centresController.close();
}
void getCentres() async {
// Retrieve all the centres from the database
List<String> centres = ['LIST'];
// Add all of the centres to the stream so we can grab them later from our pages
_inCentres.add(centres);
print('CentreBloc _incentres is: $centres'); //this prints the correct centres when the app is first loaded
}
}
class AppRouter {
final _centresBloc = CentresBloc();
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case 'test':
return MaterialPageRoute(
builder: (_) => BlocProvider<CentresBloc>.fromBloc(
bloc: _centresBloc,
child: CentreSelectScreen(),
),
);
default:
return MaterialPageRoute(builder: (context) => HomeScreen());
}
}
void dispose() {
_centresBloc.dispose();
}
}
class MyApp2 extends StatefulWidget {
@override
_MyApp2State createState() => _MyApp2State();
}
class _MyApp2State extends State<MyApp2> {
final _router = AppRouter();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
onGenerateRoute: _router.generateRoute,
);
}
@override
void dispose() {
_router.dispose();
super.dispose();
}
}
class CentreSelectScreen extends StatefulWidget {
@override
State<StatefulWidget> createState() => _CentreSelectScreenState();
}
class _CentreSelectScreenState extends State<CentreSelectScreen> {
@override
Widget build(BuildContext context) {
final _centresBloc = BlocProvider.of<CentresBloc>(context);
return Scaffold(
body: Container(
child: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
MaterialButton(
child: Text('push index'),
onPressed: () {
Navigator.pushReplacementNamed(context, '/');
},
),
StreamBuilder<List<String>>(
stream: _centresBloc.centres,
builder: (context, snapshot) {
print('snapshot == ${snapshot.data}'); //is always null now
if (snapshot.hasData) {
// If there are no centres (data), display this message.
if (snapshot.data.length == 0) {
return Text('No Centres listed');
} else {
return Text(snapshot.data.toString());
}
}
return Container();
}
),
],
),
),
),
),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: MaterialButton(
onPressed: () => Navigator.pushReplacementNamed(context, 'test'),
child: Text('push test')
)
)
)
);
}
}