BrunoJurkovic / flip_card

A Flutter widget that easily adds the flipping animation to any widget
BSD 3-Clause "New" or "Revised" License
343 stars 103 forks source link

'package:flutter/src/animation/animation_controller.dart': Failed assertion: line 454 pos 7: '_ticker != null': AnimationController.forward() called after AnimationController.dispose() #92

Open BunnyBuddy opened 1 month ago

BunnyBuddy commented 1 month ago

I am using 2 FlipCards and they have to flip every 5 seconds, but the moment I added the second flip card, every time I land on the screen it throws this error but keeps working okay.

ERROR:

'package:flutter/src/animation/animation_controller.dart': Failed assertion: line 454 pos 7: '_ticker != null': AnimationController.forward() called after AnimationController.dispose()
flutter: AnimationController methods should not be used after calling dispose., STACK TRACE: #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
flutter: #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)
flutter: #2      AnimationController.forward (package:flutter/src/animation/animation_controller.dart:454:7)
flutter: #3      FlipCardState.toggleCard (package:flip_card/flip_card.dart:181:45)
flutter: #4      new Future.delayed.<anonymous closure> (dart:async/future.dart:393:39)
flutter: #5      _rootRun (dart:async/zone.dart:1418:47)
flutter: #6      _CustomZone.run (dart:async/zone.dart:1328:19)
flutter: #7      _CustomZone.runGuarded (dart:async/zone.dart:1236:7)
flutter: #8      _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1276:23)
flutter: #9      _rootRun (dart:async/zone.dart:1426:13)
flutter: #10     _CustomZone.run (dart:async/zone.dart:1328:19)
flutter: #11     _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:1260:23)
flutter: #12     Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
flutter: #13     _Timer._runTimers (dart:isolate-patch/timer_impl.dart:395:19)
flutter: #14     _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:426:5)
flutter: #15     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)
flutter:

Steps to Reproduce

This is my current code, (using GetX for state management)

 var flipController = FlipCardController().obs;
  var monthlyFlipCon = FlipCardController().obs;

    @override
  void dispose() {
    if (flipController.value.state != null) {
      if (flipController.value.state.mounted) {
        flipController.value.state.dispose();
      }
    }
    if (monthlyFlipCon.value.state != null) {
      if (monthlyFlipCon.value.state.mounted) {
        monthlyFlipCon.value.state.dispose();
      }
    }
    super.dispose();
  }

// in the build method
FlipCard(
                                      controller: flipController.value,
                                      direction: FlipDirection.VERTICAL,
                                      side: CardSide.FRONT,
                                      flipOnTouch: false,
                                      autoFlipDuration: const Duration(seconds: 5),
                                      onFlipDone: (val) {
                                        Future.delayed(Duration(seconds: 10), () {
                                          flipController.value.state.mounted ? flipController.value.state.toggleCard() : null;
                                        });
                                      },
                                      front: CountDownTimer(
                                        secondsRemaining: productController.seconds.value,
                                        timeBoxHeight: 20.h,
                                        timeBoxWidth: 20.w,
                                        timerTextStyle: ThemeStyles.NORMAL_STYLE_LIGHT.copyWith(
                                          color: Constant.backgroundColor,
                                          fontSize: 12.sp,
                                        ),
                                        whenTimeExpires: () {
                                          productController.seconds.value = 0;
                                        },
                                        countDownTimerStyle: ThemeStyles.HEADING_STYLE_BOLD.copyWith(color: Constant.backgroundColor),
                                        prefixText: "",
                                      ),
                                      back: Text(
                                        'Limited time offer',
                                        style: ThemeStyles.NORMAL_STYLE_LIGHT.copyWith(fontSize: 14.sp, color: Constant.backgroundColor),
                                      ),
                                    ),

                                    FlipCard(
                                      controller: monthlyFlipCon.value,
                                      direction: FlipDirection.VERTICAL,
                                      side: CardSide.FRONT,
                                      flipOnTouch: false,
                                      autoFlipDuration: const Duration(seconds: 5),
                                      onFlipDone: (val) {
                                        Future.delayed(Duration(seconds: 10), () {
                                          monthlyFlipCon.value.state.mounted ? monthlyFlipCon.value.state.toggleCard() : null;
                                        });
                                      },
                                      front: CountDownTimer(
                                        secondsRemaining: productController.remainingSeconds.value,
                                        timeBoxHeight: 20.h,
                                        timeBoxWidth: 20.w,
                                        timerTextStyle: ThemeStyles.NORMAL_STYLE_LIGHT.copyWith(
                                          color: Constant.backgroundColor,
                                          fontSize: 12.sp,
                                        ),
                                        whenTimeExpires: () {
                                          productController.remainingSeconds.value = 0;
                                        },
                                        countDownTimerStyle: ThemeStyles.HEADING_STYLE_BOLD.copyWith(color: Constant.backgroundColor),
                                        prefixText: "",
                                      ),
                                      back: Text(
                                        'Limited time offer',
                                        style: ThemeStyles.NORMAL_STYLE_LIGHT.copyWith(fontSize: 14.sp, color: Constant.backgroundColor),
                                      ),
                                    ),
ciriousjoker commented 1 month ago

Two things:

  1. Imo, getx is a huge noob trap because while it makes many things more convenient than doing it the "correct" way, it also hides a lot of the internal workings and leads to hard to fix bugs down the line. I've experienced this myself with my early projects, but also heard this independently from many devs whose opinion I highly value.
  2. What is obs? Why aren't you just creating FlipCardControllers regularly? Is obs some sort of getx magic? If so, I have no idea how to help you aside from pointing towards the readme and try to follow the example as closely as possible
BunnyBuddy commented 1 month ago

@ciriousjoker Its just making the variable/controller or whatever observable and instead of calling setState() I can just wrap the widget, with that particular variable, in Obx() and it'll listen for changes. It's kinda similar to setstate (but instead of rebuilding the entire widget tree It just updates that particular widget which is performance wise better believe it or not). I've tried it without GetX as well and it does the same thing so I am pretty sure a different state management is not the issue here. Cheers.

ciriousjoker commented 1 month ago

@BunnyBuddy Ah ok. I'm not convinced that obx can bypass the regular flutter rendering process of laying out the whole subtree of widgets again on every state while reusing unchanged stuff, but I haven't done any benchmarks or looked into it yet.

Either way, my guess is that without obx magic it should work.