159 votes

Flutter récupère le contexte dans la méthode initState

Je ne suis pas sûr que le initState est la bonne fonction pour cela. Ce que j'essaie de faire, c'est de vérifier quand la page est rendue, d'effectuer certaines vérifications et, en fonction de celles-ci, d'ouvrir une fonction AlertDialog pour effectuer certains réglages si nécessaire.

J'ai une page qui a un état. Il s'agit de initState ressemble à ceci :

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

Le site _showConfiguration comme ça :

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

S'il existe une meilleure façon d'effectuer ces contrôles et, si nécessaire, d'appeler la modale, veuillez m'indiquer la direction à suivre. onState ou onRender ou un callback que je pourrais assigner à la fonction build à appeler lors du rendu, mais je n'ai pas réussi à en trouver une.


Edit : Il semble qu'ici, ils ont eu un problème similaire : Redirection de Flutter vers une page sur initState

188voto

rmtmckenzie Points 10854

Le contexte de la variable membre est accessible pendant initState mais ne peut pas être utilisé pour tout. Ceci provient du flutter pour initState documentation :

Vous ne pouvez pas utiliser [BuildContext.inheritFromWidgetOfExactType] de cette méthode. Cependant, [didChangeDependencies] sera appelé immédiatement après cette méthode, et [BuildContext.inheritFromWidgetOfExactType] peut y être utilisé.

Vous pourriez déplacer votre logique d'initialisation vers didChangeDependencies mais ce n'est peut-être pas exactement ce que vous voulez. didChangeDependencies peut être appelé plusieurs fois dans le cycle de vie du widget.

Si, au contraire, vous effectuez un appel asynchrone qui délègue votre appel jusqu'à ce que le widget ait été initialisé, vous pouvez alors utiliser le contexte comme vous le souhaitez.

Un moyen simple de le faire est d'utiliser un futur.

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

Une autre méthode, peut-être plus "correcte", consiste à utiliser le planificateur de Flutter pour ajouter un rappel post-frame :

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

Et enfin, voici une petite astuce que j'aime faire pour utiliser des appels asynchrones dans la fonction initState :

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

Voici un exemple complet utilisant le 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,
            ),
          ],
        ),
      ),
    );
  }
}

Avec plus de contexte du PO fourni dans les commentaires, je peux donner une solution légèrement meilleure à leur problème spécifique. En fonction de l'application, vous pouvez décider de la page à afficher selon qu'il s'agit de la première ouverture de l'application. home à quelque chose de différent. De plus, les boîtes de dialogue ne sont pas nécessairement le meilleur élément d'interface utilisateur sur mobile ; il peut être préférable d'afficher une page complète avec les paramètres qu'ils doivent ajouter et un bouton suivant.

0 votes

La page en question est la première page et cela a appelé à travers MaterialApp 's home propriété. Je ne peux donc pas vraiment faire un push à cet endroit. Pourriez-vous me donner un exemple de la manière de le faire dans la propriété build ? Actuellement, elle renvoie simplement un nouveau Scaffold avec un appBar , drawer , body et floatingActionButton

0 votes

Une option qui me vient à l'esprit serait d'externaliser tous les widgets utilisés dans la modale dans une fonction et dans les pages d'accueil. Scaffold les afficher (en tant que modal none), si la vérification échoue et sinon afficher simplement la page d'accueil. Cela me semble toutefois un peu compliqué.

4 votes

C'est très mauvais. le premier endroit où l'on peut accéder au contexte est le didChangeDependencies méthode

63voto

Pablo Cegarra Points 2374

Emballage avec Future

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

0 votes

Qu'est-ce que le magasin ? Il ne peut pas être importé.

0 votes

Il n'est pas nécessaire d'utiliser Store, qui est ma propre classe d'état, il suffit d'utiliser future.delayed dans initState.

24voto

Bayu Points 620

La plupart des exemples de initState() dans ce fil de discussion peut fonctionner pour des choses "UI" telles que "Dialog" qui est le cas dans la question de base de ce fil de discussion.

Mais malheureusement, cela ne fonctionne pas pour moi lorsque je l'applique pour obtenir context pour " Prestataire ".

Donc, je choisis didChangeDependencies() approche. Comme indiqué dans la réponse acceptée, elle comporte une réserve, à savoir qu'elle peut être appelée plusieurs fois dans le cycle de vie du widget. Cependant, il est assez facile de la gérer. Il suffit d'utiliser une seule variable d'aide, à savoir bool pour éviter les appels multiples à l'intérieur didChangeDependencies() . Voici un exemple d'utilisation de _BookListState classe avec la variable _isInitialized comme le principal "frein" aux "appels multiples" :

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

...
}

Voici les journaux d'erreurs lorsque j'essaie de faire initState() approche :

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):

1 votes

Vous avez cette erreur parce que vous n'utilisez pas le paramètre "listen : false". Le fournisseur détecte que ce paramètre n'est pas appelé depuis l'arbre des widgets (à l'intérieur d'une méthode "build").

1 votes

Merci de l'avoir signalé @LucasRueda, il semble que j'ai fait "listen : false" ou "listen : false". context.read() mais je faisais "hot reloat" au lieu de "restart" sur mon VSCode. Après avoir reçu votre message, j'ai vraiment essayé de "redémarrer" après avoir appliqué "listen : false" à mon fournisseur. Je confirme que le problème a été causé par "listen : true" ou "listen : false". context.watch() . Je mettrai bientôt ma réponse à jour.

4voto

Muhammad Adil Points 121

Vous devez utiliser le paquet AfterLayout, il suffit de vérifier son exemple et il fournit une belle solution à ce problème.

https://pub.dartlang.org/packages/after_layout

4voto

CopsOnRoad Points 4705

Utilisation simple Timer.run()

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

0 votes

@Kamlesh Cette question n'a rien à voir avec votre autre question. Je pense personnellement que votre question n'est pas reproductible de mon côté.

0 votes

Je pensais que vous aviez déjà une expérience de ma requête, c'est pourquoi je vous ai demandé. Merci beaucoup.

0 votes

@Kamlesh Je comprends mais vous avez écrit "votre solution fonctionne" et ensuite vous avez partagé un lien vers une question ; je pensais que ce message était lié à votre nouveau message mais ce n'était pas le cas. Quoi qu'il en soit, je ne suis pas en mesure de produire votre nouveau message. Ce serait mieux si vous pouviez partager un code minimal reproductible. Merci

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X