21


I know that there are already two posts regarding this question, but I could not solve my issue looking at them. Probably because in my case the issue is different.

The code is the following. I want to load some data from the database while showing a loading page. After the data is loaded, I initialize the provider with the loaded data and then I move into a different page. This code does not need to be in a StatefulWidget, but I try to put it in a StatefulWidget to solve the issue, but without success.

class _InitDBDataState extends State<_InitDBData> {
  @override
  Widget build(BuildContext context) {
    _fetchData(context);
    return const Center(child: const CircularProgressIndicator());
  }

  Future<void> _fetchData(BuildContext context) async {
    print('fetching data...');
    print('context: $context');
    final initData = await DBService.service.getInitialData();
    print('Data fetched');
    print('context: $context');
    Provider.of<DataProvider>(context, listen: false).init(initData);
    Navigator.of(context).pushReplacementNamed(MainScreen.routeName);
  }
}

I do not have any error if the application runs from scratch, but when I am doing a "Hot Reload" I get the following error pretty often, and it is annoying since I need to restart the application for each small change in the code.

I/flutter ( 9596): fetching data...
I/flutter ( 9596): context: _InitDBData(dirty, state: _InitDBDataState#46860)
I/flutter ( 9596): fetching data...
I/flutter ( 9596): context: _InitDBData(dirty, state: _InitDBDataState#55124)
I/flutter ( 9596): Data fetched
I/flutter ( 9596): context: _InitDBData
E/flutter ( 9596): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
E/flutter ( 9596): At this point the state of the widget's element tree is no longer stable.
E/flutter ( 9596): To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
E/flutter ( 9596): #0      Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> 
package:flutter/…/widgets/framework.dart:3508
E/flutter ( 9596): #1      Element._debugCheckStateIsActiveForAncestorLookup 
package:flutter/…/widgets/framework.dart:3522
E/flutter ( 9596): #2      Element.getElementForInheritedWidgetOfExactType 
package:flutter/…/widgets/framework.dart:3588
E/flutter ( 9596): #3      Provider.of 
package:provider/src/provider.dart:221
E/flutter ( 9596): #4      _InitDBDataState._fetchData 
package:productive_diary/initScreen.dart:46
E/flutter ( 9596): <asynchronous suspension>
E/flutter ( 9596): #5      _InitDBDataState.build 

I don't know why "fetching data..." is printed twice, and I have no clue on how to solve the issue.


I thought the issue was solved with the solution of Saman Salehi, but working in debug mode I had the same exception in the _fetchData function, that now is called in the function initState()

Exception has occurred.
FlutterError (Looking up a deactivated widget's ancestor is unsafe.
At this point the state of the widget's element tree is no longer stable.
To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.)

I got another error after applying the edits suggested by Stewie Griffin .

The error is on the line Provider.of<DataProvider>(context, listen: false).init(initData);

I got it during a hot reload. It seems less common than the other error, so the answer of Stewie Griffin surely improved the stability of my Stewie Griffin

E/flutter (23815): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: NoSuchMethodError: The getter 'owner' was called on null.
E/flutter (23815): Receiver: null
E/flutter (23815): Tried calling: owner
E/flutter (23815): #0      Object.noSuchMethod  (dart:core-patch/object_patch.dart:53:5)
E/flutter (23815): #1      Provider.of 
package:provider/src/provider.dart:193
E/flutter (23815): #2      _InitDBDataState._fetchData 
package:productive_diary/initScreen.dart:49
E/flutter (23815): <asynchronous suspension>
E/flutter (23815): #3      _InitDBDataState.initState 

Could you please help me?

Draxent
  • 500
  • 1
  • 6
  • 14
  • 1
    This error is related to the package you are using. Try to put your function in `didChangeDependencies()` instead of `initState()`. It might fix the problem. If it doesn't, see this thread: https://github.com/rrousselGit/provider/issues/183 – Stewie Griffin May 01 '20 at 19:36

5 Answers5

46

First of all, never call async methods inside of build as mentioned. Build method is constantly rebuilt and it will cause your fetch method to be repeated like an infinite loop. After you fix that, you still get the same error because of this part:

Navigator.of(context).pushReplacementNamed(MainScreen.routeName);

You shouldn't call Navigator during build. Here is what you need to do:

Add this line to the top of your file to use SchedulerBinding:

import 'package:flutter/scheduler.dart';

Wrap Navigator with SchedulerBinding to wait for completing the state before navigating to another screen. Then, call your async method inside of initState.

class _InitDBDataState extends State<_InitDBData> {
@override
  void initState() {
    // Call your async method here
    _fetchData();
    super.initState();
  }

  Future<void> _fetchData() async {
    print('fetching data...');
    print('context: $context');
    final initData = await DBService.service.getInitialData();
    print('Data fetched');
    print('context: $context');
    Provider.of<DataProvider>(context, listen: false).init(initData);

    // Wrap Navigator with SchedulerBinding to wait for rendering state before navigating
    SchedulerBinding.instance.addPostFrameCallback((_) {
      Navigator.of(context).pushReplacementNamed(MainScreen.routeName);
    });
  }
  @override
  Widget build(BuildContext context) {
    return Center(child: CircularProgressIndicator());
  }
}

Tip: You don't need to pass the context in a Stateful Widget because you can access it from everywhere.

Stewie Griffin
  • 4,690
  • 23
  • 42
  • Best Answer! I've been searching all day on how to fix this issue, tried everything but with your detailed explanation I was able to fix all errors inside my code since I had like 5 `Navigator.of(context).pushReplacementNamed()` in the `initState` . Thanks a million! – Mahmoud Eidarous Aug 06 '21 at 23:54
  • Where to call an async function in a Stateless widget? In a Stateful widget I understand the location `initState` but is there another lifecycle hook for Stateless widgets than build? – xetra11 May 18 '22 at 08:12
  • Well, there is not. As it is stated in the docs, StatelessWidget is useful when the part of the user interface you are describing does not depend on anything other than the configuration information in the object itself. If you think you have to perform an asynchronous task in a StatelessWidget, then use `FutureBuilder` which is also a StatefulWidget: https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html – Stewie Griffin May 18 '22 at 19:21
6

You shouldn't use the build method for anything other than building UI. build can be called at any time even when it's not on the screen.

I would move the _fetchData to the initState so it wouldn't cause any conflict at the build method.

class _InitDBDataState extends State<_InitDBData> {

  @override
  void initState() {
    super.initState();
    _fetchData(context);
  }

  @override
  Widget build(BuildContext context) {
    return const Center(child: const CircularProgressIndicator());
  }

  Future<void> _fetchData(BuildContext context) async {
    print('fetching data...');
    print('context: $context');
    final initData = await DBService.service.getInitialData();
    print('Data fetched');
    print('context: $context');
    Provider.of<DataProvider>(context, listen: false).init(initData);
    Navigator.of(context).pushReplacementNamed(MainScreen.routeName);
  }
}
Saman Salehi
  • 1,995
  • 1
  • 22
  • 36
  • 1
    Question: If you await a Future in initState, will that not possibly result in terrible performance? Should you not result to using StreamProvider or FutureProvider in this case? – Michiel Feb 16 '21 at 12:36
0
 Navigator.pushReplacement(context,MaterialPageRoute(builder:(context) => WelcomeScreen()),); 

|^| .wrap this code SchedulerBinding.instance!.addPostFrameCallback on above code. like this below:

SchedulerBinding.instance!.addPostFrameCallback((_) {
  Navigator.pushReplacement(
    context,
    MaterialPageRoute(builder: (context) => WelcomeScreen()),
  );
});

Schedule a callback for the end of this frame.

Does not request a new frame.

This callback is run during a frame, just after the persistent frame callbacks (which is when the main rendering pipeline has been flushed). If a frame is in progress and post-frame callbacks haven't been executed yet, then the registered callback is still executed during the frame. Otherwise, the registered callback is executed during the next frame.

The callbacks are executed in the order in which they have been added.

Post-frame callbacks cannot be unregistered. They are called exactly once.

Before:

enter image description here


After

enter image description here

SampleCode Here i use rive: ^0.8.1 dartpackage

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:rive/rive.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(MaterialApp(home: SimpleAnimation()));
}

setdata(BuildContext context) async {
  await Future.delayed(const Duration(seconds: 5), () {
    SchedulerBinding.instance!.addPostFrameCallback((_) {
      Navigator.pushReplacement(
        context,
        MaterialPageRoute(builder: (context) => WelcomeScreen()),
      );
    });

  });
}

class SimpleAnimation extends StatelessWidget {
  const SimpleAnimation({Key? key, this.loading}) : super(key: key);
  final bool? loading;

  @override
  Widget build(BuildContext context) {
    setdata(context);
    return Scaffold(
      body: Center(
        child: Container(
          height: 200,
          width: 200,
          child: RiveAnimation.network(
            'https://cdn.rive.app/animations/vehicles.riv',
          ),
        ),
      ),
    );
  }
}

class WelcomeScreen extends StatelessWidget {
  const WelcomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Container(
          child: Text(
            "HOME PAGE",
            style: TextStyle(fontSize: 50),
          ),
        ),
      ),
    );
  }
}
lava
  • 6,020
  • 2
  • 31
  • 28
0

For this probleme you can use one of this two solutions : first: add scheduler like this :

 SchedulerBinding.instance!.addPostFrameCallback((_) {
   Navigator.of(context).pushReplacementNamed(MainScreen.routeName);
});

second: add future delayed with some milliseconds like this :

  Future.delayed(Duration(microseconds: 200))
                            .then((value) {
   Navigator.of(context).pushReplacementNamed(MainScreen.routeName);
});

!!! But if you a lot of change of state in the same time this solutions may still give the same error.

if is the case try to use the second solution by augmenting the duration.

Ahmed El Rhaouti
  • 1,497
  • 1
  • 3
  • 5
-1

if it is caused by accessing ScaffoldMessenger by context then putting it inside a try catch will resolve context error.

try{
ScaffoldMessenger.of(_scaffoldKey.currentContext!).showSnackBar();
} catch(e){
print(e);
}
A.K.J.94
  • 492
  • 6
  • 14