How to push to page when async function is finished? - flutter

Im developing a app that when the user enters the first screen is a loading screen where initialize somethings from the Provider, the user cant input anything in this screen, when the loading finishes i want to push a new screen without the user "clicking" for it.
In this code, im actually given a delay of 3 seconds for the _login.getStoredEmail() to run and set a variable inside LoginController which in the next screen i consume, but of course this wont work everytime, eventually will break.
class GeneralSplashScreen extends StatefulWidget {
#override
_GeneralSplashScreenState createState() => _GeneralSplashScreenState();
}
class _GeneralSplashScreenState extends State<GeneralSplashScreen> {
#override
void initState() {
Future.delayed(
Duration(
seconds: 3,
),
() {
Navigator.pushReplacementNamed(context, kRoutes.login);
},
);
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
final LoginController _login = Provider.of<LoginController>(context);
_login.getStoredEmail();
return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 60),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Image.asset(
'lib/assets/images/logo.png',
fit: BoxFit.contain,
),
Text(
"Business Mananger",
textAlign: TextAlign.end,
style: TextStyle(
fontFamily: kFontFamily.montserrat,
fontSize: 10,
),
),
LoadingBar(),
],
),
),
);
}
}

Navigate to a different screen inside the initState() method.
initState() {
initializeAndNavigate()
}
initializeAndNavigate() async {
await initializeSomething();
Navigator.of(context) ...
}

Declare initState
Future _future;
#override
void initState() {
// TODO: implement initState
super.initState();
_future = doStuff();
}
Use FutureBuilder
FutureBuilder(
future: _future,
builder: (_, dataSnapshot) {
if (dataSnapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator()); // here add loading screen
} else {
return Dashboard();
}
},
)

Related

Flutter Looping Endlessly when adding a FutureBuilder Inside another FutureBuilder

I have a Stop Watch timer in a Flutter Screen which works as required in the beginning inside a FutureBuilder but when I add another futurebuilder to add information from an API it keeps looping indefinitely:
Initially when I opened the screen it was looping indefinelty then when I adjusted the code it looped endlessly only when I clicked the Start button, it continued looping even after clicking the stop button.
Here is the dart file:
class Screen extends StatefulWidget {
#override
_ScreenState createState() => _ScreenState();
}
class _ScreenState extends State<Screen> {
bool isStartButtonDisabled = false;
bool isStopButtonDisabled = true;
Stopwatch _stopwatch = Stopwatch();
String _elapsedTime = "00:00:00";
late Timer _timer;
void _startStopwatch() {
print("void _startStopwatch() { Starting stopwatch");
_stopwatch.start();
setState(() {
isStartButtonDisabled = true;
isStopButtonDisabled = false;
});
_timer = Timer.periodic(
const Duration(seconds: 1),
(Timer timer) {
if (mounted) {
setState(() {
_elapsedTime = _stopwatch.elapsed.toString().split(".")[0];
});
}
},
);
}
#override
void dispose() {
_timer?.cancel();
super.dispose();
}
Here is ui
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView(
children: [
Container(
child: Column(
children: [
GFButton(
onPressed: isStartButtonDisabled ? null : startWorkout,
text: "Start Workout",
),
Text(
_elapsedTime,
),
Card(
child: Padding(
padding: const EdgeInsets.all(18.0), //change here
child: Column(
children: [
FutureBuilder<List<Model_No_1>>(
future: futureModel_No_1,
builder: (BuildContext context,
AsyncSnapshot<List<Model_No_1>> snapshot) {
if (snapshot.hasData) {
return Column(
children: List.generate(snapshot.data!.length,
(int index) {
String Some_VariableName =
snapshot.data![index].name;
return Column(
children: [
Text(
snapshot.data![index].name,
),
Builder(builder: (context) {
return Container(
child: SingleChildScrollView(
child: Column(
children: [
Card(
child: Row(
children: [
Expanded(
child: FutureBuilder<
Get_Old_Model_No>(
future: () {
final Map<String,dynamic> arguments =ModalRoute.of(context)!.settings.arguments as Map<String,dynamic>;final int id =arguments['id'] ??
0;print("This is the id $id");return APIService.Get_Old_Model_No(id);}(),builder:(context, snapshot) {print("Snapshot data:${snapshot.data}");print("Snapshot error:${snapshot.error}");
if (snapshot.hasData) {
return Column(
children: [
// Text(snapshot
// .data
// ?.endDate),
],
);
} else if (snapshot
.hasError) {
return Text(
"${snapshot.error}");
}
return CircularProgressIndicator();
},),),],),),
for (var breakdown in snapshot.data![index].breakdowns)
Form(child: Expanded(child: Column(children: [TextFormField(
keyboardType:TextInputType.number,
onChanged:(value) {
final int?parsedValue =
int.tryParse(value);if (parsedValue !=
null) {setState(
() {variable1 =parsedValue;});} else {}
},),],),),),
When I add the Expanded, which runs the APIService.Get_Old_Model_No(id);
My question is why is the indefinete looping happening? How can I fix it ?
i cannot reproduce your problem from your provided code, but having multiple encapsuled futureBuilders shouldn't be a problem.
Any chance that the setState gets called from within one of your future(Builder)s?
That would trigger a rebuild, reloading the future and then setting the state again, triggering a rebuilt, and so on
When you call setState the build method is called, this triggers the FutureBulder to rebuild, thereby calling the future again.
What I see happening in your case is that once the _startStopwatch is called, the timer is started and it calls setState every second. I can't see your stop function, but it seems you are not canceling the Timer.periodic when you stop the stopWatch.
To solve your problem, you need to manage your state in such a way that only the necessary widget rebuild when the state changes. To achieve this you will need more than setState. See a simple example below using ValueNotifier and ValueListenableBuilder.
class Screen extends StatefulWidget {
#override
_ScreenState createState() => _ScreenState();
}
class _ScreenState extends State<Screen> {
ValueNotifier<bool> isStartButtonDisabled = ValueNotifier(false);
ValueNotifier<bool> isStopButtonDisabled = ValueNotifier(true);
Stopwatch _stopwatch = Stopwatch();
ValueNotifier<String> _elapsedTime = ValueNotifier("00:00:00");
late Timer _timer;
void _startStopwatch() {
print("void _startStopwatch() { Starting stopwatch");
_stopwatch.start();
isStartButtonDisabled.value = true;
isStopButtonDisabled.value = false;
_timer = Timer.periodic(
const Duration(seconds: 1),
(Timer timer) {
if (mounted) {
_elapsedTime.value = _stopwatch.elapsed.toString().split(".")[0];
}
},
);
}
#override
void dispose() {
_timer?.cancel();
super.dispose();
}
Now for the widgets that need to rebuild when the state changes, just use ValueListenableBuilder, See below.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView(
children: [
Container(
child: Column(
children: [
ValueListenableBuilder<bool>(valueListenable: isStartButtonDisabled, builder: (context, value, child)=> GFButton(
onPressed:value ? null : startWorkout,
text: "Start Workout",
)),
ValueListenableBuilder<String>(valueListenable: _elapsedTime, builder: (context, value, child)=> Text(
value,
)),
In this way when _elapsedTime changes, only the Text widget is rebuilt, similarly, when isStartButtonDisabled changes, only GFButton is rebuilt.
Also remember to cancel the Timer when you stop the StopWatch.
You are defining and calling a function in your build method with is getting called every time you set state, which is occuring at every tick of your timer.
() {
final Map<String,dynamic> arguments = ModalRoute.of(context)!.settings.arguments as Map<String,dynamic>;final
int id =arguments['id'] ?? 0;
print("This is the id $id");
return APIService.Get_Old_Model_No(id);
}()

implementing bloc pattern with API cause an exception "type 'Future<dynamic>' is not a subtype of type 'Widget?'"

I am trying to implement bloc pattern in which I am using a repository class which consist all the methods which makes the calls with the API. On the other side I am implementing BlocBuilder to render the view based on bloc state however i am getting this error BlocBuilder<VehiclesBloc, VehiclesState>(dirty, dependencies: [_LocalizationsScope-[GlobalKey#df8d0]], state: _BlocBuilderBaseState<VehiclesBloc, VehiclesState>#dba40):
type 'Future' is not a subtype of type 'Widget?'
I am really not sure where the issues comes from. here are some snippets of the code.
this is the bloc class which causes the error
class VehiclesBloc extends Bloc<VehiclesEvent,VehiclesState>{
VehiclesBloc(VehiclesState initialState) : super(initialState);
#override
Stream<VehiclesState> mapEventToState(VehiclesEvent event) async* {
// TODO: implement mapEventToState
if(event is LoadVehiclesList){
yield* mapLoadEventToState(event);
}
}
Stream<VehiclesState> mapLoadEventToState(LoadVehiclesList event) async* {
if(event is LoadVehiclesList){
var response = await VehiclesService().getAll();
if(response.IsSuccess){
yield VehiclesLoaded(response.Data);
}else{
yield VehiclesLoadingFailed(response.ErrorList.toString());
}
}else{
yield VehiclesLoading();
}
}
}
here is the Statefull widget which implements the Bloc Builder
class VehicleList extends StatefulWidget {
const VehicleList({Key key}) : super(key: key);
static const String routeName = "/VehicleList";
//final ScrollController scrollController;
#override
_VehicleListState createState() => _VehicleListState();
}
class _VehicleListState extends State<VehicleList> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
VehiclesBloc vehiclesBloc =
VehiclesBloc(VehiclesLoading())..add(LoadVehiclesList());
#override
void initState() {
// TODO: implement initState
super.initState();
//VehiclesService().getAll();
}
#override
void dispose() {
// TODO: implement dispose
vehiclesBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
final isRtl = context.locale.languageCode == "ar";
return Scaffold(
key: _scaffoldKey,
backgroundColor: kBackgroundColor,
drawer: SideNavigationDrawer(),
body: Container(
child: Column(
children: [
SizedBox(
height: 15,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
IconButton(
onPressed: () {
_scaffoldKey.currentState.openDrawer();
},
icon: Icon(
Icons.menu,
size: 35,
color: Colors.black,
),
)
],
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
BlocBuilder<VehiclesBloc,VehiclesState>(
builder: (context, state) {
if (state is VehiclesLoaded) {
// return BuildListVehicle(state.lsVehicle);
return Center();
} else if (state is VehiclesLoadingFailed) {
return Center(
child: CustomErrorWidget(),
);
} else {
return Center(
child: LoadingDialog.showLoadingDialog(context,
text: ""),
);
}
},
cubit: vehiclesBloc,
),
],
),
),
)
],
),
));
}
I think this code part causes the problem:
return Center(
child: LoadingDialog.showLoadingDialog(context,text: ""),
);
Possibly, LoadingDialog.showLoadingDialog does not return a Widget but is just a function that returns Future.
For side effects (e.g. you want to show the dialog), you should use listeners instead of executing such code inside the build method. Instead of BlocBuilder, just use BlocConsumer and add the listener:
BlocConsumer<VehiclesBloc,VehiclesState>(
listener: (context, state) {
if (state is {your loading state}) {
LoadingDialog.showLoadingDialog(context, text: "");
}
},
builder: ...,
),
Some more insights about your code:
Instead of creating BLoC as a variable in your stateful widget, use BlocProvider that would handle create/dispose part of your BLoC.
Yield the VehiclesLoading state before loading the data and not just as an "else" case. This way you could handle the loading behaviour easily in your UI.
To fix the above issues, just follow the documentation: https://bloclibrary.dev/

Using video_player package with Flutter Hooks to play a background fullscreen video

I have a Home Screen Widget, that plays a fullscreen background video using the video_player package.
This code works fine for me:
class HomeScreen extends StatefulWidget {
HomeScreen({Key key}) : super(key: key);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
VideoPlayerController _controller;
void initState() {
super.initState();
// Pointing the video controller to mylocal asset.
_controller = VideoPlayerController.asset("assets/waterfall.mp4");
_controller.initialize().then((_) {
// Once the video has been loaded we play the video and set looping to true.
_controller.play();
_controller.setLooping(true);
// Ensure the first frame is shown after the video is initialized.
setState(() {});
});
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Stack(
children: <Widget>[
SizedBox.expand(
child: FittedBox(
// If your background video doesn't look right, try changing the BoxFit property.
// BoxFit.fill created the look I was going for.
fit: BoxFit.fill,
child: SizedBox(
width: _controller.value.size?.width ?? 0,
height: _controller.value.size?.height ?? 0,
child: VideoPlayer(_controller),
),
),
),
Container(
child: Center(
child: Text('Hello!'),
),
),
],
),
),
);
}
}
The question is, how can I implement this using flutter Hooks? I understand that I have to use useEffect() to implement the functionality of initState() and dispose(), useFuture() and maybe useMemoized() to handle asynchronous _controller.initialize() call and what possibly else? But, I cannot glue them to get the desired result. Can anyone indicate to me the "using Hooks" implementation of the above code?
I was looking for the answer to how to convert a VideoPlayer demo from StatefulWidget to HookWidget when I came across this question. I've come up with something that works so I'll post it here since there is nothing elsewhere that I could find and some others are hitting this page looking for an answer.
I used a viewmodel. The video controller is a property of the viewmodel. This code will not compile since some of the controls are not included. But it will demonstrate the structure and incorporation of the viewmodel.
Here's the widget file:
import 'package:flutter/foundation.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:video_player/video_player.dart';
import 'intro_viewmodel.dart';
class IntroPage extends HookWidget {
Future<void> saveAndGetStarted(BuildContext context) async {
final IntroViewModel introViewModel = context.read(introViewModelProvider);
await introViewModel.completeIntro();
}
Future<void> onNext(BuildContext context) async {
final IntroViewModel introViewModel = context.read(introViewModelProvider);
await introViewModel.incrementIntro();
}
final List<SliderModel> slides = [
SliderModel(
description: 'A word with you before you get started.\n',
title: 'Why This App?',
localImageSrc: 'media/Screen1-Movingforward-pana.svg',
backgroundColor: Colors.lightGray),
SliderModel(
description: 'This information will help the app be more accurate\n',
title: 'Personal Profile',
localImageSrc: 'media/Screen2-Teaching-cuate.svg',
backgroundColor: Colors.lightGray)
];
#override
Widget build(BuildContext context) {
final IntroViewModel introViewModel = context.read(introViewModelProvider);
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Column(
children: [
Text(
slides[introViewModel.index].description,
style: Theme.of(context).textTheme.headline5,
textAlign: TextAlign.center,
),
Expanded(
child: FractionallySizedBox(
widthFactor: .98,
heightFactor: .5,
child: VideoPlayer(introViewModel.videoController),
)),
Align(
alignment: Alignment.bottomCenter,
child: CustomRaisedButton(
onPressed: () {
if (introViewModel.index == slides.length - 1) {
saveAndGetStarted(context);
} else {
onNext(context);
}
},
color: Theme.of(context).accentColor,
borderRadius: 15,
height: 50,
child: Text(
introViewModel.index == 0
? 'Continue'
: 'Save and Get Started',
style: Theme.of(context)
.textTheme
.headline5
.copyWith(color: Colors.white),
),
),
),
],
),
),
));
}
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IterableProperty<SliderModel>('slides', slides));
}
}
And here is the viewmodel code
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:video_player/video_player.dart';
import '../top_level_providers.dart';
final introViewModelProvider = ChangeNotifierProvider<IntroViewModel>((ref) {
//this singleton class provides global access to selected variables
final SharedPreferencesService localSharedPreferencesService =
ref.watch(sharedPreferencesService);
return IntroViewModel(localSharedPreferencesService);
});
class IntroViewModel extends ChangeNotifier {
IntroViewModel(this.localSharedPreferencesService) : super() {
state = localSharedPreferencesService?.isIntroComplete();
// Pointing the video controller to my local asset.
videoController = VideoPlayerController.asset('media/test_search.mp4');
videoController.initialize().then((_) {
// Once the video has been loaded we play the video and set looping to true.
// not autoplaying yet
// videoController.play();
// videoController.setLooping(true);
});
}
final SharedPreferencesService localSharedPreferencesService;
VideoPlayerController videoController;
bool state = false;
int index = 0;
Future<void> completeIntro() async {
await localSharedPreferencesService.setIntroComplete();
state = true;
notifyListeners();
}
Future<void> incrementIntro() async {
++index;
notifyListeners();
}
bool get isIntroComplete => state;
}

Flutter : PageController.page cannot be accessed before a PageView is built with it

How to solve the exception -
Unhandled Exception: 'package:flutter/src/widgets/page_view.dart': Failed assertion: line 179 pos 7: 'positions.isNotEmpty': PageController.page cannot be accessed before a PageView is built with it.
Note:- I used it in two screens and when I switch between screen it shows the above exception.
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _animateSlider());
}
void _animateSlider() {
Future.delayed(Duration(seconds: 2)).then(
(_) {
int nextPage = _controller.page.round() + 1;
if (nextPage == widget.slide.length) {
nextPage = 0;
}
_controller
.animateToPage(nextPage,
duration: Duration(milliseconds: 300), curve: Curves.linear)
.then(
(_) => _animateSlider(),
);
},
);
}
I think you can just use a Listener like this:
int _currentPage;
#override
void initState() {
super.initState();
_currentPage = 0;
_controller.addListener(() {
setState(() {
_currentPage = _controller.page.toInt();
});
});
}
I don't have enough information to see exactly where your problem is, but I just encountered a similar issue where I wanted to group a PageView and labels in the same widget and I wanted to mark active the current slide and the label so I was needing to access controler.page in order to do that. Here is my fix :
Fix for accessing page index before PageView widget is built using FutureBuilder widget
class Carousel extends StatelessWidget {
final PageController controller;
Carousel({this.controller});
/// Used to trigger an event when the widget has been built
Future<bool> initializeController() {
Completer<bool> completer = new Completer<bool>();
/// Callback called after widget has been fully built
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
completer.complete(true);
});
return completer.future;
} // /initializeController()
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
// **** FIX **** //
FutureBuilder(
future: initializeController(),
builder: (BuildContext context, AsyncSnapshot<void> snap) {
if (!snap.hasData) {
// Just return a placeholder widget, here it's nothing but you have to return something to avoid errors
return SizedBox();
}
// Then, if the PageView is built, we return the labels buttons
return Column(
children: <Widget>[
CustomLabelButton(
child: Text('Label 1'),
isActive: controller.page.round() == 0,
onPressed: () {},
),
CustomLabelButton(
child: Text('Label 2'),
isActive: controller.page.round() == 1,
onPressed: () {},
),
CustomLabelButton(
child: Text('Label 3'),
isActive: controller.page.round() == 2,
onPressed: () {},
),
],
);
},
),
// **** /FIX **** //
PageView(
physics: BouncingScrollPhysics(),
controller: controller,
children: <Widget>[
CustomPage(),
CustomPage(),
CustomPage(),
],
),
],
);
}
}
Fix if you need the index directly in the PageView children
You can use a stateful widget instead :
class Carousel extends StatefulWidget {
Carousel();
#override
_HomeHorizontalCarouselState createState() => _CarouselState();
}
class _CarouselState extends State<Carousel> {
final PageController controller = PageController();
int currentIndex = 0;
#override
void initState() {
super.initState();
/// Attach a listener which will update the state and refresh the page index
controller.addListener(() {
if (controller.page.round() != currentIndex) {
setState(() {
currentIndex = controller.page.round();
});
}
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Column(
children: <Widget>[
CustomLabelButton(
child: Text('Label 1'),
isActive: currentIndex == 0,
onPressed: () {},
),
CustomLabelButton(
child: Text('Label 2'),
isActive: currentIndex == 1,
onPressed: () {},
),
CustomLabelButton(
child: Text('Label 3'),
isActive: currentIndex == 2,
onPressed: () {},
),
]
),
PageView(
physics: BouncingScrollPhysics(),
controller: controller,
children: <Widget>[
CustomPage(isActive: currentIndex == 0),
CustomPage(isActive: currentIndex == 1),
CustomPage(isActive: currentIndex == 2),
],
),
],
);
}
}
This means that you are trying to access PageController.page (It could be you or by a third party package like Page Indicator), however, at that time, Flutter hasn't yet rendered the PageView widget referencing the controller.
Best Solution: Use FutureBuilder with Future.value
Here we just wrap the code using the page property on the pageController into a future builder, such that it is rendered little after the PageView has been rendered.
We use Future.value(true) which will cause the Future to complete immediately but still wait enough for the next frame to complete successfully, so PageView will be already built before we reference it.
class Carousel extends StatelessWidget {
final PageController controller;
Carousel({this.controller});
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
FutureBuilder(
future: Future.value(true),
builder: (BuildContext context, AsyncSnapshot<void> snap) {
//If we do not have data as we wait for the future to complete,
//show any widget, eg. empty Container
if (!snap.hasData) {
return Container();
}
//Otherwise the future completed, so we can now safely use the controller.page
return Text(controller.controller.page.round().toString);
},
),
//This PageView will be built immediately before the widget above it, thanks to
// the FutureBuilder used above, so whenever the widget above is rendered, it will
//already use a controller with a built `PageView`
PageView(
physics: BouncingScrollPhysics(),
controller: controller,
children: <Widget>[
AnyWidgetOne(),
AnyWidgetTwo()
],
),
],
);
}
}
Alternatively
Alternatively, you could still use a FutureBuilder with a future that completes in addPostFrameCallback in initState lifehook as it also will complete the future after the current frame is rendered, which will have the same effect as the above solution. But I would highly recommend the first solution as it is straight-forward
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
//Future will be completed here
// e.g completer.complete(true);
});
use this widget and modify it as you want:
class IndicatorsPageView extends StatefulWidget {
const IndicatorsPageView({
Key? key,
required this.controller,
}) : super(key: key);
final PageController controller;
#override
State<IndicatorsPageView> createState() => _IndicatorsPageViewState();
}
class _IndicatorsPageViewState extends State<IndicatorsPageView> {
int _currentPage = 0;
#override
void initState() {
widget.controller.addListener(() {
setState(() {
_currentPage = widget.controller.page?.toInt() ?? 0;
});
});
super.initState();
}
#override
void dispose() {
widget.controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
3,
(index) => IndicatorPageview(isActive: _currentPage == index, index: index),
),
);
}
}
class IndicatorPageview extends StatelessWidget {
const IndicatorPageview({
Key? key,
required this.isActive,
required this.index,
}) : super(key: key);
final bool isActive;
final int index;
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(right: 8),
width: 16,
height: 16,
decoration: BoxDecoration(color: isActive ?Colors.red : Colors.grey, shape: BoxShape.circle),
);
}
}

AlertDialog function runs on everypage in flutter

theAlertDialog in this code should run only when the user access the Account page (obviously), but while testing it, it runs on the Account page and all the next pages, and it is even duplicated, i mean when i head from Account page to another page the AlertDialog will be displayed twice
class Account extends StatefulWidget {
#override
_AccountState createState() => _AccountState();
}
class _AccountState extends State<Account> {
#override
Widget build(BuildContext context) {
Future.delayed(Duration.zero, () => FirstRun(context));
return Scaffold(
//there are alot of widgets here like drawer but all of it works fine
//i don't think its necessary to write it
);
}
FirstRun(BuildContext context) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool first = (prefs.getBool('firstUse'));
print('Pressed $first');
if (first == null) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Color(0xaa6b6b6b),
elevation: 10,
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'first run dialog',
overflow: TextOverflow.ellipsis,
maxLines: 6,
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
textAlign: TextAlign.center,
),
Container(
child: MaterialButton(
onPressed: () {
prefs.setBool('firstUse', false);
Navigator.of(context).pop();
print('Pressed $first');
},
child: Text(
'ok',
),
))
],
),
);
},
);
}
}
}
maybe it happens because you start showing the alert on build method. try to show it on initState method of the Account widget.
class _AccountState extends State<Account> {
#override
initState() {
Future.delayed(Duration.zero, () => FirstRun(this.context));
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
//there are alot of widgets here like drawer but all of it works fine
//i don't think its necessary to write it
);
}
as a work around i call the next page using Navigator.Replace instead of Navigator.push , but i dont think this is a real solution