[Solved] Flutter get context in initState method

Question

Asked by wawa on March 23, 2018 (source).

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

Answer

Question answered by rmtmckenzie (source).

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.

DART FLUTTER
SHARE: