【问题标题】:Flutter Streambuilder snapshot is null after moving to OnGenerateRoute移至 OnGenerateRoute 后 Flutter Streambuilder 快照为空
【发布时间】:2020-07-19 21:45:08
【问题描述】:

又回来了。我根据这个帖子中的建议重构了我的代码:Flutter Multiple Blocs and NamedRoutes

但是,由于将我的块从主材质应用树移动到路由器页面,因此数据没有加载,因为快照为空。

路由器:

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 _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');
                }...

blocpprovider 最初位于 centerselect 屏幕类中,一切正常,但自从移动它后,它就不起作用了,我似乎不知道为什么。

当应用程序首次加载时,博客本身似乎已正确初始化,因为它正在打印出所有正确的信息。来自 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 类

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
  }

【问题讨论】:

  • 请出示CentresBloc
  • 感谢@IgorKharakhordin,我已将 CentresBloc 添加到原帖中。

标签: flutter bloc


【解决方案1】:

推送新路由后,StreamBuilder 订阅同一流 _centresBloc.centres,但不会在此流上发出新事件,因为它只发生一次 - 在构造函数中 bloc 的初始化期间。这是因为 Dart 的默认 StreamController 不会将之前的事件/值(包括最后一个)发送给流的新订阅者。

但是,您可以使用基于StreamControllerrxdart 库中的BehaviorSubject,但它也存储最后发出的值并将其发送给任何新订阅者。主题也始终是广播流,并且可以具有初始(种子)值。

只需替换这个:

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')
          )
        )
      )
    );
  }
}

【讨论】:

  • 谢谢。不幸的是,快照仍然返回 null。 ://
  • @GaryFrewin 除此之外,您的代码看起来还不错。我已经用 Subject 对其进行了测试,并且可以正常工作。请用完整代码查看我的更新答案。为了简单起见,我将ClimbingCentre 类型更改为String 并使用bloc_provider 库。
  • 我有自己的 bloc_provider 类,而不是使用库。一旦我切换到图书馆,一切都开始工作了。 :) 谢谢。
猜你喜欢
  • 2021-08-08
  • 1970-01-01
  • 2021-07-01
  • 2023-02-11
  • 2021-09-30
  • 2020-04-12
  • 2020-10-26
  • 2022-06-28
  • 2020-03-19
相关资源
最近更新 更多