Flutter: Consider canceling any active work during "dispose" when internet changes its state - flutter

I am getting the following message when internet goes off.
E/flutter (26162): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: This widget has been unmounted, so the State no longer has a context (and should be considered defunct).
E/flutter (26162): Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active.
It is showing the message from this section of my code.
#override
void initState() {
super.initState();
try {
InternetAddress.lookup('google.com').then((result) {
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
// internet conn available
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) =>
(Constants.prefsMobile.getString("mobile") == null
? Login()
// : SignupPayoutPassword(signupdata: [])),
: Home(signindata: signinData)),
));
} else {
// no conn
_showdialog();
}
}).catchError((error) {
// no conn
_showdialog();
});
} on SocketException catch (_) {
// no internet
_showdialog();
}
Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult connresult) {
if (connresult == ConnectivityResult.none) {
} else if (previous == ConnectivityResult.none) {
// internet conn
Navigator.of(context).pop();
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) =>
(Constants.prefsMobile.getString("mobile") == null
? Login()
: Home(signindata: signinData)),
));
}
previous = connresult;
});
}
I have not used any dispose method for this. If any one know please let me know how can I solve this problem. How to dispose. I am getting a crash report after my app close as follows
E/AndroidRuntime( 8064): java.lang.RuntimeException: Unable to destroy activity {com.example.aa_store/com.example.aa_store.MainActivity}: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter activity
is this crash message for the above problem? Please help.

Please use.
#override
void dispose() {
Connectivity().onConnectivityChanged.cancel();
super.dispose();
}
Better, define your stream outside the initState:
Stream _connectivityStream = Connectivity().onConnectivityChanged;
and in dispose use _connectivityStream.cancel();.
The error means that you instantiated a stream, which on changes of events, triggers build changes. This stream is setup during initState, meaning when the widget is first created. Connectivity().onConnectivityChanged.listen(....etc).
But you never tell flutter to cancel listening to this stream when the widget is disposed.
This is the role of the dispose method. Similar to how you want logic to be performed when the widget is built, you use initState, you should also tell it when you are no longer interested in these changes in logic.
Failing to do so, will result in the error you are having, aside from memory leaks also.
This is the translation of the error This widget has been unmounted, so the State no longer has a context (and should be considered defunct). which you posted. "Hey, this widget isn't in the tree anymore, its state is not mounted, I can't rebuild it, and you need to pay attention to it.
Please consider using the dispose method for these Flutter elements, not to mention all of them, but from the top of my mind:
AnimationControllers.
Timers.
Streams listeners.

Related

How to dispose of an unmounted Widget in Flutter?

I am writing a flutter app and if you already started it once, I dont want the user to see the Intro Screen again
In my MaterialApp:
home: firstStart ? DeviceSelection() : StartScreen()
firstStart is a boolean, if it is the first start, it should start the App with the StartScreen() and if its not, it should go straight to DeviceSelection(). That all works fine, but my problem is the following error:
Error: This widget has been unmounted, so the State no longer has a context (and should be considered defunct).
Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active.
I think it starts StartScreen too, even if firstStart is false, because it has to get its value out of the shared preferences, because I see it pop up shortly sometimes.
I already tried some stuff I found, like writing a dispose method:
#override
void dispose() {
super.dispose();
}
or
if (!mounted) {
Navigator.pop(context);
}
in a method that I call after the screen starts, but it doesnt work either. Any ideas what I could do to get rid of this error? Thanks
mb set a flag, like:
bool lookedIntro = false; // if user look it first
and then use:
#override
void initState() {
// take data flag from database or somewhere else (file)
// and check it
}
and also read about initState() more

showModalBottomSheet and Unhandled Exception: setState() called after dispose() on parent widget

Context:
I have a modal bottom sheet that pops up, upon selection of Camera/Gallery acquires/selects an image XFile and returns it for processing (uploading) done with the help of image_picker.
This is done with a sample line:
ListTile(
onTap: () {
// definition: Future<XFile?> showCamera(IdPhotoOrientation orientation);
showCamera(orientation).then((value) => Navigator.of(context).pop<XFile?>(value));
},
...
),
Picking an image with showModalBottomSheet is done by returning the selected XFile and processing it on a chained function _handleFile(XFile, enum):
return showModalBottomSheet<XFile?>(
context: context,
builder: (context) {
return SingleChildScrollView(
child: ListBody(
children: [
...
ListTile(
onTap: () {
showCamera(orientation).then((value) => Navigator.of(context).pop<XFile?>(value));
},
leading: Icon(Icons.camera),
title: Text("From Camera"),
),
...
],
),
);
},
).then((value) => _handleFile(value, orientation));
What is the problem:
While processing file in _handle(XFile?, int), I need to update the state of the app to show progress bar updates, circular indicators, uploading status, etc.
Future<void> _handleFile(XFile? xfile, int orientation) {
if (xfile == null) {
return Future.value();
}
// store locally with Uploading Status
var imageService = locator<ImageService>();
setState(() { <-------- offending line (ui_partner_registration_id_photos.dart:103:5)
remoteImageStatus[xfile] = UploadStatus.Uploading;
images[orientation] = xfile;
});
// Upload and update result / error
return imageService.uploadIDPhoto(File(xfile.path), orientation).then((value) {
setState(() {
idPhotos[orientation] = value;
remoteImageStatus[xfile] = UploadStatus.Done;
});
print("Uploaded [${xfile.path}]");
}).onError((error, stackTrace) {
print("Error uploading image");
print(stackTrace);
setState(() {
remoteImageStatus[xfile] = UploadStatus.Error;
});
});
}
Why is this a problem?
setState() cannot be called on a stateful widget that is no longer visible/active/in-focus which is now the case for the showModalBottomSheet. That being said, after calling Navigator.pop() this should no longer be the case as the parent stateful widget is now in focus, this is causing my confusion.
(temporary) Solution
A temporary solution (which does not give exactly the desired result) is to add a mounted check as described here with an example here:
if (mounted) {
setState((){
// perform actions
})
}
StackTrace:
[VERBOSE-2:ui_dart_state.cc(199)] Unhandled Exception: setState() called after dispose(): _RegisterIDPhotosState#b75f9(lifecycle state: defunct, not mounted)
This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.
The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
#0 State.setState.<anonymous closure> (package:flutter/src/widgets/framework.dart:1052:9)
#1 State.setState (package:flutter/src/widgets/framework.dart:1087:6)
#2 _RegisterIDPhotosState._handleFile (my-awesome-app/viewcontrollers/register/partner/ui_partner_registration_id_photos.dart:103:5)
#3 _RegisterIDPhotosState.pickImageWithModalPopup.<anonymous closure> (package:my-awesome-app/viewcontrollers/register/partner/ui_partner_registration_id_photos.dart:188:23)
#4 _rootRunUnary (dart:async/zone.dart:1362:47)
#5 _CustomZone.runUnary (dart:async/zone.dart:1265:19)
<asynchronous suspension>
Question:
After selecting a file and starting the upload process, how can I call setState() as in the example of _handleFile(XFile?, int) above?
Refactor that logic to a ChangeNotifier or ValueNotifier higher up in the widget tree and make your Widgets use it to share state between them see the official docs for a more in thorough description.
The setState approach won't work because you are handling 2 different widgets there. You state:
"That being said, after calling Navigator.pop() this should no longer be the case as the parent stateful widget is now in focus, this is causing my confusion."
Whats causing your confusion is that setState is not a global callback which is executed in the currently focused Sateful Widget, setState is nothing more than executing your callback and calling markNeedsBuild for the specific widget in which the setState call was made, which in your case is no longer mounted.
That being said the docs I pointed you to is a recommended way of sharing state in a Flutter app.

How to catch specified exception, display warning and logout effectively?

Web application contains many screens with list views and edit dialogs for edit selected records. List view uses "standard display behavour" using StreamBuilder.
Simplified bloc.dart
class Bloc {
final _subject = StreamController<Data>.broadcast();
Stream<Data> get data => _subject.asyncMap(_getData);
Future<Data> _getData(...) {
try {
final data = await api.getUrl(...);
return data;
} on SomeException catch (e) {
// This exception is catched by StreamBuilder
throw ApiException('could not get data');
}
}
}
Simplified widget.dart
StreamBuilder<...>(
stream: bloc.data,
builder: (context, snapshot) {
if (snapshot.hasError) {
// Bloc throws an exception which thrown by API client (HttpClient).
return ErrorWidget(snapshot.error.toString());
}
if (snapshot.hasData) {
return ListView.builder(...);
} else {
return CircularProgressIndicator();
}
}
),
Each edit dialog uses showDialog to display selected item details and save modifications.
There may be a situation when (for example) access token is invalid (or expired) during his already authenticated session. It is necessary to inform user about this (showing him a warning) and after closing warning destroy the session and return to login screen. Is it possible to implement effective solution to catch specific exception (eg. TokenInvalidException) in a single place without adding symbolic code like:
if (snapshot.error is TokenInvalidException) {
// destroy session
// return to Login
}
into each StreamBuilder list view and some code to check if exception occurred on each Dialog's save action?
I have 100500 screens and I do not want to implement the same code for each screen. Actually I want some global exception catcher which catches the required exception and executes specific action.
Now I see some global StreamController with stream which accepts such exception (events) and is listen by some global parent widget (MaterialApp or maybe Main).
final globalExceptionController = StreamController<TokenInvalidException>.broadcast();
// somewhere
globalExceptionController.stream.listen((e) async {
// Show warning
final result = await showDialog(...);
// Go to login
Navigator.pushReplacement(context, <login page route>);
}) {}
The problem I see here is the bloc object which throws the exception will have strong dependency on stream controller which is not interested to it absolutely. But actually I need to link somehow the bloc and GUI because to show warning and switch the route I need a context.
Any ideas or critics are appreciated.
Maybe the coordinator bloc pattern described here helps you with this problem

Close `showModalBottomSheet` after a timeout

I tried to close showModalBottomSheet widget through an event listener.
showModalBottomSheet(
useRootNavigator: true,
context: context,
builder: (newcontext) {
return MyBottomDialog();
});
I tried to close this through an event listener:
class _MyBottomDialogState extends State<MyBottomDialog> {
...
...
void initState() {
_myEventStream.listen((state) {
if (state == 'timeout') {
Navigator.of(context, rootNavigator: true).pop();
}
});
super.initState();
}
TypeError: Cannot read property 'findAncestorStateOfType' of null
.
.
.
Looking up a deactivated widget's ancestor is unsafe.
At this point the state of the widget's element tree is no longer stable.
To safely refer to a widget's ancestor in its dispose() method, save a
reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in
the widget's didChangeDependencies() method.
How do I achieve this?
The stream class actually comes with a timeout method. You just have to feed it a duration and if it exceeds that duration it throws a TimeoutException. Wrap the execution in a try block and call the close modal sheet method from the on TimeoutException catch (e) block perhaps.
May be I didn't capture properly what the error is saying.
Even after closing the dialog the stream subscription persists. So the handler is called even if the dialog is not open.
Adding _streamSubscription.cancel() in the widget's dispose method helps solve this problem.

Flutter Socket IO can't get data after tab is changed

So, When I open the tab for the first time, data from Socket.IO appears. But when I change the tab and go back, I can't get data from Socket.IO.
This is my Code:
Map <String,dynamic> list;
IO.Socket socket = IO.io('http://localhost:3000', <String, dynamic>{
'transports': ['websocket']
});
#override
initState(){
socket.on('connect',(_){
socket.on('stockQuote',(jsonData){
setState(() {
list = jsonData;
isLoading = false;
});
});
});
super.initState();
}
dispose(){
super.dispose();
}
I get these errors:
Unhandled Exception: setState() called after dispose(): _StocksState#0faab(lifecycle state: defunct, not mounted)
This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.
The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose()
So apparently I found a solution to make the Socket.io load again.
Fix Code:
IO.Socket socket = IO.io('http://localhost:3000', <String, dynamic>{
'transports': ['websocket'],
'forceNew':true
});
So apparently I need to add 'forceNew': true so that the page loads the socket.io like new.
Source: How to close a socket.io connection