Reputation: 1
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
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