I have made a chess clock but the time only updates when you click one of the buttons and I want it so the time is constantly updating. I have the setState() function after the button press but no ware else. How could this be mad to updating every 10th of a second or so. Thanks. Here is my code:
import 'package:flutter/material.dart';
...
Widget build(context) {
return MaterialApp(
home: Scaffold(// this is what creates the page, everything should be inside this cuz everything is inside the page
appBar: AppBar(//what is displayed at the top WOW
title: Text('chess clock'),
),
body: Column(
children: [
Container(
margin: EdgeInsets.all(150.0),
child: RaisedButton(
onPressed: () {
setState((){
if (button1==1){
button1=2;
minus=minus2-minus;
fminus=fminus+minus;
print(minus);
divid=DateTime.now().microsecondsSinceEpoch/1000000-start/1000000;
divid.round();
divid=divid-fminus;
minus=DateTime.now().microsecondsSinceEpoch/1000000;
divid=60.0-divid;
}
});
},
child: Text(divid.round().toString()),
)
),
Container(
margin: EdgeInsets.all(150.0),
child: RaisedButton(
onPressed: () {
setState((){
if (button1==2){
button1=1;
minus2=minus-minus2;
fminus2=fminus2+minus2;
divid2=DateTime.now().microsecondsSinceEpoch/1000000-start/1000000;
divid2.round();
divid2=divid2-fminus2;
minus2=DateTime.now().microsecondsSinceEpoch/1000000;
divid2=60.0-divid2;
}
});
},
child: Text(divid2.round().toString()),
...
For a complete solution, you should put Kauli's code in your initState() method and tear it down in dispose(). It would look something like this.
I've made some small changes to fit the needs of the solution.
Edit: I added the surrounding StatefulWidget class and removed some cut and paste which was confusing. You can wrap the build method with your original Scaffold and other widgets. I left that out to make the answer a little clearer.
class ChessClock extends StatefulWidget {
#override
_ChessClockState createState() => _ChessClockState();
}
class _ChessClockState extends State<ChessClock> {
// Important to capture the timer object reference
Timer chessClockTimer;
Timer periodicSec() {
// How could this be made to updating every 10th of a second or so.
// Per op question
return Timer.periodic(Duration(milliseconds: 10), (_){
setState((){
if (button1==2){
button1=1;
minus2=minus-minus2;
fminus2=fminus2+minus2;
divid2=DateTime.now().microsecondsSinceEpoch/1000000-start/1000000;
divid2.round();
divid2=divid2-fminus2;
minus2=DateTime.now().microsecondsSinceEpoch/1000000;
divid2=60.0-divid2;
}
});
});
}
#override
void initState() {
super.initState();
chessClockTimer = periodicSec(); // Kauli's code
}
#override
void dispose() {
// Stop the timer
chessClockTimer?.cancel();
// Clean up the timer
chessClockTimer?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
// Your build method code
}
}
You can use the Timer.periodic to do this
void periodicSec() {
Timer.periodic(Duration(seconds: 1), (_){
setState((){
if (button1==2){
button1=1;
minus2=minus-minus2;
fminus2=fminus2+minus2;
divid2=DateTime.now().microsecondsSinceEpoch/1000000-start/1000000;
divid2.round();
divid2=divid2-fminus2;
minus2=DateTime.now().microsecondsSinceEpoch/1000000;
divid2=60.0-divid2;
}
});
});
}
Related
I am trying to display 3 images one after another by using GestureDetector and its onTap method but one image should be displayed without tapping. Supposing I have three images 1.png, 2.png, 3.png I would like to display the first image 1.png at the first place. Clicking on it will result in displaying the second image 2.png.
The second image 2.png should now be replaced after x seconds by displaying the image 3.png.
I am using a stateful widget that stores which image is currently shown. For the first image change the onTap will execute setState( () {screen = "2.png"} and the widget is rebuilt. However, I am pretty stuck now when the third image should be displayed after x seconds delay because there should not be any user interaction and I am not sure where to change it. I tried a lot of stuff especially editting the state from outside the widget but I did not succeed til now.
This is the code I am using:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Navigation Basics',
home: MyScreen(),
));
}
class MyScreen extends StatefulWidget {
#override
_MyScreenState createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
String currentScreen = 'assets/images/1.png';
#override
Widget build(BuildContext context) {
return GestureDetector(
child: FittedBox(
child: Container(
child: Image.asset(currentScreen),
),
fit: BoxFit.fill,
),
onTap: () {
if (currentScreen == 'assets/images/1.png') {
setState(() {
currentScreen = 'assets/images/2.png';
});
}
},
);
}
}
Displaying the second image worked without any issues but I have no idea what/where to code in order to display the third image.
Thanks for any help!
I'm assuming you don't want the second and third picture to respond to the gesture detection. Try this, please.
onTap: () {
if (currentScreen == 'assets/images/1.png') {
setState(() {
currentScreen = 'assets/images/2.png';
});
Future.delayed(const Duration(milliseconds: 3000), () {
setState(() {
currentScreen = 'assets/images/3.png';
});
});
} else {
return;
}
},
Original Answer
I'm using the Getx State Management on Flutter.
Simplifying as much as possible:
I build a GetxController to control my Page, and in this controller i have a StatefulWidget instance that evoque http requests.
class MyController extends GetxController {
Player player;
}
class Player extends StatefulWidget {
PlayerState state;
#override
PlayerState createState() {
state = PlayerState();
return state;
}
}
class PlayerState extends State<Player> {
void methodName async() {
futureRequest().then((data) {
// when the error ocurrs
setState(() {});
});
}
}
The problem occurs when the user closes the mobile page, triggering the controller's close method, before the end of the request.
That way, when setState is triggered, there is no more page instance and the error occurs.
I believe that the solution would be to interrupt all requests related to this GetxController and "delete" this instance of StatefulWidget at the moment the controller close method was called.
I don't know if this would be right, and if it's how to do it ..
==================================================================
Updated Answer
The main problem was that the async request in getDetails() method, return a response even after the controller is disposed, even using GetBuilder, and this response carried a url from a video that is started by the videoPlayerController (a video_player plugin instance).
So, the user is in another screen but keep listen to the video that is playing on background.
As a workaround and thinking in apply good practices to the code, i make a refactor to use only stateless widgets, following the GetX rules. I solved the problem, but i had to convert the Future's to Stream's
The binding is being created with Get.lazyPut() to perform dependencies injection:
class Binding implements Bindings {
Get.lazyPut<PlayerController>(() {
return PlayerController(videoRepository: VideoRepository(VideoProvider(Dio())));
});
}
This binding is linked to the page router, based on GetX documentation.
class AppPages {
static final routes = [
GetPage(name: Routes.MyRoute, page: () => MyPage(), binding: MyBinding()),
];
}
To prevent the controller to make actions even before it is disposed, i have to created a Stream and cancel it on controller dispose.
class MyController extends GetxController {
MyController({#required this.repository}) : assert(repository != null);
StreamSubscription<bool> stream;
// Instance of plugin video_player
VideoPlayerController videoPlayerController;
#override
void onClose() {
if (streamGetVideo != null) streamGetVideo.cancel();
super.onClose();
if (videoPlayerController != null) videoPlayerController?.dispose();
}
// This is the method called by the user on screen
void loadVideo() {
stream = getDetails().asStream().listen((bool response) {
// This code is canceled on onClose() method by the stream
if (response) update();
});
}
Future<bool> getDetails() async {
return await repository.getDetails().then((data) async {
videoPlayerController = VideoPlayerController.network(data);
initFuture = videoPlayerController.initialize();
await initFuture.whenComplete(() { return true; });
});
}
}
I think that Flutter/GetX should have a better way to do this, without these workarounds that i made. If anyone has a better approach or a hint, i'm open to suggestions.
One solution could be to wrap your setState with
if(mounted){
setState(() {});
}
GetBuilder + update()
In GetX using a GetBuilder with update() takes care of that lifecycle checking / handling so you don't have to do it.
Below is an example of a screen/route being closed prior to an HTTP call finishing & calling setState(), without an exception thrown.
(On the 2nd screen, click the Go Back! button fast to simulate an already disposed StatefulWidget.)
Below, an update() call is used to update the screen, instead of setState(), but they are the same in a GetBuilder. GetBuilder is (extends) a StatefulWidget.
GetBuilder adds listeners to the Controller you pass it, either through init: constructor arg or via the GetBuilder<Type> parameter if the Controller was initialized elsewhere/earlier.
That listener will be disposed if the StatefulWidget (i.e. GetBuilder) is disposed.
(See GetBuilder's dispose() function for some wizardry. While adding a listener, the returned value from adding that listener, is a function to dispose/unsubscribe from that listen. Pretty clever.)
So the GetBuilder/StatefulWidget will never have its update() / setState() called if that widget has been disposed because the listener for those calls has been disposed. So a slow returning HTTP call won't attempt to update/setState a widget that no longer exists in the widget tree.
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class HttpX extends GetxController {
String slowValue = 'loading...';
#override
void onInit() {
slowCall();
}
/// Simulate a slow, long running HTTP call
Future<void> slowCall() async {
slowValue = 'Slow call STARTED!';
print(slowValue);
update(); // update the screen to show started message
await Future.delayed(Duration(seconds: 5), () {
slowValue = 'Slow call FINISHED!';
print(slowValue);
update(); // won't call setState() if GetBuilder is disposed
});
}
}
class GetXDisposePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GetX Dispose'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('awaiting http call to finish'),
RaisedButton(
child: Text('Go Call Page'),
onPressed: () => Get.to(SlowCallPage()),
// using Get.to ↑ requires GetMaterialApp in place of MaterialApp in MyApp
)
],
),
),
);
}
}
class SlowCallPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GetX Dispose - Go Back!'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GetBuilder<HttpX>(
init: HttpX(), // fake slow http call starts on init
builder: (hx) => Text(hx.slowValue),
),
RaisedButton(
child: Text('Go Back!'),
onPressed: () => Get.back(),
),
],
),
),
);
}
}
I have a game screen in a stateful widget that includes a function that lets the player take a step back (undo the previous move). I would like to trigger this function when onUnityAdsFinish() runs from another widget.
Here's the stateful widget on game.dart where the game is played:
class GameScreen extends StatefulWidget {
#override
GameScreenState createState() => GameScreenState();
}
class GameScreenState extends State<GameScreen> with SingleTickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Container(
// game content here
),
MaterialButton(
child: Text("Tap Here to Watch Ad to Undo Move"),
onPressed: () => unityBloc.showUnityAd(),
// (side note: unityBloc is accessable because I have it declared as "final unityBloc = UnityBloc();" higher up the widget tree on app.dart.)
),
],
),
}
// This is the function I want to call after the ad is finished.
void undoMove() {
setState(() {
// code to undo move
});
}
}
When the button is pressed to watch the ad, it calls the showUnityAd() function in my UnityBloc class on unity_bloc.dart:
import 'package:unity_ads_flutter/unity_ads_flutter.dart';
class UnityBloc with UnityAdsListener {
...
showUnityAd() {
UnityAdsFlutter.show('rewardedVideo').whenComplete(() => null);
} // called when undo button is pressed on game.dart
#override
void onUnityAdsFinish(String placementId, FinishState result) {
print("Finished $placementId with $result");
// here is where I want to call the undoMove() function from game.dart
}
}
The ad displays perfectly and the print statement in onUnityAdsFinish prints to the console when the ad is finished, but I can't figure out how to call undoMove at that point.
I have spent all day watching tutorials and reading stackoverflows about callbacks and global keys and such, but I'm just still not getting it.
Can someone please help? How can I trigger the undoMove() function on game.dart when onUnityAdsFinish() fires on unity_bloc.dart? (Or is their another/better way to do this?)
Thank you!
You can have a variable that holds a function in the UnityBloc class and pass the undoMove method to the showUnityAd method to assign it to the variable and then you can call it in onUnityAdsFinish method.
So UnityBloc becomes this:
class UnityBloc with UnityAdsListener {
void Function() doOnUnityAdsFinish;
...
showUnityAd({#required unityAdsFinishFunction}) {
...
doOnUnityAdsFinish = unityAdsFinishFunction;
} // called when undo button is pressed on game.dart
#override
void onUnityAdsFinish(String placementId, FinishState result) {
...
doOnUnityAdsFinish?.call(); //Only calls if the function is not null
}
}
And you can it in the MaterialButton's onPressed like this:
MaterialButton(
child: Text("Tap Here to Watch Ad to Undo Move"),
onPressed: () => unityBloc.showUnityAd(unityAdsFinishFunction: undoMove ),
),
Simplest approach would be a global/static variable (callback) or a ValueNotifier/Stream.
Maybe, a cleaner approach, would be some package with dependency injection capabilities like (get, get_it).
class UnityBloc with UnityAdsListener {
/// pass a callback into the constructor.
/// Function<String,FinishState> onAdCompleted ;
/// UnityBloc([this.onAdCompleted]);
/// easier approach, not very "flexible".
static Function<String,FinishState> onAdCompleted ;
showUnityAd() {
UnityAdsFlutter.show('rewardedVideo').whenComplete(() => null);
} // called when undo button is pressed on game.dart
#override
void onUnityAdsFinish(String placementId, FinishState result) {
onAdCompleted?.call(placementId, result);
// here is where I want to call the undoMove() function from game.dart
}
}
class GameScreenState extends State<GameScreen> with SingleTickerProviderStateMixin {
#override
void initState(){
super.initState();
UnityBloc.onAdCompleted = _onAddCompleted;
}
#override
void dispose(){
super.dispose();
if( UnityBloc.onAdCompleted == _onAddCompleted) {
UnityBloc.onAdCompleted = null;
}
}
void _onAddCompleted(String placementId, FinishState result)
=> print("Finished $placementId with $result");
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Container(
// game content here
),
MaterialButton(
child: Text("Tap Here to Watch Ad to Undo Move"),
onPressed: () => unityBloc.showUnityAd(),
// (side note: unityBloc is accessable because I have it declared as "final unityBloc = UnityBloc();" higher up the widget tree on app.dart.)
),
],
),
}
// This is the function I want to call after the ad is finished.
void undoMove() {
setState(() {
// code to undo move
});
}
}
I'm Building An Flutter Application which requires image changes after a period of time. I thought using while loop with a sleep method inside may solve the problem. But It didn't, Image is only getting change after the loop ends. Application UI also gets froze.
So, I used the async Task which I can't control with a Button.
Desired Output: Image should be changed after every 10 seconds and the user can pause or resume method execution.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Test(
),
),
)
);
}}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
int imgnumber=1;
int varToCheckButtonPress = 0;
String BtnTxt = "START";
void inc(){
while(imgnumber<10)
{
print(imgnumber);
await Future.delayed(const Duration(seconds: 10));
setState(() {
imgnumber++;
});
}
}
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(flex: 1,
child: Container(
child: Image.asset('images/'+imgnumber.toString()+'.png'),
height: 500,
width:500,
color: Colors.green,
),
),
FlatButton(
child: Text(BtnTxt),
onPressed: (){
if (varToCheckButtonPress == 0) {
setState(() {
inc();
BtnTxt = 'PAUSE';
varToCheckButtonPress = 1;
});
} else if (varToCheckButtonPress == 1) {
setState(() {
BtnTxt = 'RESUME';
varToCheckButtonPress = 0;
});
}
},
)
],
);
}
}
I want the user to control the UI with a single button behave as START, PAUSE and RESUME.
Can we Use normal function To implement this functionality?
You should make use of Bloc pattern to manage your states, e.g: StreamBuilder, Providers, and make a timer to push new imageUrl to the sink and let the streamBuilder receive the latest imageUrl.
As for your button, all it controls is the timer. When u hit the play button, new imageUrl will keep pushing to the sink, while you press paused, simply stop the timer, and new image Url will not be pushing new imageUrl to the sink, and of course, reset the timer when you hit the stop button.
Here is a very detail Bloc pattern tutorial you can follow: Medium
The shortcut to achieve this is :
You can probably hold a function in async loop and call setState method on tap to change it's state.
For example :
call this function in desired location
while (_isPaused) {
await Future.delayed(Duration(milliseconds: 500));
}
and then call set state method from onTap, just like this
onTap:(){
setState((){
_isPaused? _isPaused=false: _isPaused=true;
});
}
I would like to find out in a stateful widget when the whole widget appears on screen or disappears, similar to iOS onViewWillAppear / disappear. Is that possible somehow? I didn't find anything related in the Flutter docs.
Thanks!
What your looking for is in the flutter_widgets package
Add the following to your pubspec.yaml
flutter_widgets: ^0.1.7+1
Inside this package is a widget called VisibilityDetector it requires a key, a child, and a function onVisibilityChanged
return VisibilityDetector(
key: Key("1"),
onVisibilityChanged: (visibility) {
//This will give you a range of values between 0 and 1,
//0 being not visible and 1 being fully visible.
print(visibility.visibleFraction)
}
child: Container(
width:double.infinity,
height: 300,
),
),
If you are thinking to perform something after widget build, You should use below code:
void initState() {
super.initState();
if (SchedulerBinding.instance.schedulerPhase ==
SchedulerPhase.persistentCallbacks) {
SchedulerBinding.instance.addPostFrameCallback((_) => onWidgetBuild());
}
/// appear
void onWidgetBuild() {
/// This block will be called onWidgetBuild
/// do your code here
}
/// disappear
#override
void dispose() {
super.dispose();
/// release whatever you have consume
}
Hope this will helps you.
I've had good luck using the focus_detector package. A FocusDetector widget will need to be placed within the default build() function and your existing UI code made it's child:
#override
Widget build(BuildContext context) =>
FocusDetector(
onFocusLost: () {
},
onFocusGained: () {
},
onVisibilityLost: () {
},
onVisibilityGained: () {
},
onForegroundLost: () {
},
onForegroundGained: () {
},
// Your previous build code replaces the Container() below
child: Container(),
);