Don't use 'BuildContext' across async gaps around showDialog in Flutter - flutter

I'm getting this warning in my flutter project around every showDialog widget inside InAppWebView Widget. How to solve this ?
return InAppWebView(
...
onLongPressHitTestResult: (controller, hitTestResult) async {
if (LongPressAlertDialog.hitTestResultSupported
.contains(hitTestResult.type)) {
var requestFocusNodeHrefResult =
await _webViewController?.requestFocusNodeHref();
if (requestFocusNodeHrefResult != null) {
//Don't use 'BuildContext' across async gaps
showDialog(
context: context,
builder: (context) {
return LongPressAlertDialog(
webViewModel: widget.webViewModel,
hitTestResult: hitTestResult,
requestFocusNodeHrefResult: requestFocusNodeHrefResult,
);
},
);
}
}
},
}

try this:
if (requestFocusNodeHrefResult != null && context.mounted) { // <=== add this
showDialog(
context: context,
builder: (context) {
return LongPressAlertDialog(
webViewModel: widget.webViewModel,
hitTestResult: hitTestResult,
requestFocusNodeHrefResult: requestFocusNodeHrefResult,
);
},
);
}

Related

how to open a url with url_launcher and onTap of InkWell only once?

if i touch it once then it prints out 1 line 123
if i touch it many times then it prints out many line 123
So how when I touch it many times then it prints out 1 line 123 or exiting _launchUrl
When I touch it many times then I also had to go back to that number of times to get rid of _launchUrl
My code here
Hope to get everyone's help!
final Uri _url = Uri.parse('https://flutter.dev');
....
Future<void> _launchUrl() async {
if (!await launchUrl(_url)) {
throw 'Could not launch $_url';
}
}
...
InkWell(
onTap: () {
_launchUrl;
print('123');
}
)
I tried using the wait function but still not getting the desired result
Create a variable buttonPressed and set it default to false
bool buttonPressed = false;
Inside your onTap you can check if the buttonPressed is set to false. If it is set to false you can set it to true and run your _launchUrl function. After you called _launchUrl you can set it back to false to run it again.
if(buttonPressed == false){
buttonPressed = true;
await _launchUrl();
buttonPressed = false;
}
Also mark your onTap as async to use the await keyword
onTap: () async {
I fixed it by following way
Uri uri = Uri.parse('https://flutter.dev');
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LoadURL(uri),
)
);
// Navigate to here
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class LoadURL extends StatelessWidget {
late final Uri uri;
LoadURL(this.uri);
Future<void> _launchUrl(Uri uri) async {
if (!await launchUrl(uri)) {
throw 'Could not launch $uri';
}
}
#override
Widget build(BuildContext context) {
return Center(
child: FutureBuilder(
future: _launchUrl(uri),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Navigator.pop(context);
}
return CircularProgressIndicator();;
},
),
);
}
}

How distinguish inner context?

I wrote the code like data: (data){Navigator.of(context).pop();},//here to pop showDialog but actually other Navigator.of(context) was popped and figured out that I should distinguish inner context in builder: (context) { //here2 from outer context but could not reach how to do that. Would somebody please give me some advise to solve this ?
final authC = ref.watch(authFutureProvider);
authC.when(
data: (data){Navigator.of(context).pop();},//here
error: (error, stackTrace) {},
loading: () {
final loading =
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) { //here2
return const Center(
child: CircularProgressIndicator(),
);
}
);
});
},
);
Well, you can easily define different BuildContexts by giving them different names, like this:
#override
Widget build(BuildContext parentContext) {
final authC = ref.watch(authFutureProvider);
authC.when(
data: (data){Navigator.of(parentContext).pop();},//here
error: (error, stackTrace) {},
loading: () {
final loading =
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog(
barrierDismissible: false,
context: parentContext,
builder: (context) { //here2
return const Center(
child: CircularProgressIndicator(),
);
}
);
});
},
);
return Widget();
}
However, that won't change the fact that the context I've named "parentContext" is the parent context! This is the context sent TO the Dialog, not the context of the Dialog itself.
If you want to pop whichever context is on top at the moment, you can define a GlobalKey variable like this:
//global.dart
import 'package:flutter/material.dart';
class GlobalVariable {
static final GlobalKey<NavigatorState> navState = GlobalKey<NavigatorState>();
}
Then, you can pop the current context like this, from anywhere:
import 'global.dart';
void func(){
Navigator.pop(GlobalVariable.navState.currentContext!);
}
That should pop the Dialog IF the Dialog is currently on top...
But of course, if some other BuildContext is on top, then that will be popped, instead. 😏 So make sure you write the proper conditions for that.
Edit:
You could also use popUntil(). If this happens to be the first screen, you can just write:
#override
Widget build(BuildContext parentContext) {
final authC = ref.watch(authFutureProvider);
authC.when(
data: (data){Navigator.popUntil(context, (route) => route.isFirst);},//here
error: (error, stackTrace) {},
loading: () {
final loading =
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog(
barrierDismissible: false,
context: parentContext,
builder: (context) { //here2
return const Center(
child: CircularProgressIndicator(),
);
}
);
});
},
);
return Widget();
}
If not, then it's a bit more complicated to make it pop back to this particular screen... But you could use routeSettings, for example.
I strongly not recommend WidgetsBinding.instance.addPostFrameCallback to show loading because it takes time to render, so your app will load a loading function. It will make End-user can do multiple clicks on the button because the loading has not rendered yet.
this package is verylight and easy to use, the dismiss and show even different from regular widget, so you will not accidentally close your screen because wrong logic or back-end handler failure.
class LoadingExample {
showLoadingExample(){
EasyLoading.instance
..displayDuration = const Duration(milliseconds: 2000)
..indicatorType = EasyLoadingIndicatorType.fadingCircle
..loadingStyle = EasyLoadingStyle.dark
..indicatorSize = 45.0
..radius = 10.0
..progressColor = Colors.yellow
..backgroundColor = Colors.green
..indicatorColor = Colors.yellow
..textColor = Colors.yellow
..maskColor = Colors.blue.withOpacity(0.5)
..userInteractions = true
..dismissOnTap = false
..customAnimation = CustomAnimation();
}
dismissExampleLoading(){
EasyLoading.dismiss();
}
}
to call it simply do: *I recommend OOP so you can modify it in 1 place when you need.
LoadingExample().showLoadingExample();
or
LoadingExample().dismissExampleLoading();
even you accidentally call show or dissmiss multiple time, the loading will not stack, and it will just recall or reset the animation. So your app will not crash
Flutter_easyloading

How to conditionally show Get.bottomSheet when starting app

I am trying to popup Get.bottomSheet when starting app.
I did it like bottom code.
#override
void initState() {
_popUpBottomBanner();
}
void _popUpBottomBanner() {
WidgetsBinding.instance.addPostFrameCallback(
(_) {
Get.bottomSheet(
...
);
},
);
}
But I want to judge show or hide bottom sheet by API result.
So I changed Code like below.
void _popUpBottomBanner() {
WidgetsBinding.instance.addPostFrameCallback(
(_) {
FutureBuilder<CustomListVO>(
future: Api().getBanners(...),
builder: (BuildContext _, AsyncSnapshot<CustomListVO> snapshot) {
if (snapshot.hasError) return;
if (!snapshot.hasData) return;
if (snapshot.data?.list?.isNotEmpty == true) {
Get.bottomSheet(
...
);
}
return;
},
);
},
);
}
Despite confirming API result arriving properly,
bottomSheet isn't showing.
What is the problem with this code?
Thanks for reading :D
====== resolved like below ========
#override
Widget build(BuildContext context) {
return FutureBuilder<CustomListVO>(
future: Api().getBanners(...),
builder: (BuildContext _, AsyncSnapshot<CustomListVO> snapshot) {
if (snapshot.data?.list?.isNotEmpty == true) {
Get.bottomSheet(
...
);
}
return HomePage();
}
);
}
you need to show bottom sheet that is not required to use Futurebuilder so you can use it like this :
var result = await Api().getBanners(...);
if (result.data?.list?.isNotEmpty == true) {
Get.bottomSheet(
...
);
}

How to pick color from image in Flutter?

I am trying to get color from image in Flutter.
I'm trying this package but it's not working.
You can do this with the paletter_generator package
var paletteGenerator;
var itemBackgroundColor;
Future<PaletteGenerator> _updatePaletteGenerator() async {
paletteGenerator = await PaletteGenerator.fromImageProvider(
Image.asset(product.image).image,
);
return paletteGenerator;
}
//Then return a Future Builder
return FutureBuilder<PaletteGenerator>(
future: _updatePaletteGenerator(), // async work
builder:
(BuildContext context, AsyncSnapshot<PaletteGenerator> snapshot) {
itemBackgroundColor = snapshot.data.dominantColor.color;
},
);

Correct way to work with future objects within multiple Future builder in Flutter

For example I have a late future object with news list within a StatefulWidget
late Future<List<News>> loadedList;
I need to use this loadedNewsList outside of Future.builder in some methods like that (this code will fail as far loadedList is not inited yet)
void markLoadedAsRead() async {
String _time = DateTime.now().toString();
for (var el in loadedList!) {
if(el.readAt == null){
el.readAt = _time;
final _n = await DatabaseHelper.instance.findNews(el.uuid);
if(_n == null) {
await DatabaseHelper.instance.insertNews(el);
} else {
await DatabaseHelper.instance.updateNews(el);
}
}
}
setState((){});
}
And also I want some inerface Future.builders to be aware loadedList is loaded like that. Appbar example that is shown only when loadedList is completed.
actions: <Widget>[
FutureBuilder<List<News>>(
future: loadedList, // a Future<String> or null
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) developer.log(snapshot.error.toString());
if (!snapshot.hasData) return const SizedBox.shrink();
return IconButton(
icon: const Icon(Icons.filter_list_alt),
onPressed: () async {
developer.log('FEED smooth scroll news to top before filter click');
_scrollToTop();
// disposeAd();
await Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => const FilterPage()));
// getBanner();
developer.log('FEED call news after filter page visit');
// await fetchNews(false);
// setState(() {});
},
);
}
)
]
So, what is the correct way to handle that in Flutter?