While the onBoarding screen is running, as I am trying to edit it, it is just gives the below error:
Bad state: Stream has already been listened to.
The relevant error-causing widget was:
OnBoardingView OnBoardingView:file:///Users/mac/Desktop/My%20Portofolio/saibmob/lib/presentation/resources/routes_manager.dart:32:56
When the exception was thrown, this was the stack:
#6 _StreamBuilderBaseState._subscribe (package:flutter/src/widgets/async.dart:134:38)
#7 _StreamBuilderBaseState.initState (package:flutter/src/widgets/async.dart:108:5)
#8 StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4893:57)
#9 ComponentElement.mount (package:flutter/src/widgets/framework.dart:4729:5)
#10 Element.inflateWidget (package:flutter/src/widgets/framework.dart:3790:14)
#11 Element.updateChild (package:flutter/src/widgets/framework.dart:3524:20)
#12 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4780:16)
#13 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4928:11)
#14 Element.rebuild (package:flutter/src/widgets/framework.dart:4477:5)
#15 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2659:19)
#16 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:882:21)
#17 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:363:5)
#18 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1144:15)
#19 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1081:9)
#20 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:995:5)
#24 _invoke (dart:ui/hooks.dart:151:10)
#25 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:308:5)
#26 _drawFrame (dart:ui/hooks.dart:115:31)
(elided 9 frames from dart:async)
====================================================================================================
and this is the OnBoardingView
:
import 'package:saibmob/app/app_prefs.dart';
import 'package:saibmob/domain/model/models.dart';
import 'package:saibmob/presentation/onboarding/viewmodel/onboarding_viewmodel.dart';
import 'package:saibmob/presentation/resources/assets_manager.dart';
import 'package:saibmob/presentation/resources/color_manager.dart';
import 'package:saibmob/presentation/resources/routes_manager.dart';
import 'package:saibmob/presentation/resources/strings_manager.dart';
import 'package:saibmob/presentation/resources/values_manager.dart';
import 'package:easy_localization/easy_localization.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 '../../../app/di.dart';
import '../../resources/constants_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();
final OnBoardingViewModel _viewModel = OnBoardingViewModel();
final AppPreferences _appPreferences = instance<AppPreferences>();
_bind() {
_appPreferences.setOnBoardingScreenViewed();
_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),
),
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,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () {
Navigator.pushReplacementNamed(context, Routes.loginRoute);
},
child: Text(
AppStrings.skip,
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.end,
).tr(),
),
),
// widgets 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(
width: AppSize.s20,
height: AppSize.s20,
child: SvgPicture.asset(ImageAssets.leftArrowIc),
),
onTap: () {
// go to previous slide
_pageController.animateToPage(_viewModel.goPrevious(),
duration: const Duration(
milliseconds: AppConstants.sliderAnimationTime),
curve: Curves.bounceInOut);
},
),
)
// circle indicator
// right arrow
,
Row(
children: [
for (int i = 0; i < sliderViewObject.numOfSlides; i++)
Padding(
padding: const EdgeInsets.all(AppPadding.p8),
child: _getProperCircle(i, sliderViewObject.currentIndex),
)
],
),
Padding(
padding: const EdgeInsets.all(AppPadding.p14),
child: GestureDetector(
child: SizedBox(
width: AppSize.s20,
height: AppSize.s20,
child: SvgPicture.asset(ImageAssets.rightArrowIc),
),
onTap: () {
// go to previous slide
_pageController.animateToPage(_viewModel.goNext(),
duration: const Duration(
milliseconds: AppConstants.sliderAnimationTime),
curve: Curves.bounceInOut);
}),
)
],
),
);
}
Widget _getProperCircle(int index, int currentIndex) {
if (index == currentIndex) {
return SvgPicture.asset(ImageAssets.hollowCircleIc);
} else {
return SvgPicture.asset(ImageAssets.solidCircleIc);
}
}
@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.displayLarge,
),
),
Padding(
padding: const EdgeInsets.all(AppPadding.p8),
child: Text(
_sliderObject.subTitle,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium,
),
),
const SizedBox(height: AppSize.s60),
SvgPicture.asset(_sliderObject.image)
],
);
}
}
and this is the OnBoarding view model:
import 'dart:async';
import 'dart:ffi';
import 'package:saibmob/domain/model/models.dart';
import 'package:easy_localization/easy_localization.dart';
import '../../base/baseviewmodel.dart';
import '../../resources/assets_manager.dart';
import '../../resources/strings_manager.dart';
class OnBoardingViewModel extends BaseViewModel
with OnBoardingViewModelInputs, OnBoardingViewModelOutputs {
// stream controllers outputs
final StreamController _streamController =
StreamController<SliderViewObject>();
late final List<SliderObject> _list;
int _currentIndex = 0;
//OnBoarding ViewModel Inputs
@override
void dispose() {
_streamController.close();
}
@override
void start() {
// view model start your job
_list = _getSliderData();
_postDataToView();
}
@override
int goNext() {
int nextIndex = ++_currentIndex;
if (nextIndex == _list.length) {
nextIndex = 0;
}
return nextIndex;
}
@override
int goPrevious() {
int previousIndex = --_currentIndex;
if (previousIndex == -1) {
previousIndex = _list.length - 1;
}
return previousIndex;
}
@override
void onPageChanged(int index) {
_currentIndex = index;
_postDataToView();
}
@override
Sink get inputSliderViewObject => _streamController.sink;
//OnBoarding ViewModel outputs
@override
Stream<SliderViewObject> get outputSliderViewObject =>
_streamController.stream.map((sliderViewObject) => sliderViewObject);
// onboarding private functions
void _postDataToView() {
inputSliderViewObject.add(
SliderViewObject(_list[_currentIndex], _list.length, _currentIndex));
}
List<SliderObject> _getSliderData() => [
SliderObject(AppStrings.onBoardingTitle1.tr(),
AppStrings.onBoardingSubTitle1.tr(), ImageAssets.onboardingLogo1),
SliderObject(AppStrings.onBoardingTitle2.tr(),
AppStrings.onBoardingSubTitle2.tr(), ImageAssets.onboardingLogo2),
SliderObject(AppStrings.onBoardingTitle3.tr(),
AppStrings.onBoardingSubTitle3.tr(), ImageAssets.onboardingLogo3),
SliderObject(AppStrings.onBoardingTitle4.tr(),
AppStrings.onBoardingSubTitle4.tr(), ImageAssets.onboardingLogo4),
];
}
// inputs mean that "Orders" that our view model will receive from view
abstract class OnBoardingViewModelInputs {
int goNext(); // when user clicks on right arrow or swipe left
int goPrevious(); // when user clicks on left arrow or swipe right
void onPageChanged(int index);
// stream controller input
Sink get inputSliderViewObject;
}
abstract class OnBoardingViewModelOutputs {
// stream controller output
Stream<SliderViewObject> get outputSliderViewObject;
}
and this is the route manager screen:
import 'package:saibmob/presentation/forgot_password/forgot_password_view.dart';
import 'package:saibmob/presentation/login/view/login_view.dart';
import 'package:saibmob/presentation/main/main_view.dart';
import 'package:saibmob/presentation/onboarding/view/onboarding_view.dart';
import 'package:saibmob/presentation/register/view/register_view.dart';
import 'package:saibmob/presentation/resources/strings_manager.dart';
import 'package:saibmob/presentation/splash/splash_view.dart';
import 'package:saibmob/presentation/store_details/store_details_view.dart';
import 'package:flutter/material.dart';
import '../../app/di.dart';
import 'package:easy_localization/easy_localization.dart';
class Routes {
static const String splashRoute = "/";
static const String loginRoute = "/login";
static const String registerRoute = "/register";
static const String forgotPasswordRoute = "/forgotPassword";
static const String onBoardingRoute = "/onBoarding";
static const String mainRoute = "/main";
static const String storeDetailsRoute = "/storeDetails";
}
class RouteGenerator {
static Route<dynamic> getRoute(RouteSettings settings) {
switch (settings.name) {
case Routes.splashRoute:
return MaterialPageRoute(builder: (_) => const SplashView());
case Routes.loginRoute:
initLoginModule();
return MaterialPageRoute(builder: (_) => const LoginView());
case Routes.onBoardingRoute:
return MaterialPageRoute(builder: (_) => const OnBoardingView());
case Routes.registerRoute:
initRegisterModule();
return MaterialPageRoute(builder: (_) => const RegisterView());
case Routes.forgotPasswordRoute:
initForgotPasswordModule();
return MaterialPageRoute(builder: (_) => const ForgotPasswordView());
case Routes.mainRoute:
initHomeModule();
return MaterialPageRoute(builder: (_) => const MainView());
case Routes.storeDetailsRoute:
initStoreDetailsModule();
return MaterialPageRoute(builder: (_) => const StoreDetailsView());
default:
return unDefinedRoute();
}
}
static Route<dynamic> unDefinedRoute() {
return MaterialPageRoute(
builder: (_) => Scaffold(
appBar: AppBar(
title: Text(AppStrings.noRouteFound.tr()),
),
body: Center(child: Text(AppStrings.noRouteFound.tr())),
));
}
}