Flutter: update page before running method in setState() - flutter

I need the run a method in a setState() after some previous variables in the same setState() have been updated. At the moment it waits for the method to complete before updating the page.
(I'm new to Flutter and my programming skills aren't very good).
I have tried splitting the setState() into different ones:
// this method is called once the animation has finished
void _solve() {
setState(() {
_isSolving = true; // I need to update the display here
});
setState(() {
_solution = PuzzleSolver().solve(); // this takes some time and returns a map
});
setState(() {
_isSolving = false; // and I need to update the display again here
});
}
But this didn't help, because I don't really know how it all works.
Here's the stripped down version of the code:
import 'package:flutter/material.dart';
import './puzzleSolver.dart';
class SomePage extends StatefulWidget {
#override
_SomePageState createState() => _SomePageState();
}
class _SomePageState extends State<SomePage> with TickerProviderStateMixin {
AnimationController animationController;
Map _solution = {};
bool _isSolving = false;
void initState() {
super.initState();
animationController = AnimationController(
vsync: this, duration: Duration(seconds: 5))
..addStatusListener(
(state) => (state == AnimationStatus.dismissed) ? _solve() : null); // run _solve() once the animation has finished
animationController.reverse();
}
// this method is called once the animation has finished
void _solve() {
setState(() {
_isSolving = true; // I need to update the display here
_solution = PuzzleSolver().solve(); // this takes some time and returns a map
_isSolving = false; // and I need to update the display here again
});
// at the moment it updates the display here
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Some Page'),
),
body: Column(
children: <Widget>[
(_isSolving == false && _solution.isEmpty)
? Text('hello world') // only show when it's not solving and there is no solution
: RaisedButton(
child: Text('show solution'),
onPressed: (_isSolving)
? null // disable the button when its solving
: () {}, // enable it when its solved
),
AnimatedBuilder(
animation: animationController,
builder: (BuildContext context, Widget child) {
return Container(); // this is where the animated widgets would be
}
),
],
),
);
}
}

Assuming the PuzzleSolver().solve() method is a Future, you can do the following:
void _solve() async{
setState(() {
_isSolving = true;
});
_solution = await PuzzleSolver().solve(); // waits for the future to finish
setState(() {
_isSolving = false;
});

Related

Using Floating action button with PageView

I'm trying to use Flutter FAB in PageView, but since the FAB takes a variable that is initialized in the page 2, it creates an error.
So how can i show the FAB only after the second page?
You can use PageView on top level, and use scaffold on every item.It can also be done by adding listener to the pageController.
class FabG extends StatefulWidget {
const FabG({super.key});
#override
State<FabG> createState() => _FabGState();
}
class _FabGState extends State<FabG> {
bool isFabActivePage = false;
late final PageController controller = PageController()
..addListener(() {
if (controller.hasClients && (controller.page ?? 0) < 2) { //your logic here
isFabActivePage = false;
setState(() {});
} else if (isFabActivePage == false) {
isFabActivePage = true;
setState(() {});
}
});
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton:
isFabActivePage ? FloatingActionButton(onPressed: () {}) : null,
body: PageView.builder(
controller: controller,
itemBuilder: (context, index) => Center(child: Text("page ${index+1}")),
),
);
}
}
The solution I used was to set a callback with the bool floating.
on the home page:
`
bool floating = false;
showFAB(show) {
setState(() {
floating = show;
});
}`
floatingActionButton:(floating) ? FloatingMenu(pageController: pageController) : null,
then on the initState function of second page used the callback to change the bool value

How to open a Dialog in flutter when a value changes in a ChangeNotifierProvider

I have a screen that is using values from a ChangeNotifierProvider to display the content of the screen. As expected when these values change the screen build method is triggered and the screen content is updated.
I would like that if one of those values changes from false to true, a Dialog is opened on that screen.
The only way I figured out to open this Dialog is by launching it from the build method when the value has changed and a new build is called.
I know that the showDialog is an async method while build is sync and it is an antipattern to manage these side effects from inside the build method. I cannot know when the build method will be called and this could lead to having several dialogs opened everytime the build is called.
So, so far I can only open the dialog from the build method and using a boolean flag in memory to control if the Dialog is opened or not. Like this:
class MyScreen extends StatefulWidget {
const MyScreen();
#override
State<StatefulWidget> createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
late final myModel = Provider.of<MyModel?>(context);
bool _isDialogShowing = false;
#override
void initState() {
super.initState();
...
}
void _showMyDialog(BuildContext ctx) {
showDialog(
context: ctx,
barrierDismissible: false,
builder: (context) => AlertDialog(
content: const Text("Hello I am a dialog"),
),
).then((val) {
// The dialog has been closed
_isDialogShowing = true;
});
}
#override
Widget build(BuildContext context) {
// Open dialog if value from `ChangeNotifierProvider` has changed
if (myModel?.hasToShowDialog == true && _isDialogShowing == false) {
_isDialogShowing = true;
Future.delayed(
Duration.zero,
() {
_showMyDialog(context);
},
);
}
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
children: [
.....
],
),
],
);
}
}
Do you know how to properly trigger the dialog opening event when a value changes in the ChangeNotifierProvider? Thank you very much!!!
What you can do is to add a listener to your ChangeNotifier:
late final myModel = Provider.of<MyModel?>(context);
bool _isDialogShowing = false;
#override
void initState() {
super.initState();
myModel.addListener(_showDialog);
}
#override
void dipose() {
myModel.removeListener(_showDialog);
super.dispose();
}
Future<void> _showDialog() async {
if (_isDialogShowing || !mounted) return;
// `hasToShowDialog` could be a getter and not a variable.
if (myModel?.hasToShowDialog != true) return;
_isDialogShowing = true;
await showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
content: const Text("Hello I am a dialog"),
),
);
_isDialogShowing = false;
}
I don't know what your MyModel looks like so it is hard to know what else you could fo. hasToShowDialog could be a getter and not a variable as you suggested in your question.

Flutter how to show AlertDialog on particular page only

I am building a chess clock in flutter. when i change clocks time duration in settings page i want to ask the user for confirmation using alert dialog. I want the alert dialog to only appear in homepage right now it appears immediately after I change duration in settings page.
I am using provider to manage state when I change duration I change a bool value to true. then in the home page I check if the bool value is true then run the alert dialog code. I tried putting dialog code in future delay.Zero but it did not work.
right now home page rebuilds immediatly after i change settings and alert dialog pops up.
my code:
// home page code
class HomePage extends StatefulWidget {
const HomePage({
Key? key,
}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with TickerProviderStateMixin, WidgetsBindingObserver {
// code for initstate and logic
late AnimationController _whiteControl;
late AnimationController _blackControl;
int whiteTime = 1;
int blackTime = 1;
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
_whiteControl = AnimationController(
vsync: this,
duration: Duration(seconds: whiteTime * 60),
);
_blackControl = AnimationController(
vsync: this,
duration: Duration(seconds: blackTime * 60),
);
#override
void dispose() {
WidgetsBinding.instance!.removeObserver(this);
_whiteControl.dispose();
_blackControl.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var gameState = Provider.of<GameState>(context, listen: false);
final ChoiceDialog resetChoice = ChoiceDialog(
buttonOkOnPressed: () {
_blackControl.reset();
_whiteControl.reset();
gameState.isgameChanged = false;
Navigator.pop(context);
},
buttonCancelOnPressed: () {
gameState.isgameChanged = false;
Navigator.pop(context);
},
);
if (gameState.isGameStarted && gameState.isgameChanged) {
Future.delayed(Duration.zero, () {
print('👉 Game Changed ');
resetChoice.show(context);
});
}
print('🏠 Rebuilding Home Page ---');
return Scaffold();
//reset of homepage code
Settings Page Code:
class SettingsPage extends StatelessWidget {
const SettingsPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Settings'),
),
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: gamesList.length,
itemBuilder: (context, index) {
return Consumer<GameState>(
builder: (context, _gameState, child) {
return GestureDetector(
onTap: () {
_gameState.changeGame(gamesList[index]);
},
child: Container(),
);
},
);
}),
)
],
),
);
}
}
Provider State Logic:
class Game {
final String name;
final int whiteTime;
final int blackTime;
Game({
required this.name,
required this.whiteTime,
required this.blackTime,
});
}
class GameState with ChangeNotifier {
bool turnWhite = false;
bool turnBlack = false;
bool isgameChanged = false;
bool isClocksFinished = false;
bool isPaused = false;
bool isGameStarted = false;
bool resetGame = false;
int white = 0;
int black = 0;
Game _currentGame = gamesList[0];
Game get currentGame => _currentGame;
bool get isWhiteTurn => turnWhite;
void resetGameClocks() {
resetGame = true;
notifyListeners();
}
void setTurnWhite() {
turnWhite = true;
turnBlack = false;
isGameStarted = true;
notifyListeners();
}
void setTurnBlack() {
turnWhite = false;
turnBlack = true;
isGameStarted = true;
notifyListeners();
}
void changeGame(Game game) {
_currentGame = game;
isgameChanged = true;
white = game.whiteTime;
black = game.blackTime;
notifyListeners();
}
}
project repository: https://github.dev/tonydavidx/chess-clock-app/tree/dev
I have checked your code you did a blunder in a very initial point/starting point of the Homepage screen kindly avoid this
never ever initialize or put your code before returning in build function of widgets if you really need some logic or code to be executed in the beginning then put it inside an initstate() or inside your provider constructor and handle it properly.
this is the root cause of the problem which you are facing.

Flutter - How to navigate to another page on an asynchronous value

I'm trying to show a splash screen on initial app startup until I have all of the data properly retrieved. As soon as it's there, I want to navigate to the main screen of the app.
Unfortunately, I can't find a good way to trigger a method that runs that kind of Navigation.
This is the code that I'm using to test this idea. Specifically, I want to run the command Navigator.pushNamed(context, 'home'); when the variable shouldProceed becomes true. Right now, the only way I can think to do it is to display a button that I need to press to trigger the navigation code:
import 'package:flutter/material.dart';
import 'package:catalogo/src/navigationPage.dart';
class RouteSplash extends StatefulWidget {
#override
_RouteSplashState createState() => _RouteSplashState();
}
class _RouteSplashState extends State<RouteSplash> {
ValueNotifier<bool> buttonTrigger;
bool shouldProceed = false;
_fetchPrefs() async { //this simulates the asynchronous function
await Future.delayed(Duration(
seconds:
1)); // dummy code showing the wait period while getting the preferences
setState(() {
shouldProceed = true; //got the prefs; ready to navigate to next page.
});
}
#override
void initState() {
super.initState();
_fetchPrefs(); // getting prefs etc.
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: shouldProceed
? RaisedButton(
onPressed: () {
print("entered Main");
Navigator.pushNamed(context, 'home'); // <----- I want this to be triggered by shouldProceed directly
},
child: Text("Continue"),
)
: CircularProgressIndicator(), //show splash screen here instead of progress indicator
),
);
}
}
So, in short, how can I trigger a function that runs the Navigation code when shouldProceed changes?
All you have to do is after you get the preferences, just navigate to the screen and have the build method just build a progress indicator.
Try this:
_fetchPrefs() async {
await Future.delayed(Duration(seconds: 1));
Navigator.of(context).pushNamed("home"); //stateful widgets give you context
}
Here's your new build method:
#override
Widget build(BuildContext context) {
return Center(child: CircularProgressIndicator());
}
I've made a DartPad to illustrate: https://dartpad.dartlang.org/431fcd9a1ea5748a82506f13be042e85
Create a widget which can be shown or hidden similar to my ProgressBar code. Show and hide based on a timer or when the data load from the api has completed.
ProgressBar progressBar = new ProgressBar();
progressBar.show(context);
Provider.of<Api>(context, listen: false)
.loadData(context, param)
.then((data) {
progressBar.hide();
Provider.of<Api>(context, listen: false).dataCache.login = loginValue;
Navigator.pop(context, true);
Navigator.pushNamed(context, "/home");
}
});
replace the ProgressBar code with your splash screen:
class ProgressBar {
OverlayEntry _progressOverlayEntry;
void show(BuildContext context){
_progressOverlayEntry = _createdProgressEntry(context);
Overlay.of(context).insert(_progressOverlayEntry);
}
void hide(){
if(_progressOverlayEntry != null){
_progressOverlayEntry.remove();
_progressOverlayEntry = null;
}
}
OverlayEntry _createdProgressEntry(BuildContext context) =>
OverlayEntry(
builder: (BuildContext context) =>
Stack(
children: <Widget>[
Container(
color: Colors.white.withOpacity(0.6),
),
Positioned(
top: screenHeight(context) / 2,
left: screenWidth(context) / 2,
child: CircularProgressIndicator(),
)
],
)
);
double screenHeight(BuildContext context) =>
MediaQuery.of(context).size.height;
double screenWidth(BuildContext context) =>
MediaQuery.of(context).size.width;
}

How to play a list of videos stored in your assets folder referenced as an array using video player plugin

import 'package:video_player/video_player.dart';
import 'package:flutter/material.dart';
void main() => runApp(VideoApp());
class VideoApp extends StatefulWidget {
#override
_VideoAppState createState() => _VideoAppState();
}
class _VideoAppState extends State<VideoApp> {
var vidlist = [
'assets/videos/1.mp4',
'assets/videos/2.m4',
'assets/videos/3.mp4'
];
VideoPlayerController _controller;
bool _isPlaying = false;
#override
void initState() {
super.initState();
_controller = VideoPlayerController.asset(vidlist)
..addListener(() {
final bool isPlaying = _controller.value.isPlaying;
if (isPlaying != _isPlaying) {
setState(() {
_isPlaying = isPlaying;
});
}
})
..initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Video Demo',
home: Scaffold(
body: Center(
child: _controller.value.initialized
? AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
)
: Container(),
),
floatingActionButton: FloatingActionButton(
onPressed: _controller.value.isPlaying
? _controller.pause
: _controller.play,
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
),
);
}
}
This is what I have done.I tried to make an array of Strings referencing the paths to the videos. However, the video player controller cannot accept list types. So then how does one play a list of videos stored locally? I also tried using a for loop in the controller but it doesn't work.
I am new to Flutter, so I am not sure that this is a clean solution, but works in my case.
According to your variables
var vdlist = [.......];
VideoPlayerController _controller;
//I am not using _isPlaying as variable
var currentIndexPosition = 0; // Position on the list
void initState(){
getFile();
}
void getFile(){
_controller = VideoPlayerController.asset(currentfile)
..addListener(() {
final bool isPlaying =_controller.value.position.inMilliseconds < _controller.value.duration.inMilliseconds;
// Tested with the following print
print("POSITION = " + _controller.value.position.inMilliseconds.toString() + " DURATION = " + _controller.value.duration.inMilliseconds.toString());
if (!isPlaying) {
setState(() {
nextVideo();
getFile();
});
}})
..initialize().then((_) {
setState(() {
_controller.play();
});
});
void nextVideo(){
if(currentIndexPosition < vdlist.length-1){
currentIndexPosition++;
}else{
currentIndexPosition = 0;
}
Hope could help, and also it could be a start to get an improved solution.