4

The context:
I stumbled upon a minor crash while testing a ListView of Dismissibles in Flutter. When swiping a dismissible, a Dialog is shown using the confirmDismiss option, for confirmation. This all works well, however the UI crashes when testing an unlikely use case. On the page are several options to navigate to other (named) routes. When a dismissible is swiped, and during the animation an option to navigate to a new route is tapped, the crash happens.

How to replicate the crash:

  1. Dismiss the Dismissible
  2. During the animation that follows (the translation of the position of the dismissible), tap on an action that brings you to a new route. The timeframe to do this is minimal, I've extended it in the example.
  3. The new route loads and the UI freezes

For reference, this is the error message:

AnimationController.reverse() called after AnimationController.dispose()

The culprit is the animation that tries to reverse when it was already disposed:

package:flutter/…/widgets/dismissible.dart:449

Things I've tried:
Initially, I tried checking this.mounted inside the showDialog builder but quickly realised the problem is not situated there.
Another idea was to circumvent the problem by using CancelableOperation.fromFuture and then cancelling it in the dispose() method of the encompassing widget, but that was to no avail.

What can I do solve or at least circumvent this issue?

The code (can also be found and cloned here):

// (...)
class _DimissibleListState extends State<DimissibleList> {
  int childSize = 3;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        itemCount: childSize,
        itemBuilder: (context, index) {
          if (index == 0) {
            return _buildNextPageAction(context);
          }
          return _buildDismissible();
        },
      ),
    );
  }

  Widget _buildNextPageAction(context) {
    return FlatButton(
      child: Text("Go to a new page"),
      onPressed: () => Navigator.of(context).pushNamed('/other'),
    );
  }

  Dismissible _buildDismissible() {
    GlobalKey key = GlobalKey();

    return Dismissible(
      key: key,
      child: ListTile(
        title: Container(
          padding: const EdgeInsets.all(8.0),
          color: Colors.red,
          child: Text("A dismissible. Nice."),
        ),
      ),
      confirmDismiss: (direction) async {
        await Future.delayed(const Duration(milliseconds: 100), () {});
        return showDialog(
          context: context,
          builder: (context) {
            return Dialog(
              child: FlatButton(
                onPressed: () => Navigator.of(context).pop(true),
                child: Text("Confirm dismiss?"),
              ),
            );
          },
        );
      },
      resizeDuration: null,
      onDismissed: (direction) => setState(() => childSize--),
    );
  }
}
Sven
  • 3,204
  • 1
  • 20
  • 28
  • Did you tried to navigate with a delay? Like pop after 500 milliseconds ? – danypata May 16 '19 at 10:34
  • Hey @danypata that's an interesting chain of thought! But having a delay did not solve the issue. Now the app crashes when the user returns to the page with the listview (and the error remains the same) – Sven May 16 '19 at 11:54
  • i have added a line "await Future.delayed(const Duration(milliseconds: 100), () {});", it worked for me. Thanks for asking this question. – Kamlesh Jan 24 '20 at 09:51

1 Answers1

2

I had almost same problem with confirmDismiss ,in my case I was using (await Navigator.push() ) inside of confirmDismiss to navigate to another screen but in return I faced this error :

AnimationController.reverse() called after AnimationController.dispose()

so to solve my problem inside of confirmDismiss I call a future function out side of confirmDismiss (without await ) and then add return true or false after that function call to finish animation of confirmDismiss.

Shojaeddin
  • 1,851
  • 1
  • 18
  • 16