Поток уже прослушан из элементов нижней панели навигации

#flutter #dart #bloc

#flutter #dart #блок

Вопрос:

В моем проекте я использую архитектуру с пользовательским провайдером.

 class BlocProvider<T extends BlocBase> extends StatefulWidget {
  BlocProvider(
      {@required this.child,
      @required this.bloc,
      @required this.blocContext,
      Key key})
      : super(key: key);

  final T bloc;
  final Widget child;
  final BlocContextBase<T> blocContext;

  @override
  _BlocProviderState<T> createState() => _BlocProviderState<T>();

  static T of<T extends BlocBase>(BuildContext context) {
    final BlocProvider<T> provider = context.findAncestorWidgetOfExactType();
    return provider.bloc;
  }
}

class _BlocProviderState<T> extends State<BlocProvider<BlocBase>> {
  @override
  void initState() {
    super.initState();
    widget.blocContext.subscribe(widget.bloc, context);
  }

  @override
  void dispose() {
    widget.bloc.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}
 

И все работает хорошо, пока я не переключился на BottomNavigationBar.

 class TabContainerContent {
  final Widget firstTab;
  final Widget secondTab;
  final Widget thirdTab;

  TabContainerContent(
      {@required this.firstTab,
      @required this.secondTab,
      @required this.thirdTab});
}

class TabContainerScreen extends StatefulWidget {
  final TabContainerContent _content;

  TabContainerScreen({@required TabContainerContent content})
      : _content = content;

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

class _TabContainerScreenState extends State<TabContainerScreen> {
  int _selectedIndex = 0;
  List<BottomNavigationBarItem> _bottomBarItems = [];
  List<Widget> _tabWidgets = [];

  @override
  void initState() {
    super.initState();

    _tabWidgets.add(widget._content.firstTab);
    _bottomBarItems.add(
      BottomNavigationBarItem(
        icon: Icon(Icons.map),
        label: 'Profile',
      ),
    );

    _tabWidgets.add(widget._content.secondTab);
    _bottomBarItems.add(
      BottomNavigationBarItem(
        icon: Icon(Icons.location_city),
        label: 'Search',
      ),
    );

    _tabWidgets.add(widget._content.thirdTab);
    _bottomBarItems.add(
      BottomNavigationBarItem(
        icon: Icon(Icons.ac_unit),
        label: 'Ads',
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _tabWidgets[_selectedIndex],
      bottomNavigationBar: BottomNavigationBar(
        items: _bottomBarItems,
        currentIndex: _selectedIndex,
        onTap: (int value) {
          setState(() {
            _selectedIndex = value;
          });
        },
      ),
    );
  }
}
 

Проблема возникает только в том случае, если в моем блоке есть контроллер потока, и я использую StreamBuilder в своем виджете.
Если я перейду на другую вкладку, а затем вернусь назад. Затем я получаю сообщение о том, что поток ошибок уже прослушан.
Мне кажется, что я где-то не удаляю некоторые данные.

Вот как выглядит blocs .

 class ProfileBloc extends IProfileBloc {
      final StreamController<User> _userControllerOne = StreamController<User>();
    
      Sink<User> get _inUserState => _userControllerOne.sink;
      Stream<User> get outUserState => _userControllerOne.stream;
    
      @override
      void dispose() {
        _userControllerOne.close();
        super.dispose();
      }
}
 

 class SearchBloc extends ISearchBloc {
      final StreamController<User> _userControllerTwo = StreamController<User>();
    
      Sink<User> get _inUserState => _userControllerTwo.sink;
      Stream<User> get outUserState => _userControllerTwo.stream;
    
      @override
      void dispose() {
        _userControllerTwo.close();
        super.dispose();
      }
}
 

 class AdsBloc extends IAdsBloc {
      final StreamController<User> _userControllerThree = StreamController<User>();
    
      Sink<User> get _inUserState => _userControllerThree.sink;
      Stream<User> get outUserState => _userControllerThree.stream;
    
      @override
      void dispose() {
        _userControllerThree.close();
        super.dispose();
      }
}
 

This is how my UI looks like

 class ProfileScreen extends StatefulWidget {
  ProfileScreen({Key key}) : super(key: key);

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

class _ProfileScreenState extends State<ProfileScreen> {
  IProfileBloc _bloc;

  @override
  void initState() {
    super.initState();
    _bloc = BlocProvider.of(context);
    _bloc.loadUser();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: SafeArea(
      child: StreamBuilder(
        stream: _bloc.outUserState,
        builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
          return Container();
        },
      ),
    ));
  }
}
 

 class SearchScreen extends StatefulWidget {
  SearchScreen({Key key}) : super(key: key);

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

class _SearchScreenState extends State<SearchScreen> {
  ISearchBloc _bloc;

  @override
  void initState() {
    super.initState();
    _bloc = BlocProvider.of(context);
    _bloc.loadUser();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: SafeArea(
      child: StreamBuilder(
        stream: _bloc.outUserState,
        builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
          return Container();
        },
      ),
    ));
  }
}
 

 class AdsScreen extends StatefulWidget {
  AdsScreen({Key key}) : super(key: key);

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

class _AdsScreenState extends State<AdsScreen> {
  IAdsBloc _bloc;

  @override
  void initState() {
    super.initState();
    _bloc = BlocProvider.of(context);
    _bloc.loadUser();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: SafeArea(
      child: StreamBuilder(
        stream: _bloc.outUserState,
        builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
          return Container();
        },
      ),
    ));
  }
}
 

Я также использую инжектор.

 class ApplicationAssembly {
  static final Injector injector = Injector('ApplicationAssemblyInjector');

  //----------------------------------------------------------------------------

  static void initialize() {
    _registerManagers();
    _registerBlocs();
    _registerModuleBuilders();
  }

  static void _registerManagers() {
  }

  static void _registerBlocs() {
    injector.map<IProfileBloc>((i) => ProfileBloc());
    injector.map<IAdsBloc>((i) => AdsBloc());
    injector.map<ISearchBloc>((i) => SearchBloc());
  }

  static void _registerModuleBuilders() {
//TAB BAR MODULES---------------------------------------------------------------
    injector.map<ProfileModuleBuilder>((i) => () {
          return BlocProvider(
              child: ProfileScreen(),
              bloc: i.get<IProfileBloc>(),
              blocContext: ProfileBlocContext());
        });
    injector.map<SearchModuleBuilder>((i) => () {
          return BlocProvider(
              child: SearchScreen(),
              bloc: i.get<ISearchBloc>(),
              blocContext: SearchBlocContext());
        });
    injector.map<AdsModuleBuilder>((i) => () {
          return BlocProvider(
              child: AdsScreen(),
              bloc: i.get<IAdsBloc>(),
              blocContext: AdsBlocContext());
        });
//------------------------------------------------------------------------------
//TAB BAR-----------------------------------------------------------------------
    injector.map<TabModuleBuilder>((i) => () {
          Widget profileTab = i.get<ProfileModuleBuilder>()();
          Widget searchTab = i.get<SearchModuleBuilder>()();
          Widget adsTab = i.get<AdsModuleBuilder>()();

          return TabContainerScreen(
            content: TabContainerContent(
                firstTab: profileTab, secondTab: searchTab, thirdTab: adsTab),
          );
        });

//------------------------------------------------------------------------------
  }
}
 

Комментарии:

1. Похоже, информации недостаточно. Не могли бы вы предоставить нам больше кода, чтобы мы могли воспроизвести проблему?

2. Для вашего outerUserState попробуйте вернуться .stream.asBroadcastStream() . Подробнее о asBroadcastStream

3. Хорошо, позвольте мне понять ваш вариант использования. Есть ли какая-то особая причина, по которой вы используете StreamController s в своем BLoC s?

4. К сожалению, это не помогло. Могу ли я как-то удалить все потоки прошлого при переключении в другое окно, а затем создать их снова?

5. Мне очень понравилась эта архитектура. И я хотел бы понять причину повторной подписки в моем пользовательском интерфейсе

Ответ №1:

У меня была такая же ошибка в моем проекте.

В моем случае мне нужно было повторно использовать StreamController. Сценарий заключается в том, что есть кнопка сброса для поиска информации с самого начала.

в любом случае я решил это следующим образом.

  1. добавить .close() к кнопке сброса.
  2. снова инициализируйте StreamController в поиске.
     if (streamController.isCloded) {
    streamController = new StreamController<List<A>>(); 
    }
 

В вашем случае, я думаю, что tabview восстанавливается (или перестраивается?) представление.
Итак, StreamController повторно инициализируется.

Я думаю, вы бы добавили инициализацию StreamController в initState.

 @override
void initState(){
super.initState();
if (streamController == null){
    streamController = new StreamController();
}
 

Я надеюсь, что это поможет вам.