Flatter Get Context in der initState-Methode

75

Ich bin mir nicht sicher, ob das initStatedie richtige Funktion dafür ist. Ich versuche zu überprüfen, wann die Seite gerendert wird, um einige Überprüfungen durchzuführen, und basierend darauf, dass sie ein öffnen AlertDialog, um bei Bedarf einige Einstellungen vorzunehmen.

Ich habe eine Seite, die einen Status hat. Die initStateFunktion sieht folgendermaßen aus:

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

Das _showConfigurationgefällt mir:

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);
}

Wenn es eine bessere Möglichkeit gibt, diese Überprüfungen durchzuführen und bei Bedarf das Modal aufzurufen, weisen Sie mich bitte in die richtige Richtung. Ich habe nach einer onStateoder onRenderFunktion gesucht oder nach einem Rückruf, den ich der buildbeim Rendern aufzurufenden Funktion zuweisen könnte, aber nicht in der Lage, einen zu finden.


Bearbeiten: Es scheint hier ein ähnliches Problem zu haben: Flattern Weiterleiten auf eine Seite in initState

Wawa
quelle

Antworten:

105

Auf den Kontext der Mitgliedsvariablen kann während zugegriffen werden initState, er kann jedoch nicht für alle verwendet werden. Dies ist aus dem Flattern zur initStateDokumentation:

Sie können [BuildContext.inheritFromWidgetOfExactType]diese Methode nicht verwenden . Allerdings [didChangeDependencies]wird genannt nach dieser Methode sofort und [BuildContext.inheritFromWidgetOfExactType] kann dort verwendet werden.

Sie können Ihre Initialisierungslogik auf verschieben didChangeDependencies, dies ist jedoch möglicherweise nicht genau das, was Sie möchten, da didChangeDependenciesdies im Lebenszyklus des Widgets mehrmals aufgerufen werden kann.

Wenn Sie stattdessen einen asynchronen Anruf tätigen, der Ihren Anruf delegiert, bis das Widget initialisiert wurde, können Sie den Kontext wie beabsichtigt verwenden.

Ein einfacher Weg, dies zu tun, besteht darin, eine Zukunft zu nutzen.

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

Eine andere Möglichkeit, die möglicherweise „korrekter“ ist, besteht darin, den Flutter-Scheduler zu verwenden, um einen Post-Frame-Rückruf hinzuzufügen:

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

Und zum Schluss noch ein kleiner Trick, den ich gerne mache, um asynchrone Aufrufe in der initState-Funktion zu verwenden:

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

Hier ist ein vollständig ausgearbeitetes Beispiel mit dem einfachen 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,
            ),
          ],
        ),
      ),
    );
  }
}

Mit mehr Kontext aus dem OP in den Kommentaren kann ich eine etwas bessere Lösung für ihr spezifisches Problem geben. Abhängig von der App möchten Sie möglicherweise tatsächlich eine Entscheidung treffen, die darauf basiert, welche Seite angezeigt werden soll, je nachdem, ob die App zum ersten Mal geöffnet wird, dh homeauf etwas anderes eingestellt ist. Und Dialoge sind nicht unbedingt das beste UI-Element auf Mobilgeräten. Es ist möglicherweise besser, eine vollständige Seite mit den Einstellungen anzuzeigen, die hinzugefügt werden müssen, sowie eine Schaltfläche "Weiter".

rmtmckenzie
quelle
Die betreffende Seite ist die erste Seite und wird durch MaterialAppdie homeEigenschaft von aufgerufen . Also mache ich dort nicht wirklich einen Push. Können Sie mir ein Beispiel geben, wie es in der buildFunktion geht? Derzeit gibt es nur eine neue Scaffoldmit ein appBar, drawer, bodyundfloatingActionButton
wawa
Eine Option, die mir in den Sinn kommt, wäre, alle im Modal verwendeten Widgets in einer Funktion auszulagern und auf den Homepages Scaffoldanzuzeigen (als keine Modal), wenn die Prüfung fehlschlägt und ansonsten nur die Homepage anzuzeigen. Das fühlt sich für mich jedoch irgendwie hackig an.
Wawa
4
das ist sehr schlecht. Der erste Ort, an dem Sie auf den Kontext zugreifen können, ist die didChangeDependenciesMethode
Razvan Cristian Lung
1
@wawa - Ich habe das Beispiel ein wenig korrigiert. Ich habe tatsächlich vergessen, dass dies contexttatsächlich eine Mitgliedsvariable des Zustands = D ist. Damit Sie den Booleschen Future.delayedWert nicht benötigen, können Sie ihn einfach direkt in Ihrem Initstate verwenden. Dies ist jedoch weiterhin erforderlich. Ohne diese Fehler werden beim Versuch, während eines Pushs zu pushen, Bestätigungsfehler angezeigt.
rmtmckenzie
2
in meinem Fall heißt es 'undefined name context' in initState
temirbek
38

Einwickeln mit Future

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

Wir können den globalen Schlüssel verwenden als:

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),
        ));
      }


}
Avijit Nagare
quelle
0

Einfache Verwendung Timer.run()

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