简体   繁体   中英

How can I use BottomNavigationBar with BLoC?

When I use BottomNavigationBar with BLoC pattern, it causes the error, Bad state: Stream has already been listened to.

I may listen a stream of a BLoC at just one place.

My code is the following.

main.dart

import 'package:flutter/material.dart';
import 'package:bottom_tab_bloc/app_state_bloc.dart';
import 'package:bloc_provider/bloc_provider.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      creator: (context, _bag) => AppStateBloc(),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(title: 'Flutter Demo Home Page'),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String activeTab = "tab1";
  final bottomTabs = ["tab1", "tab2"];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(activeTab),
      ),
      body: buildTab(activeTab),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: bottomTabs.indexOf(activeTab),
        onTap: (int index) {
          setState(() {
            activeTab = bottomTabs[index];
          });
        },
        items: bottomTabs.map((tab) =>
          buildBnbItem(tab)
        ).toList(),
      ),
    );
  }

  Widget buildTab(String tab) {
    if (tab == "tab1") {
      return TabOne();
    } else if (tab == "tab2") {
      return TabTwo();
    }
  }

  BottomNavigationBarItem buildBnbItem (String tab) {
    assert(bottomTabs.contains(tab));

    if (tab == "tab1") {
      return BottomNavigationBarItem(
        title: Text('Tab1'),
        icon: Icon(Icons.looks_one),
      );
    } else if (tab == "tab2") {
      return BottomNavigationBarItem(
        title: Text('Tab1'),
        icon: Icon(Icons.looks_two),
      );
    }
  }
}

class TabOne extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final AppStateBloc bloc = BlocProvider.of<AppStateBloc>(context);
    return StreamBuilder(
      stream: bloc.outValue1,
      builder: (context, snapshot) =>
        Center(child: Text(snapshot.data.toString())),
    );
  }
}

class TabTwo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final AppStateBloc bloc = BlocProvider.of<AppStateBloc>(context);
    return StreamBuilder(
      stream: bloc.outValue2,
      builder: (context, snapshot) =>
        Center(child: Text(snapshot.data.toString())),
    );
  }
}

app_state_bloc.dart

import 'dart:async';
import 'package:bloc_provider/bloc_provider.dart';

class AppStateBloc implements Bloc {
  StreamController<int> _value1Controller
    = StreamController<int>();
  Sink<int> get _inValue1 => _value1Controller.sink;
  Stream<int> get outValue1 => _value1Controller.stream;

  StreamController<int> _updateValue1Controller
    = StreamController<int>();
  Sink<int> get updateValue1 =>
      _updateValue1Controller.sink;

  StreamController<int> _value2Controller
  = StreamController<int>();
  Sink<int> get _inValue2 => _value2Controller.sink;
  Stream<int> get outValue2 => _value2Controller.stream;

  StreamController<int> _updateValue2Controller
  = StreamController<int>();
  Sink<int> get updateValue2 =>
      _updateValue2Controller.sink;

  AppStateBloc(){
    _inValue1.add(1);
    _updateValue1Controller.stream.listen(_updateValue1);
    _inValue2.add(2);
    _updateValue2Controller.stream.listen(_updateValue2);
  }

  @override
  void dispose() {
    _value1Controller.close();
    _updateValue1Controller.close();
    _value2Controller.close();
    _updateValue2Controller.close();
  }

  void _updateValue1(int value1) {
    _inValue1.add(value1);
  }

  void _updateValue2(int value2) {
    _inValue2.add(value2);
  }
}

I can go to the TabTwo from TabOne only at the first time, but the error occurs when I go back to TabOne .

I also tried using StreamController<int>.broadcast() in app_state_bloc.dart , but snapshot.data is always null .

  • How can I implement BottomNavigationBar with BLoC pattern?
  • Why the streams are called more than twice, though I write each stream at just one place?
  • Is AppStateBloc.dispose() is called in this code? When and where AppStateBloc.dispose() is called?
  • Why broadcast stream's snapshot.data is always null?

Why the streams are called more than twice, though I write each stream at just one place?

The error is related to the number of subscribers to the stream. StreamController by default only allows one subscriber. That's why TabOne works fine the first time, but breaks afterwards.

Is AppStateBloc.dispose() is called in this code? When and where AppStateBloc.dispose() is called?

It would be called when the BlocProvider widget gets removed, but since it's being used as the app root, I guess this only happens when the app is closed.

Why broadcast stream's snapshot.data is always null?

Broadcast streams do not buffer events when there is no listener. Since you're writing to the stream before TabOne is created, the event is lost and you get null .

How can I implement BottomNavigationBar with BLoC pattern?

I guess it depends on your use case, but for this particular example, if you replace StreamController with rxdart 's BehaviorSubject , it works fine, because then you'd have a broadcast stream that always sends you the last event.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM