How to find out in Flutter when a Widget appears / disappears? - flutter

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(),
);

Related

Flutter Ads Causing Delay

I recently ported my flutter app from admob_flutter to google_mobile_ads. I've successfully implemented banner ads according to the tutorial here. The ads appear as expected however they cause the rest of the app to wait for them to load, as if something is being awaited. Here is a visual of the problem:
no banner (expected behaviour)
with banner (buggy)
The delay is even worse when I want to load pages with more than a couple widgets. At first I thought this was because the app is being run on an emulator, however the phenomenon persists on physical phones with updated OSes as well. I found this issue and thought it might be related however using the suggested package didn't work, and again the problem occurs on updated versions of android as well ( > 10). Here is my Ad class:
class AnchoredAdaptiveBanner extends StatefulWidget {
#override
_AnchoredAdaptiveBannerState createState() => _AnchoredAdaptiveBannerState();
}
class _AnchoredAdaptiveBannerState extends State<AnchoredAdaptiveBanner> {
BannerAd _anchoredAdaptiveAd;
bool _isLoaded = false;
#override
void didChangeDependencies() {
super.didChangeDependencies();
_loadAd();
}
Future<void> _loadAd() async {
// This was originally dynamic with an await, I changed it to static values for a sanity check, again same problem.
final AnchoredAdaptiveBannerAdSize size = AnchoredAdaptiveBannerAdSize(Orientation.portrait, width: 300, height: 50);
_anchoredAdaptiveAd = BannerAd(
adUnitId: AdManager.bannerAdUnitId,
size: size,
request: AdRequest(),
listener: BannerAdListener(
onAdLoaded: (Ad ad) {
setState(() {
_anchoredAdaptiveAd = ad as BannerAd;
_isLoaded = true;
});
},
onAdFailedToLoad: (Ad ad, LoadAdError error) {
ad.dispose();
},
),
);
return _anchoredAdaptiveAd.load();
}
#override
Widget build(BuildContext context) {
if (_anchoredAdaptiveAd != null && _isLoaded)
return Container(
width: _anchoredAdaptiveAd.size.width.toDouble(),
height: _anchoredAdaptiveAd.size.height.toDouble(),
child: AdWidget(ad: _anchoredAdaptiveAd),
);
else
return Container();
}
#override
void dispose() {
super.dispose();
_anchoredAdaptiveAd?.dispose();
}
}
My usage in my home widget for example:
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Container(
child: SafeArea(
child: Scaffold(
body: Builder(...),
floatingActionButton: Container(...),
bottomNavigationBar: AnchoredAdaptiveBanner()
),
),
);
}
}
As you can see nothing is being top-level awaited. I am currently on version 2.0.1 of google_mobile_ads.
Any idea what is causing this? Any help is appreciated!
The solution: Return SizedBox.shrink()
Why does this work?
Until the ad is loaded you're returning an empty container from your build method. If you don't pass height or width to the container, it expands as much as possible. In this case bottomNavigationBars can expand infinitely. This causes the rest of the widgets to be pushed out of screen and the page seems frozen until the ad loads. Using SizedBox.shrink() will have the container be 0x0 until it loads.

How to make a timer constantly display

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;
}
});
});
}

Flutter - rebuild GestureDetector widget without user interaction

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;
}
},

How to trigger a function in a stateful widget from another class (when onUnityAdsFinish)?

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
});
}
}

Is there any callback to tell me when "build" function is done in Flutter?

I have a listView in my screen. I have attached a controller to it. I am able to call my Endpoint, receive response, parse it and insert in row. ListView supposed to Scroll automatically. It does, but not in perfect way. I am always an item behind. This is my code:
#override
Widget build(BuildContext context) {
// Scroll to the most recent item
if (equationList.length > 0) {
_toEnd();
}
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: EquList(equationList, _scrollController),
floatingActionButton: new FloatingActionButton(
onPressed: onFabClick,
tooltip: 'Fetch Post',
child: new Icon(isLoading ? Icons.pause : Icons.play_arrow),
),
);
}
void _toEnd() {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 250),
curve: Curves.ease,
);
}
The problem is, I am calling _toEnd() function before the last item inserts in to the list. So, I am looking for a callback (if there is any) that tells me build() is done. Then I call my _toEnd() function.
What is the best practice in this case?
General solution
Just to clear things up, I did not expect this question to attract so much attention. Hence, I only answered for this very specific case.
As explained in another answer WidgetsBinding offers a way to add a one time post frame callback.
WidgetsBinding.instance!.addPostFrameCallback((_) {
// executes after build
})
As this callback will only be called a single time, you will want to add it every time you build:
#override
Widget build(BuildContext context) {
WidgetsBinding.instance!.addPostFrameCallback((_) => afterBuild);
return Container(); // widget tree
}
void afterBuild() {
// executes after build is done
}
Specific (async)
Elaborating on Günter's comment:
#override
Widget build(BuildContext context) {
executeAfterBuild();
return Container();
}
Future<void> executeAfterBuild() async {
// this code will get executed after the build method
// because of the way async functions are scheduled
}
There is a nice example illustrating that effect here.
Extensive information about scheduling in Dart can be found here.
The async way from #creativecreatorormaybenot is enough to answer the question for most situations.
But if you want to setState() or do something that will change widgets in the tree right after building the widget, you cannot use the async way. Because the callback will be fired during the build process of the widget tree. It will throw an exception:
Dart Error: Unhandled exception:
E/flutter (25259): setState() or markNeedsBuild() called during build.
For this situation, you can register a post frame callback to modify the widget:
#override
Widget build(BuildContext context) {
WidgetsBinding.instance
.addPostFrameCallback((_) => executeAfterWholeBuildProcess(context));
If you don't want to use WidgetsBinding:
Use Future or Timer
#override
Widget build(BuildContext context) {
Timer(_runsAfterBuild); // <-- Just add this line
return Container();
}
Future<void> _runsAfterBuild() async {
// This code runs after build ...
}
Add a dummy wait (fixes #creativecreatureormaybenot problem)
#override
Widget build(BuildContext context) {
_runsAfterBuild();
return Container();
}
Future<void> _runsAfterBuild() async {
await Future.delayed(Duration.zero); // <-- Add a 0 dummy waiting time
// This code runs after build ...
}