1

I got the 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" upon upgrading the flutter SDK from >2.5.1 and their pub packages. Here is the code snippet of the error-causing widget:

   class SplashScreen extends StatefulWidget {
  SplashScreen({this.auth});
  final BaseAuth auth;
  @override
  _SplashScreenState createState() => _SplashScreenState();
}

class _SplashScreenState extends State<SplashScreen>
    with TickerProviderStateMixin {
  Animation logoanimation, textanimation;
  AnimationController animationController;

  @override
  void initState() {
    super.initState();
    animationController =
        AnimationController(vsync: this, duration: Duration(seconds: 2));
    logoanimation = Tween(begin: 25.0, end: 100.0).animate(
        CurvedAnimation(parent: animationController, curve: Curves.bounceOut));
    textanimation = Tween(begin: 0.0, end: 27.0).animate(CurvedAnimation(
        parent: animationController, curve: Curves.bounceInOut));
    logoanimation.addListener(() => this.setState(() {}));
    textanimation.addListener(() => this.setState(() {}));
    animationController.forward();
    Timer(
        Duration(seconds: 4),
        () => Navigator.of(context).pushReplacement(MaterialPageRoute(
            builder: (BuildContext context) => RootPage(       // <----- Error pointing to 
                  auth: widget.auth,
                ))));
  }

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

This error is pointing to the line builder: (BuildContext context) => RootPage(..

Here is what i have tried based on this solution but, I coud not be able to resolve the issue.

 Timer(
        Duration(seconds: 4),
        () => Navigator.of(context, rootNavigator: true)
            .pushReplacement(MaterialPageRoute(
                builder: (BuildContext context) => RootPage(
                      auth: widget.auth,
                    ))));

Update: I tried to use future.delayed and if(mounted) scenarios instead of Timer but this has not resolved the issue.

But, one observation which would be interesting is:

Instead of naviagating to RootPage that needs authentication, If we navigate to some other dart page which do not need login authentication the navigation works fine without any errors.

Here is the code snippet for the RootPage:

class RootPage extends StatefulWidget {
  const RootPage({this.auth});
  final BaseAuth auth;
  @override
  _RootPageState createState() => _RootPageState();
}

enum AuthStatus {
  //NotDetermined,
  SignedIn,
  NotSignedIn
}
//...
//...


@override
  Widget build(BuildContext context) {
    switch (authStatus) {
      case AuthStatus.NotSignedIn:
        return LoginPage(
          auth: widget.auth,
          onSignedIn: _signedIn,
        );
        break;
      case AuthStatus.SignedIn:
        return TabbedHomeScreen(
          auth: widget.auth,
          onSignedOut: _signedOut,
        );
        break;
      /*case AuthStatus.NotDetermined:
        return Scaffold(body: Center(child: Text("Hello World"),),);
        break;*/
    }
    
  }
}

Could someone please share your thoughts and assist me in resolving this issue.

ranjith
  • 115
  • 2
  • 10
  • cancel `Timer` in dispose method – Pokaboom Apr 20 '22 at 08:46
  • create a timer variable and use that to start in `initState `and use that variable to `cancel` – Pokaboom Apr 20 '22 at 08:48
  • Thanks for the suggestion. I tried it but i still get the same error while running the project @Pokaboom – ranjith Apr 20 '22 at 14:30
  • timer will call that function every 4 seconds if not canceled. For example, let's say navigator take 10 ms to push new screen then that function will be call be called twice. That's why you getting error. – Pokaboom Apr 21 '22 at 05:43
  • In your case you are just moving from splash screen to auth screen which only needs to be done once so just `Future.delayed` which will be called only once – Pokaboom Apr 21 '22 at 05:44

2 Answers2

2

Not sure if it helps, but it's a good idea to also cancel the Timer in the dispose, so something like:

class _SplashScreenState extends State<SplashScreen>
    with TickerProviderStateMixin {
  Animation logoanimation, textanimation;
  AnimationController animationController;
  Timer? timer;

  @override
  void initState() {
    super.initState();
    animationController =
        AnimationController(vsync: this, duration: Duration(seconds: 2));
    logoanimation = Tween(begin: 25.0, end: 100.0).animate(
        CurvedAnimation(parent: animationController, curve: Curves.bounceOut));
    textanimation = Tween(begin: 0.0, end: 27.0).animate(CurvedAnimation(
        parent: animationController, curve: Curves.bounceInOut));
    logoanimation.addListener(() => this.setState(() {}));
    textanimation.addListener(() => this.setState(() {}));
    animationController.forward();
    timer = Timer(
        Duration(seconds: 4),
            () => Navigator.of(context).pushReplacement(MaterialPageRoute(
            builder: (BuildContext context) => RootPage(       // <----- Error pointing to 
              auth: widget.auth,
            ))));
  }

  @override
  void dispose() {
    animationController.dispose();
    timer?.cancel();
    super.dispose();
  }
Ivo
  • 18,659
  • 2
  • 23
  • 35
0

Here is an example of a Splash screen and a transition without an error. timer?.cancel - is what you most likely need.

class SplashPage extends StatefulWidget {
  const SplashPage({super.key});

  @override
  State<SplashPage> createState() => _SplashPageState();
}

class _SplashPageState extends State<SplashPage> {
  Timer? timer;

  route() {
    Navigator.of(context).pushReplacement(
      PageRouteBuilder(
        transitionDuration: const Duration(milliseconds: 400),
        pageBuilder: (BuildContext context, Animation<double> animation,
            Animation<double> secondaryAnimation) {
          return const LoginPage();
        },
        transitionsBuilder: (BuildContext context, Animation<double> animation,
            Animation<double> secondaryAnimation, Widget child) {
          return FadeTransition(
            opacity: animation,
            child: child,
          );
        },
      ),
    );
  }

  startTimer() async {
    timer = Timer(const Duration(seconds: 2), route);
  }

  @override
  void initState() {
    startTimer();
    super.initState();
  }

  @override
  void dispose() {
    timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: Provider.of<ThemeProvider>(context).getThemeMode(),
      builder: (context, AsyncSnapshot snapshot) => Scaffold(
        backgroundColor: Theme.of(context).canvasColor,
        body: Center(
          child: Image.asset('assets/images/big_logo.png'),
        ),
      ),
    );
  }
}
dubinsky
  • 31
  • 4