Flutter Navigation and the requirement for dispose() in the mean time
Solution 1
Instead of setState(() {...})
, try if (mounted) { setState(() {...}) }
for code that may be running after the user navigates away.
Solution 2
It turned out, it was not the dispose()-method in view1 that caused the image_picker from failure. It was the animation that was still running when image_picker was called.
I have finally a working solution by doing the following:
Inside view1 (where image_picker is originally called), add one line of code:
onPressed: () async {
controller.dispose(); // !!!!!!!!! Adding this line helped !!!!!!!!!!!
await navigateToImagePicker(context);
},
Also, delete (or comment out) the entire dispose()-method :
// @override
// void dispose() {
// if (_debounce?.isActive ?? false) {
// _debounce.cancel(); // if _debounce is active cancel it...
// }
// _debounce = Timer(const Duration(milliseconds: 200), () {});
// controller.dispose();
// _debounce.cancel();
// super.dispose();
// }
iKK
Updated on December 09, 2022Comments
-
iKK over 1 year
Trying to use
image_picker
in Flutter, I have the following issue:When navigation is supposed to pop back to Widget Nr1, I can no longer call
setState()
inside Widget Nr1. This is due to the fact that thedispose()
method was called once theNavigation.push
from Widget-Nr1 to Widget-Nr2 happened.It turns out that I absolutely need to call this
dispose()
method in order for theimage_picker
plugin to work correctly. (if I don't then the error...was disposed with an active Ticker...
happens, probably due to the fact that theimage_picker
plugin does something under the hood that desperately needs dispose() beforehand.Anyway, I feel like the snake bites its tail.
As a summary I do the following (also see code below):
- inside Widget Nr1: Pressing a FloatingAction-Button, pushes the Navigator to a Widget Nr2
- both Widgets (Nr1 and Nr2) are Stateful Widgets
- they both have a dispose-method (needed otherwise image_picker does not work)
- Widget-Nr2 calls the
image_picker
plugin (letting the user take a photo with the camera and asking the user for some String-text describing the image) - the result (i.e. imageFile and some String-text) needs to be given back to Widget-Nr1 (using
Navigation.pop
) - the Widget-Nr1 actually does get this data (i.e. image plus some String-text)
- but: it cannot call
setState()
anymore after the Navigation.pop most likely due to the fact that both Widgets had already called theirdispose()
method
I get the error inside Widget-Nr1:
Dart Error: Unhandled exception: setState() called after dispose()
What can I do in order to make this work ?
How can I use the result-data of the
image_picker
(that requiresdispose()
in Widget-1) as a Navigation.pop result again in Widget-1 and this in a way wheresetState()
is still possible after all the Navigation ??Or is there another approach to go with ?
Here is my code:
StatefulWidget Nr1 (excerpt of it):
child: FloatingActionButton( onPressed: () async { _imagePickerResult = await navigateToImagePicker(context); setState(() async { this.itemBins.add(ItemBin( _imagePickerResult.locationName, _imagePickerResult.locationImage)); }); }, child: Icon(Icons.add), ), // ... Future<ImagePickerResult> navigateToImagePicker(BuildContext context) async { return await Navigator.push( context, MaterialPageRoute(builder: (context) => MyImagePickerView()) ); } // ... class ImagePickerResult { String locationName; Image locationImage; ImagePickerResult({this.locationName, this.locationImage}); }
StatefulWidget Nr2:
import 'package:flutter/material.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; import './../../models/image_picker_location.dart'; class MyImagePickerView extends StatefulWidget { _MyImagePickerViewState createState() => _MyImagePickerViewState(); } class _MyImagePickerViewState extends State<MyImagePickerView> { TextEditingController _myController = TextEditingController(); File _imageFile; bool _pickImage = true; @override Widget build(BuildContext context) { if (_pickImage) { return FutureBuilder<File>( future: ImagePicker.pickImage(source: ImageSource.camera), builder: (BuildContext context, AsyncSnapshot<File> snapshot) { if (snapshot.hasData) { _pickImage = false; _imageFile = snapshot.data; return _showImage(snapshot.data); } else { return Scaffold( body: Center( child: Text('no image picker availalbe'), ), ); } }, ); } else { return _showImage(_imageFile); } } Widget _showImage(File imgFile) { return Scaffold( body: Stack( alignment: AlignmentDirectional.topStart, children: <Widget>[ Positioned( left: 0.0, bottom: 0.0, width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, child: Center( child: imgFile == null ? Text('No image selected.') : Image.file(imgFile), ), ), Positioned( left: 16.0, bottom: 70.0, width: MediaQuery.of(context).size.width - 32.0, height: 50.0, child: Container( color: Colors.grey[100], child: TextField( autofocus: false, keyboardType: TextInputType.text, autocorrect: false, style: TextStyle( color: Colors.black, fontSize: 22.0, fontWeight: FontWeight.w600), decoration: InputDecoration( hintStyle: TextStyle( color: Colors.black38, fontSize: 22.0, fontWeight: FontWeight.normal), hintText: "depart From :", contentPadding: const EdgeInsets.fromLTRB(6.0, 13.0, 0, 12.0), enabledBorder: UnderlineInputBorder( borderSide: BorderSide(color: Colors.red, width: 2.0), ), ), maxLines: 1, textAlign: TextAlign.left, controller: _myController, onEditingComplete: () { FocusScope.of(context) .requestFocus(FocusNode()); // dismiss keyboard Navigator.pop( context, ImagePickerResult( locationName: _myController.text, locationImage: Image.file(imgFile), ), ); }, ), ), ), ], ), ); } }
dispose-method of Widget Nr1:
@override void dispose() { if (_debounce?.isActive ?? false) { _debounce.cancel(); // if _debounce is active cancel it... } _debounce = Timer(const Duration(milliseconds: 200), () { // security wait due to the fact that there are animations still running during setState() }); // dispose AnimationController controller.dispose(); _debounce.cancel(); super.dispose(); }
dispose method of Widget-Nr2:
@override void dispose() { _myController.dispose(); super.dispose(); }
Here is the error-message if I don't make view1 do the dispose() before image_picker starts... (please note that there is an animation running at the moment the user wants to start image_picker and therefore the dispose() makes an artificial "wait" of 200ms before the segue to the image_picker takes place)....
flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ flutter: The following assertion was thrown while finalizing the widget tree: flutter: _HistoryViewState#a8eac(ticker active but muted) was disposed with an active Ticker. flutter: _HistoryViewState created a Ticker via its SingleTickerProviderStateMixin, but at the time dispose() flutter: was called on the mixin, that Ticker was still active. The Ticker must be disposed before calling flutter: super.dispose(). Tickers used by AnimationControllers should be disposed by calling dispose() on the flutter: AnimationController itself. Otherwise, the ticker will leak. flutter: The offending ticker was: Ticker(created by _HistoryViewState#a8eac(lifecycle state: created)) flutter: The stack trace when the Ticker was actually created was: flutter: #0 new Ticker.<anonymous closure> package:flutter/…/scheduler/ticker.dart:64 flutter: #1 new Ticker package:flutter/…/scheduler/ticker.dart:66 flutter: #2 __HistoryViewState&State&SingleTickerProviderStateMixin.createTicker package:flutter/…/widgets/ticker_provider.dart:93 flutter: #3 new AnimationController
-
iKK about 5 yearsThank you. I did not know the keyword State.mounted. It eliminated the crash after the Navigation.pop - however now the data given back to Widget-Nr1 does never get used (and shown) but just gets lost in the
onPressed() async {...}
callback. How can I keep theresult-data
(passed with the Navigation.pop) inside Widget-1 whenState.mounted == false
. Do I need to callinitState()
again after the Navigation.pop or is there another approach ? Is there some stateManagement needed (...maybe withInherited Widget
) or do you have another idea ? Thanks for any thought. -
Gazihan Alankus about 5 yearsSorry, your question is too wordy and hard to understand with a glance. What do you want to do with that result-data? You can't affect the UI as your screen is not there anymore. Do you want to save it somewhere? Maybe do that outside of setState if mounted is false?
-
iKK about 5 yearsI simply want the result-data brought back by the navigate.pop to be shown in the first view. But this mecano does not work for the image_picker for some reason, since dispose() needed to be called so that image_picker works at all. You are absolutely right that the first view is gone after having to call dispose(). After doing so, I guess, I need some sort of persistence or an inherited widget if I ever want to show the image and text that was picked in the second screen back in the first one. Normally, this navigate.push followed by an navigate.pop-with-result-data works fine - but not here.
-
iKK about 5 yearsI will try to persist the image picked in second view and retrieve it back in the first view from the persistence (and therefore get rid of the navigate.pop result-data followed by setState() since this does no longer work...). Thank you for your help.
-
Gazihan Alankus about 5 yearsYou could try to pop after the async result comes back. Then you can return whatever you want. If your problem is solved, consider accepting the answer here. Good luck!
-
iKK about 5 yearsThat is what I do already (i.e. pop after result came back). I do the pop in the second view only if I have a result form the image_picker. Again, due to the requirement of dispose() of the first view (so that image_picker works), I can never pop-return from the second view to the first view and thereafter call setState() (since dispose() had already eliminated the first view). I need to re-build the first view from scratch after any navigation. It is really quite tricky. If you ever use the image_picker plugin, you will know what I mean ;)
-
Gazihan Alankus about 5 yearsI have no problems doing push() in w1, then pop() in w2, and then setState() in the same async function in w1. Which tells me that your theory about what's going on is not accurate. I think that dispose is messing something up. Where did you learn that you have to do a debounce like that? Also minor point: don't give async functions to setState(): github.com/flutter/flutter/issues/3951#issuecomment-221638703
-
iKK about 5 yearsThank you very much for your explanations. Normally, I have no problem to do push() in w1 and then pop() in w2 (..and using the result from pop() back in w1 - I have done it many times). However, this @#!%%#^-image_picker makes my life hard :). I don't know why image_picker in w2 requires dispose() in w1. Maybe you find out why image_picker requires dispose() in w1 for it to work in w2 after push(). Did you try image_picker in w2 and sending results (i.e. its picked image and some text) back to w1. That is the mess I am not able to solve. Anyway, I gladly give you an upvote for trying to help.
-
Gazihan Alankus about 5 yearsCan you show me what gave you the idea "I don't know why image_picker in w2 requires dispose() in w1"?
-
iKK about 5 yearsSee error-message that I get if I don't (at very bottom of my initial question above....). It said
State(ticker active but muted) was disposed with an active Ticker
- do you know what that means ?? Could the animation be the problem ?? (I realised that there is a 200ms delay inside the dispose() and if this is missing (i.e. when dispose() is commented) - this might actually be the root cause of it all... What do you think ? -
iKK about 5 yearsOh gosh - I have found a solution (thanks to your last question): It was enough to dispose the animation-Controller (that was the root cause of the image_picker failure). I took the one line of code out of the w1-dispose() and added the line right before the image_picker call. And with it, I commented out the dispose()-method in w1. Thank you very much for your great support and help with this !!!
-
Gazihan Alankus about 5 yearsGreat! I'll come back to here when I need to use image_picker.