Flutter Bad State Stream has been listened to

Good day Please can anyone help me with this error, the code works properly but whenever I do hot restart it shows me 'Bad state: Stream has already been listened to.'.

This is the on_boarding_view.dart

  import 'package:flutter/material.dart';
  import 'package:flutter/services.dart';
  import 'package:flutter_svg/flutter_svg.dart';
  import 'package:flutter_svg/svg.dart';
  import 'package:mvvm_design_pattern/domain/model.dart';
  import 'package:mvvm_design_pattern/presentation/on_boarding/on_boarding_view_model.dart';
  import 'package:mvvm_design_pattern/presentation/resources/assets_manager.dart';
  import 'package:mvvm_design_pattern/presentation/resources/color_manager.dart';
  import 'package:mvvm_design_pattern/presentation/resources/routes_manager.dart';
  import 'package:mvvm_design_pattern/presentation/resources/string_manager.dart';
  import 'package:mvvm_design_pattern/presentation/resources/values_manager.dart';
  
  class OnBoardingView extends StatefulWidget {
    const OnBoardingView({Key? key}) : super(key: key);
  
    @override
    _OnBoardingViewState createState() => _OnBoardingViewState();
  }
  
  class _OnBoardingViewState extends State<OnBoardingView> {
    final PageController _pageController = PageController(initialPage: 0);
    final OnBoardingViewModel _viewModel = OnBoardingViewModel();
  
    _bind() {
      _viewModel.start();
    }
  
    @override
    void initState() {
      _bind();
      super.initState();
    }
  
    @override
    Widget build(BuildContext context) {
      return StreamBuilder<SliderViewObject>(
          stream: _viewModel.outputSliderViewObject,
          builder: (context, snapShot) {
            return _getContentWidget(snapShot.data);
          });
    }
  
    Widget _getContentWidget(SliderViewObject? sliderViewObject) {
      if (sliderViewObject == null) {
        return Container();
      } else {
        return Scaffold(
          backgroundColor: ColorManager.white,
          appBar: AppBar(
            backgroundColor: ColorManager.white,
            elevation: AppSize.s0,
            systemOverlayStyle: SystemUiOverlayStyle(
              statusBarColor: ColorManager.white,
              statusBarBrightness: Brightness.dark,
              statusBarIconBrightness: Brightness.dark,
            ),
          ),
          body: PageView.builder(
              controller: _pageController,
              itemCount: sliderViewObject.numOfSlides,
              onPageChanged: (index) {
                _viewModel.onPageChanged(index);
              },
              itemBuilder: (context, index) {
                return OnBoardingPage(sliderViewObject.sliderObject);
              }),
          bottomSheet: Container(
            color: ColorManager.white,
            height: AppSize.s100,
            child: Column(
              children: [
                Align(
                    alignment: Alignment.centerRight,
                    child: TextButton(
                      onPressed: () {
                        Navigator.pushReplacementNamed(
                            context, Routes.loginRoute);
                      },
                      child: Text(
                        AppStrings.skip,
                        style: Theme.of(context).textTheme.subtitle2,
                        textAlign: TextAlign.end,
                      ),
                    )),
                // add layout for indicator and arrows
                _getBottomSheetWidget(sliderViewObject)
              ],
            ),
          ),
        );
      }
    }
  
    Widget _getBottomSheetWidget(SliderViewObject sliderViewObject) {
      return Container(
        color: ColorManager.primary,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            // left arrow
            Padding(
              padding: const EdgeInsets.all(AppPadding.p14),
              child: GestureDetector(
                child: SizedBox(
                  height: AppSize.s20,
                  width: AppSize.s20,
                  child: SvgPicture.asset(ImageAssets.leftArrowIc),
                ),
                onTap: () {
                  // go to previous slide
                  _pageController.animateToPage(_viewModel.goPrevious(),
                      duration: const Duration(milliseconds: DurationConstants.d300),
                      curve: Curves.bounceInOut);
                },
              ),
            ),
  
            // circles indicator
            Row(
              children: [
                for (int i = 0; i < sliderViewObject.numOfSlides; i++)
                  Padding(
                    padding: const EdgeInsets.all(AppPadding.p8),
                    child: _getProperCircle(i, sliderViewObject.currentIndex),
                  )
              ],
            ),
  
            // right arrow
            Padding(
              padding: const EdgeInsets.all(AppPadding.p14),
              child: GestureDetector(
                child: SizedBox(
                  height: AppSize.s20,
                  width: AppSize.s20,
                  child: SvgPicture.asset(ImageAssets.rightArrowIc),
                ),
                onTap: () {
                  // go to next slide
                  _pageController.animateToPage(_viewModel.goNext(),
                      duration: const Duration(milliseconds: DurationConstants.d300),
                      curve: Curves.bounceInOut);
                },
              ),
            )
          ],
        ),
      );
    }
  
    Widget _getProperCircle(int index, int currentIndex) {
      if (index == currentIndex) {
        return SvgPicture.asset(ImageAssets.hollowCircleIc); // selected slider
      } else {
        return SvgPicture.asset(ImageAssets.solidCircleIc); // unselected slider
      }
    }
  
    @override
    void dispose() {
      _viewModel.dispose();
      super.dispose();
    }
  }
  
  class OnBoardingPage extends StatelessWidget {
    final SliderObject _sliderObject;
  
    const OnBoardingPage(this._sliderObject, {Key? key}) : super(key: key);
  
    @override
    Widget build(BuildContext context) {
      return Column(
        mainAxisAlignment: MainAxisAlignment.start,
        children: [
          const SizedBox(height: AppSize.s40),
          Padding(
            padding: const EdgeInsets.all(AppPadding.p8),
            child: Text(
              _sliderObject.title,
              textAlign: TextAlign.center,
              style: Theme.of(context).textTheme.headline1,
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(AppPadding.p8),
            child: Text(
              _sliderObject.subTitle,
              textAlign: TextAlign.center,
              style: Theme.of(context).textTheme.subtitle1,
            ),
          ),
          const SizedBox(
            height: AppSize.s60,
          ),
          SvgPicture.asset(_sliderObject.image)
          // image widget
        ],
      );
    }
  }

This is the on_boarding_view_model.dart

    import 'dart:async';
    
    import 'package:mvvm_design_pattern/domain/model.dart';
    import 'package:mvvm_design_pattern/presentation/base/base_view_model.dart';
    import 'package:mvvm_design_pattern/presentation/resources/assets_manager.dart';
    import 'package:mvvm_design_pattern/presentation/resources/string_manager.dart';
    
    class OnBoardingViewModel extends BaseViewModel
        with OnBoardingViewModelInputs, OnBoardingViewModelOutputs {
      final StreamController _streamController =
          StreamController<SliderViewObject>();
    
      late final List<SliderObject> _list;
      int _currentIndex = 0;
    
    
      // inputs
      @override
      void dispose() {
        _streamController.close();
      }
    
      @override
      void start() {
        _list = _getSliderData();
        // send data to view
        _postDataToView();
      }
    
      @override
      int goNext() {
        int nextIndex = _currentIndex++;
        if (nextIndex >= _list.length) {
          _currentIndex = 0;
        }
        //_postDataToView();
        return _currentIndex;
      }
    
      @override
      int goPrevious() {
        int previousIndex = _currentIndex--;
        if (previousIndex == -1) {
          _currentIndex = _list.length - 1;
        }
        //_postDataToView();
        return _currentIndex;
      }
    
      @override
      void onPageChanged(int index) {
        _currentIndex = index;
        _postDataToView();
      }
    
      @override
      Sink get inputSliderViewObject => _streamController.sink;
    
      // outputs
      @override
      Stream<SliderViewObject> get outputSliderViewObject =>
          _streamController.stream.map((sliderViewObject) => sliderViewObject);
    
      // private functions
      List<SliderObject> _getSliderData() => [
        SliderObject(AppStrings.onBoardingTitle1,
            AppStrings.onBoardingSubTitle1, ImageAssets.onBoardingLogo1),
        SliderObject(AppStrings.onBoardingTitle2,
            AppStrings.onBoardingSubTitle2, ImageAssets.onBoardingLogo2),
        SliderObject(AppStrings.onBoardingTitle3,
            AppStrings.onBoardingSubTitle3, ImageAssets.onBoardingLogo3),
        SliderObject(AppStrings.onBoardingTitle4,
            AppStrings.onBoardingSubTitle4, ImageAssets.onBoardingLogo4),
      ];
    
      _postDataToView(){
        inputSliderViewObject.add(SliderViewObject(_list[_currentIndex], _list.length, _currentIndex));
      }
    }
    
    // inputs mean the orders that our view model will receive from our view
    abstract class OnBoardingViewModelInputs {
      void goNext(); // when user clicks on right arrow or swipe to left
      void goPrevious(); // when user clicks on left arrow or swipe right
      void onPageChanged(int index);
    
      Sink
          get inputSliderViewObject; // this is the way to add data to the stream .. stream inputs
    }
    
    // outputs mean data or results that will be sent from our view model to our view
    abstract class OnBoardingViewModelOutputs {
      Stream<SliderViewObject> get outputSliderViewObject;
    }
    
    class SliderViewObject {
      // things that the view needs to know
      SliderObject sliderObject;
      int numOfSlides;
      int currentIndex;
    
      SliderViewObject(this.sliderObject, this.numOfSlides, this.currentIndex);
    }

Can someone please explain to me why the code is doing like that, and show me if it is possible to fix it.

Upvotes: 0

Views: 805

Answers (1)

Denzel
Denzel

Reputation: 1109

final StreamController _streamController =
          StreamController<SliderViewObject>.broadcast();

You need to make your stream a broadcast stream so that you can continue listening to it. You current stream by default will be listened to only once. To learn more check this video here

Upvotes: 0

Related Questions