ChatGPT解决这个技术问题 Extra ChatGPT

Flutter get context in initState method

I'm not sure if the initState is the right function for this. What I'm trying to achieve is to check when the page is rendered to perform some checks and based on them opening a AlertDialog to make some settings if needed.

I've got a Page which has a state. It's initState function looks like this:

@override
void initState() {
    super.initState();
    if (!_checkConfiguration()) {
        _showConfiguration(context);
    }
}

The _showConfiguration like this:

void _showConfiguration(BuildContext context) {
    AlertDialog dialog = new AlertDialog(
        content: new Column(
            children: <Widget>[
                new Text('@todo')
            ],
        ),
        actions: <Widget>[
            new FlatButton(onPressed: (){
                Navigator.pop(context);
            }, child: new Text('OK')),
        ],
    );

    showDialog(context: context, child: dialog);
}

If there's a better way to make this checks and if needed call the modal, please point me in the proper direction, I was looking for a onState or onRender function, or a callback I could assign to the build function to be called on render but wasn't able to find one.

Edit: It seams over here they had a similar problem: Flutter Redirect to a page on initState


r
rmtmckenzie

The member variable context can be accessed during initState but can't be used for everything. This is from the flutter for initState documentation:

You cannot use [BuildContext.inheritFromWidgetOfExactType] from this method. However, [didChangeDependencies] will be called immediately following this method, and [BuildContext.inheritFromWidgetOfExactType] can be used there.

You could move your initialization logic to didChangeDependencies, however that might not be exactly what you want as didChangeDependencies can be called multiple times in the lifecycle of the widget.

If you instead make an asynchronous call which delegates your call until after the widget has been initialized, you can then use context as you intend.

A simple way to do that is to use a future.

Future.delayed(Duration.zero,() {
  ... showDialog(context, ....)
}

Another way, which may be more 'correct', is to use flutter's scheduler to add a post-frame callback:

SchedulerBinding.instance.addPostFrameCallback((_) {
  ... showDialog(context, ....)
});

And finally, here's a little trick I like to do to use asynchronous calls in the initState function:

() async {
  await Future.delayed(Duration.zero);
  ... showDialog(context, ...)      
}();

Here's a fully fleshed out example using the simple Future.delayed:

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  bool _checkConfiguration() => true;

  void initState() {
    super.initState();
    if (_checkConfiguration()) {
      Future.delayed(Duration.zero,() {
        showDialog(context: context, builder: (context) => AlertDialog(
          content: Column(
            children: <Widget>[
              Text('@todo')
            ],
          ),
          actions: <Widget>[
            FlatButton(onPressed: (){
              Navigator.pop(context);
            }, child: Text('OK')),
          ],
        ));
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
    );
  }
}

With more context from the OP provided in comments, I can give a slightly better solution to their specific problem. Depending on the app, you may actually want to make a decision based on which page to show depending on whether it's the first time the app is opened i.e. set home to something different. And dialogs aren't necessarily the best UI element on mobile; it may be better to show a full page with the settings they need to add and a next button.


The Page in question is the first page and this called through MaterialApp's home property. So I don't really do a push there. Could you give me an Example how to do it in the build function? Currently it just returns a new Scaffold with an appBar, drawer, body and floatingActionButton
this is very bad. the first place you can access the context is the didChangeDependencies method
@wawa - I fixed up the example a bit. I actually forgot that context is actually a member variable of the state =D. So you don't need the boolean, you can just directly use the Future.delayed in your initstate. That is still required though - without it you'll get assertion errors around trying to push while in a push.
in my case it says 'Undefined name context' in initState
@rmtmckenzie is it always the case that the function Future.delayed(Duration.zero,() {} runs just after build() ? What if you add non-Future or another Future method in the initState() ? Are you aware of any gotchas? I implemented your example and it works fine so far.
P
Pablo Cegarra

Wrapping with Future

  @override
  void initState() {
    super.initState();
    _store = Store();
    new Future.delayed(Duration.zero,() {
      _store.fetchContent(context);
    });
  }

what is Store? cannot import.
you dont need to use Store, is my own state class, just use future.delayed inside initState
B
Bayu

====== UPDATED ======

Just like pointed by Lucas Rueda ( thanks to him :), When we need to get context inside initState() in order to work with "Provider", we should set the parameter listen to be = false. It makes sense because we should not listen to the initState() phase. So, for example, it should be:

final settingData = Provider.of<SettingProvider>(context, listen: false);

=========== OLD ANSWER =======

Most examples of initState() in this thread may be works for "UI" things such as "Dialog" which is the case in the root question of this thread.

But unfortunately, it doesn't work for me when applying it to get context for "Provider".

Hence, I pick didChangeDependencies() approach. As mentioned in the accepted answer, it has a caveat which is, it can be called multiple times in the lifecycle of the widget. However, it quite easy to handle it. Just use a single helper variable which is bool to prevent multiple calls inside didChangeDependencies(). Here is the example usage of _BookListState class with variable _isInitialized as the main "stopper" of "multiple calls":

class _BookListState extends State<BookList> {
  List<BookListModel> _bookList;
  String _apiHost;
  bool _isInitialized; //This is the key
  bool _isFetching;

  @override
  void didChangeDependencies() {
    final settingData = Provider.of<SettingProvider>(context);
    this._apiHost = settingData.setting.apiHost;
    final bookListData = Provider.of<BookListProvider>(context);
    this._bookList = bookListData.list;
    this._isFetching = bookListData.isFetching;

    if (this._isInitialized == null || !this._isInitialized) {// Only execute once
      bookListData.fetchList(context);
      this._isInitialized = true; // Set this to true to prevent next execution using "if()" at this root block
    }

    super.didChangeDependencies();
  }

...
}

Here is error logs when I am trying to do initState() approach:

E/flutter ( 3556): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: 'package:provider/src/provider.dart': Failed assertion: line 242 pos 7: 'context.owner.debugBuilding ||
E/flutter ( 3556):           listen == false ||
E/flutter ( 3556):           debugIsInInheritedProviderUpdate': Tried to listen to a value exposed with provider, from outside of the widget tree.
E/flutter ( 3556):
E/flutter ( 3556): This is likely caused by an event handler (like a button's onPressed) that called
E/flutter ( 3556): Provider.of without passing `listen: false`.
E/flutter ( 3556):
E/flutter ( 3556): To fix, write:
E/flutter ( 3556): Provider.of<SettingProvider>(context, listen: false);
E/flutter ( 3556):
E/flutter ( 3556): It is unsupported because may pointlessly rebuild the widget associated to the
E/flutter ( 3556): event handler, when the widget tree doesn't care about the value.
E/flutter ( 3556):
E/flutter ( 3556): The context used was: BookList(dependencies: [_InheritedProviderScope<BookListProvider>], state: _BookListState#1008f)
E/flutter ( 3556):
E/flutter ( 3556): #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:46:39)
E/flutter ( 3556): #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:36:5)
E/flutter ( 3556): #2      Provider.of
package:provider/src/provider.dart:242
E/flutter ( 3556): #3      _BookListState.initState.<anonymous closure>
package:perpus/…/home/book-list.dart:24
E/flutter ( 3556): #4      new Future.delayed.<anonymous closure> (dart:async/future.dart:326:39)
E/flutter ( 3556): #5      _rootRun (dart:async/zone.dart:1182:47)
E/flutter ( 3556): #6      _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter ( 3556): #7      _CustomZone.runGuarded (dart:async/zone.dart:997:7)
E/flutter ( 3556): #8      _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1037:23)
E/flutter ( 3556): #9      _rootRun (dart:async/zone.dart:1190:13)
E/flutter ( 3556): #10     _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter ( 3556): #11     _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:1021:23)
E/flutter ( 3556): #12     Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
E/flutter ( 3556): #13     _Timer._runTimers (dart:isolate-patch/timer_impl.dart:397:19)
E/flutter ( 3556): #14     _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:428:5)
E/flutter ( 3556): #15     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
E/flutter ( 3556):

You have that error because you are not using the "listen: false" paramenter. Provider detects that is not being called from the widget tree (inside a "build" method).
Thanks for pointing it out @LucasRueda, looks like I did "listen: false" or context.read(), but was doing "hot reloat" instead "restart" on my VSCode. After got your message, I really try "restart" after applying "listen: false" to my provider. I confirm that it was really caused by "listen: true" or context.watch(). Will update my answer soon.
C
CopsOnRoad

Simple use Timer.run()

@override
void initState() {
  super.initState();
  Timer.run(() {
    // you have a valid context here
  });
}

@Kamlesh This question doesn't have to do anything with your other question. I personally think your question isn't reproducible on my end.
I thought you have any previous experience of my raised query that's why I asked you. Thanks dear.
@Kamlesh I understand but your wrote "your solution works" and then shared a link of a question; I thought this post is somewhat related to your new post but it wasn't. Anyway, I am not able to produce your new post. It would be better if you can share a minimal reproducible code. Thanks
A
Avijit Nagare

We can use Global key as:

class _ContactUsScreenState extends State<ContactUsScreen> {

    //Declare Global Key
      final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

    //key
    Widget build(BuildContext context) {
        return  Scaffold(
            key: _scaffoldKey,
            appBar: AppBar(
              title: Text('Contact Us'),
            ),
            body:
       }

    //use
      Future<void> send() async {
        final Email email = Email(
          body: _bodyController.text,
          subject: _subjectController.text,
          recipients: [_recipientController.text],
          attachmentPaths: attachments,
          isHTML: isHTML,
        );

        String platformResponse;

        try {
          await FlutterEmailSender.send(email);
          platformResponse = 'success';
        } catch (error) {
          platformResponse = error.toString();
        }

        if (!mounted) return;

        _scaffoldKey.currentState.showSnackBar(SnackBar(
          content: Text(platformResponse),
        ));
      }


}

P
Pedro Molina

This work using a key in your method build widget.

First create the key:

  final GlobalKey<NavigatorState> key =
  new GlobalKey<NavigatorState>();

After we bind with our widget:

  @override
  Widget build(BuildContext context) {
    return Scaffold(key:key);
  }

Finally we use the key calling .currentContext parameter.

    @override
      void initState() {
        super.initState();
        SchedulerBinding.instance.addPostFrameCallback((_) {
            // your method where use the context
            // Example navigate:
            Navigator.push(key.currentContext,"SiestaPage"); 
        });
   }

Happy coding.


关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now