How to: Check (or avoid checking) if BuildContext passed as Function argument is still mounted - flutter

Let's say I have a function for handling errors (used across an entire app) that takes a BuildContext as an argument and displays an alert to the user e.g.
void handleException(BuildContext context, String messageToDisplay) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Error'),
content: Text(messageToDisplay),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
);
},
);
}
The problem I'm trying to solve is having to perform a mounted check before every call to handleException e.g.
try {
...
} on CustomException catch (e) {
if (mounted) { // I want to handle this inside of `handleException`
return;
}
handleException(
context,
"A known error occurred",
);
} catch (e) {
if (mounted) {
return;
}
handleException(
context,
"An unknown error occurred",
);
}
It would be great if I could perform this check inside of handleException or if anyone can suggest a better method for achieving the same results that would also work!
Thanks in advance

Related

Null check operator used on a null value when calling a function from another class

I have a class called DataCollectionPage where I have the following function in the initState function:
void establishConnection (){
BluetoothConnection.toAddress(widget.server!.address).then((connection) {
Utils.showSnackBar2("Successfully connected to your device");
this.connection = connection;
setState(() {
isConnecting = false;
isDisconnecting = false;
});
this.connection!.input!.listen(_onDataReceived).onDone(() {
if (isDisconnecting) {
Utils.showSnackBar2("Device Disconnected!!");
} else {
Utils.showSnackBar2("Device Disconnected!!");
}
if (mounted) {
setState(() {});
}
});
}).catchError((error) {Utils.showSnackBar("Connection failed, please try again later");
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const BluetoothConnectionTask()),
);
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Error occurred while connecting'),
content: const Text(
"Connection failed. Please Try connecting to your device again"),
actions: <Widget>[
TextButton(
child: const Text("Close",style: TextStyle(color: Colors.black),),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
});
}
Then, in the same class, I have another function (startDataCollection) that looks like this:
void startDataCollection() async {
startFlag = startFlag.trim();
try {
List<int> list = startFlag.codeUnits;
Uint8List bytes = Uint8List.fromList(list);
connection!.output.add(bytes);
await connection!.output.allSent;
} catch (e) {
print(e);
}
}
At this point, there is no issue. However when I tried calling startDataCollection function from another screen (DataCollectionTimer Class), I got an error that
Null check operator used on a null value
which was referring to "connection" from the startDataCollection function.
Basically, I established a bluetooth connection with some hardware when I called the function establishConnection for the first time, and when I called it from the class DataCollectionTimer, it was still connected, but for some reason the connection var was empty when I called it from the DataCollectionPage class.
How can I use the function startDataCollection from another screen correctly?
If you need any more elaboration please let me know and thank you in advance.
Update:
I simple solved the problem by changing Bluetooth connection to global
Old:
BluetoothConnection? connection;
New:
static BluetoothConnection? connection;
Please ensure:
You're using the same connection object in all the pages.
you're awaiting BluetoothConnection.toAddress(widget.server!.address).then before doing anything with the connection value
The connection received from BluetoothConnection.toAddress(widget.server!.address).then((connection) is not null

Returning showDialog() as a Future does not trigger then() callback

I'm developing a flutter application and I am using showDialog() to show some errors that I've caught in catchError() method that I'm using for catching a potential exception for an api request.
i am returning showDialog() in catchError() as a Future result and im using then() after catchError() to know when showDialog() is poped to run some code as failure result of my request.
based on an online course that im watcing, showDialog() is resolved when we pop it from screens stack and i am using Navigator.of(context).pop(); to do that. but it's not working and it's not runing then() method.
I will be so grateful if anyone knows what's wrong.
this is my code and I hope it's clear enough.
Provider.of<Products>(context, listen: false)
.addProduct(Product(
id: edittedProduct.id,
title: edittedProduct.title,
description: edittedProduct.description,
price: edittedProduct.price,
imageUrl: edittedProduct.imageUrl,
isFavorite: edittedProduct.isFavorite))
.catchError((error) {
print("in second catchError method. error is: ${error.toString()}");
return showDialog<void>(
context: context,
builder: (ctx) => AlertDialog(
title: Text("An error occured!"),
content: Text("adding failed due to some issues."),
actions: [
TextButton(
onPressed: () {
Navigator.of(ctx).pop();
},
child: Text("Okay"))
],
),
);
}).then((_) {
print("in second then method");
setState(() {
_isLoading = false;
});
Navigator.of(context).pop();
});

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

Why using <Null> after showDialog?

Here I'm rendering an AlertDialog inside showDialog to display when error thrown from provider file. But that didn't work first time( Seems like stuck in catchError block, didn't execute future of catchError), Then I told to add <Null> after showDialog. That worked But How, What is changed, what it means?
Here is the code
if (_editedProduct.id == null) {
Provider.of<Products>(context, listen: false)
.addProduct(_editedProduct)
.catchError((error) {
return showDialog<Null>( //Here is that Null I didn't understand
context: context,
builder: (ctx) {
return AlertDialog(
title: Text('ERROR'),
content: Text('Error Occured'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('I GOT IT'),
)
],
);
});
}).then((_) {
print('THEN');
setState(() {
isLoading = false;
});
Navigator.of(context).pop();
});
} else {
Provider.of<Products>(context, listen: false)
.updateProduct(_editedProduct.id, _editedProduct);
Navigator.of(context).pop();
}
}
Nb: isLoading true shows a circluarProgressIndicator()
From the Official Docs
Returns a Future that resolves to the value (if any) that was passed to Navigator.pop when the dialog was closed.
The place where you sent Null indicates the type of data the Future will return.
By using Null you are indicating that the Future won't return any data, this tell your program not to wait for the possible value that might be returned.
Suppose in your dialog user has to pick 2 numbers and you want the picked number to be returned to the place where you called the showDialog() function then you'll use int instead of Null.
Something like
showDialog<int>(...)

Flutter exception: 'Context is not a subtype of BuildContext' error using Navigator

I am working on a flutter app and I am running into the following error: "The argument type 'Context' can't be assigned to the parameter type 'BuildContext'." However, when I try to pass in context to my functions as it says to do on the internet, I am unable to get it to work. Can someone please help me with this? The app is a camera app, and I know the video is successfully being recorded. here is the code:
This is the stop button that is pressed.
IconButton(
icon: const Icon(Icons.stop),
color: Colors.red,
onPressed: () {controller != null &&
controller.value.isInitialized &&
controller.value.isRecordingVideo
? onStopButtonPressed. //this is the function that is being called
: null;},
) //icons.stop
This is where the app isn't working with my Navigator.push call. It works fine when I take it out.
void onStopButtonPressed() {
stopVideoRecording().then((_) {
if (mounted) setState(() {});
print('Video recorded to: $videoPath');
print('Navigator is hit');
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PreviewImageScreen(videoPath: videoPath),
), //MaterialpageRoute
); //Navigator
});
}
And here is my stopVideoRecording function
Future<void> stopVideoRecording() async {
if (!controller.value.isRecordingVideo) {
return null;
}
try {
await controller.stopVideoRecording();
} on CameraException catch (e) {
_showCameraException(e);
return null;
}
//await _startVideoPlayer();
}
Thanks!
change it to
Navigator.push(
this.context, //add this so it uses the context of the class
MaterialPageRoute(
builder: (context) => PreviewImageScreen(videoPath: videoPath),
), //MaterialpageRoute
); //Navigator
Maybe you're importing a library or something that has a class named context and is interfering with the name context