Навигация по флаттеру и требование к dispose () за это время - PullRequest
0 голосов
/ 08 февраля 2019

Пытаясь использовать image_picker во флаттере, у меня возникает следующая проблема:

Когда навигация должна вернуться к Widget Nr1, я больше не могу звонить setState() внутриВиджет Nr1.Это связано с тем, что метод dispose() был вызван после того, как произошло Navigation.push от Widget-Nr1 до Widget-Nr2.

Оказывается, мне абсолютно необходимо вызвать этот метод dispose() вДля корректной работы плагина image_picker.(если я этого не сделаю, то ошибка ...was disposed with an active Ticker... произойдет, возможно, из-за того, что плагин image_picker делает что-то под капотом, что заранее отчаянно нуждается в dispose ().

В любом случае, я чувствую, чтозмея кусает свой хвост.

В качестве резюме я делаю следующее (см. также код ниже):

  • внутри виджета Nr1: нажатие кнопки FloatingAction переводит навигатор в виджетNr2
  • оба виджета (Nr1 и Nr2) являются виджетами с отслеживанием состояния
  • они оба имеют метод dispose (необходим, иначе image_picker не работает)
  • Widget-Nr2 вызывает image_picker плагин (позволяющий пользователю сделать снимок с помощью камеры и запрашивающий у него некоторый текст String, описывающий изображение)
  • результат (то есть imageFile и некоторый текст String) должен быть возвращен Widget-Nr1 (используя Navigation.pop)
  • Widget-Nr1 действительно получает эти данные (т. Е. Изображение плюс некоторый текст строки)
  • , но: он больше не может вызывать setState() после навигации.pop скорее всего из-затот факт, что оба виджета уже вызвали свой dispose() метод

Я получаю сообщение об ошибке внутри Widget-Nr1:

Dart Error: Unhandled exception:
setState() called after dispose()

Что я могу сделать, чтобы эта работа работала?

Как я могу использовать данные результата image_picker (для которого требуется dispose() в Widget-1) в качестве результата Navigation.pop снова в Widget-1, и это таким образом, что setState()все-таки возможно все-таки навигация ??

Или есть другой подход?

Вот мой код:

StatefulWidget Nr1 (отрывок из него):

    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-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 Widget-Nr2:

   @override
   void dispose() {
     _myController.dispose();
     super.dispose();
   }

Вот сообщение об ошибке, если я не заставляю view1 выполнить dispose () до запуска image_picker ... (обратите внимание, что в момент, когда пользователь хочет запустить image_picker, запускается анимация и, следовательно, dispose ()делает искусственное «ожидание» 200 мс, прежде чем происходит переход к image_picker) ....

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 

Ответы [ 2 ]

0 голосов
/ 12 февраля 2019

Оказалось, что не метод dispose () в view1 вызвал сбой image_picker.Это была анимация, которая все еще работала, когда вызывался image_picker.

Наконец-то у меня есть рабочее решение, сделав следующее:

Внутри view1 (где изначально вызывается image_picker), добавьте одну строкукод:

onPressed: () async {
  controller.dispose();  // !!!!!!!!! Adding this line helped !!!!!!!!!!!
  await navigateToImagePicker(context);
},

Кроме того, удалите (или закомментируйте) весь dispose () - метод:

// @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();
// }
0 голосов
/ 08 февраля 2019

Вместо setState(() {...}) попробуйте if (mounted) { setState(() {...}) } для кода, который может быть запущен после того, как пользователь ушел.

...