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.
Related
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
i have two screens A and B.In screen A iam calling a function periodically(i.e every 5 seconds).At the time of navigating to screen B i need to stop the function calling and when its back to screen A, the function call should be resumed.
Is there any way to do it?
Navigator doesn't expose the current route.
What you can do instead is use Navigator.popUntil(callback) as popUtil pass to the callback the current Route, which includes it's name and stuff.
final newRouteName = "/NewRoute";
bool isNewRouteSameAsCurrent = false;
Navigator.popUntil(context, (route) {
if (route.settings.name == newRouteName) {
isNewRouteSameAsCurrent = true;
}
return true;
});
if (!isNewRouteSameAsCurrent) {
Navigator.pushNamed(context, newRouteName);
}
Use This bool to check current screen and toggle your function .
From what i see is you can use the Timer class in the widget and manupulate based on your needs, I have created a sample example for you.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: PageOne()
);
}
}
class PageOne extends StatefulWidget {
const PageOne({Key? key}) : super(key: key);
#override
_PageOneState createState() => _PageOneState();
}
class _PageOneState extends State<PageOne> {
Timer? timer;
#override
void initState() {
// TODO: implement initState
super.initState();
timer = Timer.periodic(const Duration(seconds: 2), (timer) {
printMethod("init");
});
}
printMethod(String type){
print("This is the $type print statement ");
}
#override
void dispose() {
super.dispose();
timer?.cancel();
print("First Page timer cancelled");
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TextButton(onPressed: () async {
timer?.cancel();
var result = await Navigator.push(context, MaterialPageRoute(builder: (context)=> const PageTwo() ));
if(result)
{
timer = Timer.periodic(const Duration(seconds: 2), (timer) {
printMethod("init");
});
}
}, child: const Text("go to next page"),),
),
);
}
}
class PageTwo extends StatefulWidget {
const PageTwo({Key? key}) : super(key: key);
#override
_PageTwoState createState() => _PageTwoState();
}
class _PageTwoState extends State<PageTwo> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Page two"),),
body: Center(
child: TextButton(onPressed: () async {
Navigator.of(context).pop(true);
}, child: const Text("go to prev page"),),
),
);
}
}
Let me know if it works
You can simply use bool to handle this case as follows :
class _ScreenAState extends State<ScreenA> {
bool runPeriodicFun = true;
void periodicFun() {
if (runPeriodicFun) {
//write your periodic logic
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: InkWell(
onTap: () {
setState(() {
runPeriodicFun = false;
});
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => ScreenB()))
.then((value) {
setState(() {
runPeriodicFun = true;
});
periodicFun();
});
},
child: Container()),
);
}
}
when you go to Screen B then runPeriodicFun set to false and coming back to Screen A runPeriodicFun set to true. So it will use periodicFun block only when runPeriodicFun is true.
You will receive callback in then block in the Navigator.push method.
what i did is that, in timer function i checked the current page is at top of stack by
ModalRoute.of(context)!.isCurrent
what it did is that it will check the page by context which we pass if the page is at top it will continue the timer, if it navigates to another page then the above code will return false.
In false, i just stop the timer.
if(ModalRoute.of(context)!.isCurrent){
//timer running
}else{
_timer?.cancel();
}
so if the page is same timer will perform otherwise it will stop the timer.
Now for resuming the timer, under the build in screen where you called i just called the function from where timer get activated.
Widget build(BuildContext context) {
//call timer function
return SafeArea()}
i think this might solve the problem. if anyone identify any problem please comment it.
In my Flutter app, I wish to pause an animation if a new page is pushed to the navigation stack.
Question: From a widgets perspective, is there any way to know if the widget is at the top of the navigation stack?
Example:
while (isTopOfNavigationStack) {
// Do the animation
}
Edit 1: More details
Im using the library widget_marquee for creating a horizontal ticker. This library works great until a new page is pushed to the navigation stack. When this happens, the while-loop becomes infinite and the application freezes.
library widget_marquee;
import 'dart:developer';
import 'package:flutter/material.dart';
/// Rotates the [child] widget indefinitely along the horizontal axis if the
/// content extends pass the edge of the render area.
///
/// [delayDuration] - One time delay to wait before starting the text rotation
/// [gap] - Spacing to add between widget end and start
/// [loopDuration] - Time for one full rotation of the child
/// [onLoopFinish] - Function to run upon finishing each loop
/// [onScrollingTap]
/// [pixelsPerSecond] - Alternate to loop duration
class Marquee extends StatelessWidget {
const Marquee({
Key? key,
required this.child,
this.delayDuration = const Duration(milliseconds: 1500),
this.gap = 50,
this.loopDuration = const Duration(milliseconds: 8000),
this.onLoopFinish = _onLoopFinish,
this.onScrollingTap = _onScrollingTap,
this.pixelsPerSecond = 0,
}) : super(key: key);
final Widget child;
final Duration delayDuration;
final double gap;
final Duration loopDuration;
final Future<void> Function() onLoopFinish;
final Future<void> Function() onScrollingTap;
final int pixelsPerSecond;
#override
Widget build(BuildContext context) {
return _Marquee(
key: UniqueKey(),
child: child,
delay: delayDuration,
gap: gap,
loopDuration: loopDuration,
onLoopFinish: onLoopFinish,
onScrollingTap: onScrollingTap,
pps: pixelsPerSecond,
);
}
}
class _Marquee extends StatefulWidget {
const _Marquee({
required Key key,
required this.child,
required this.delay,
required this.gap,
required this.loopDuration,
required this.onLoopFinish,
required this.onScrollingTap,
required this.pps,
}) : super(key: key);
final Widget child;
final Duration delay;
final double gap;
final Duration loopDuration;
final Future<void> Function() onLoopFinish;
final Future<void> Function() onScrollingTap;
final int pps;
#override
_MarqueeState createState() => _MarqueeState();
}
class _MarqueeState extends State<_Marquee> with TickerProviderStateMixin {
late double contentArea;
bool isScrolling = false;
late ScrollController scrollController;
List<Widget> widgets = <Widget>[];
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
scrollController = ScrollController(
initialScrollOffset: 0.0,
keepScrollOffset: false,
);
widgets = <Widget>[widget.child];
// Initialize the scroll controller
WidgetsBinding.instance?.addPostFrameCallback(scroll);
super.didChangeDependencies();
}
void scroll(_) async {
if (scrollController.position.maxScrollExtent > 0) {
late Duration duration;
final double initMax = scrollController.position.maxScrollExtent;
// Add a sized box and duplicate widget to the row
setState(() {
widgets.add(SizedBox(width: widget.gap));
widgets.add(widget.child);
});
await Future<dynamic>.delayed(widget.delay);
try {
setState(() {
isScrolling = true;
});
while (scrollController.hasClients) {
// Calculate the position where the duplicate widget lines up with the original
final scrollExtent =
(initMax * 2) - (initMax - contentArea) + widget.gap;
// Set the duration of the animation
if (widget.pps <= 0) {
duration = widget.loopDuration;
} else {
duration = Duration(
// Calculate the duration based on the pixels per second
milliseconds: ((scrollExtent / widget.pps) * 1000).toInt(),
);
}
await scrollController.animateTo(
scrollExtent,
duration: duration,
curve: Curves.linear,
);
// Jump to the beginning of the view to imitate loop
scrollController.jumpTo(0);
await widget.onLoopFinish();
}
} catch (e) {
log('Marquee element has been disposed');
}
}
}
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
contentArea = constraints.maxWidth;
// Thanks to how widgets work, the gesture detector is only triggered
// if there's nothing clickable in the child
return GestureDetector(
onTap: () async {
if (isScrolling) {
await widget.onScrollingTap();
}
},
child: Container(
alignment: Alignment.center,
child: SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Row(
children: widgets,
),
scrollDirection: Axis.horizontal,
controller: scrollController,
),
),
);
},
);
}
#override
void dispose() {
scrollController.dispose();
super.dispose();
}
}
Future<void> _onLoopFinish() async {}
Future<void> _onScrollingTap() async {
log('Marquee onScrollingTap function triggered');
}
You can get to know if the current route is the top route in navigation with ModalRoute.of(context)?.isCurrent. In your example should be something like
final _isTopOfNavigationStack = ModalRoute.of(context)?.isCurrent ?? false;
while (_isTopOfNavigationStack) {
// Do the animation
}
Check this answer if you need more details.
Trying to Navigate back and forth between SignIn and ForgotPassword Page using Navigation.push().
But the Forgot Password Page Keep Losing its state (the countdown timer here).
Home Page for App. Home
When I enter email it shows a message and a counter (goes from 30 to 0). Page State
I go back to SignIn page and come back the timer and message are gone. State Gone
How to save a state for Navigation.push() page?
Forgot.dart
class ForgotPassword extends StatefulWidget {
const ForgotPassword({Key? key}) : super(key: key);
#override
_ForgotPasswordState createState() => _ForgotPasswordState();
}
class _ForgotPasswordState extends State<ForgotPassword> {
TextEditingController emailC = TextEditingController();
final formkey = GlobalKey<FormState>();
AuthMethods authMethods = AuthMethods();
bool sent = false;
Timer? _timer;
int _start = 0;
void startTimer() async {
if (formkey.currentState!.validate() && _start == 0) {
_start = 30;
const oneSec = const Duration(seconds: 1);
_timer = new Timer.periodic(
oneSec,
(Timer timer) {
if (_start == 0) {
setState(() {
timer.cancel();
});
} else {
setState(() {
_start--;
});
}
},
);
await authMethods.resetPassword(emailC.text);
}
}
#override
void dispose() {
_timer!.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(... rest of code
body: PageView(
controller: _pageController,
physics: NeverScrollableScrollPhysics(),
children: [
FirstScreen(),
SecondScreen(),
],
),
replace Navigator.push()/pop() with _pageController.goToPage(0 or 1)
In a Flutter Desktop app, I want to know if, when a user clicks on a button with the mouse, they were also holding down a key (like Shift, Control, Alt etc).
How can this be done?
EDIT
My initial question wasn't clear enough.
I have a dynamic list of checkboxes and I want to use SHIFT+click to select everything between the last selected one and the one that was selected with SHIFT down.
I have looked at FocusNode but that seems to only work for 1 element.
This can be done with a FocusNode.
You'll need a stateful widget where you can use initialize the node. You need to attach the node and define the callback that is called on keyboard presses. Then you can request focus from the node with requestFocus so that the node receives the keyboard events.
You'll also need to call _nodeAttachment.reparent(); in your build method. You should also dispose the node in dispose.
The example below prints true or false for whether the shift key is pressed when the button is pressed. This can be easily expanded to other keys like control and alt with the isControlPressed and isAltPressed properties.
Full example:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late final FocusNode focus;
late final FocusAttachment _nodeAttachment;
bool isShiftPressed = false;
#override
void initState() {
super.initState();
focus = FocusNode(debugLabel: 'Button');
_nodeAttachment = focus.attach(context, onKey: (node, event) {
isShiftPressed = event.isShiftPressed;
});
focus.requestFocus();
}
#override
void dispose() {
focus.dispose();
super.dispose();
}
Widget build(BuildContext context) {
_nodeAttachment.reparent();
return TextButton(
onPressed: () {
print(isShiftPressed);
},
child: Text('Test'),
);
}
}
You can still use this solution for your more specific problem. Wrap the above example around your list of checkboxes. You can do a bit of simple logic to get your intended behavior. If what I have here is not exact, you should be able to easily modify it to your needs. This proves that you can use this method for your need, however, even if some details in the logic are not exact:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late final FocusNode focus;
late final FocusAttachment _nodeAttachment;
bool isShiftPressed = false;
List<bool> checkboxStates = List.filled(5, false);
int lastClicked = -1;
#override
void initState() {
super.initState();
focus = FocusNode(debugLabel: 'Button');
_nodeAttachment = focus.attach(context, onKey: (node, event) {
isShiftPressed = event.isShiftPressed;
});
focus.requestFocus();
}
#override
void dispose() {
focus.dispose();
super.dispose();
}
Widget build(BuildContext context) {
_nodeAttachment.reparent();
return Column(
children: List.generate(checkboxStates.length, (index) => Checkbox(
value: checkboxStates[index],
onChanged: (val) {
if(val == null) {
return;
}
setState(() {
if(isShiftPressed && val) {
if(lastClicked >= 0) {
bool loopForward = lastClicked < index;
if(loopForward) {
for(int x = lastClicked; x < index; x++) {
checkboxStates[x] = true;
}
}
else {
for(int x = lastClicked; x > index; x--) {
checkboxStates[x] = true;
}
}
}
}
checkboxStates[index] = val;
});
if(val) {
lastClicked = index;
}
else {
lastClicked = -1;
}
print('Checkbox $index: $isShiftPressed');
}
)),
);
}
}