web-dev-qa-db-fra.com

Flutter Globalkey State actuel est null

Ce que j'essaie essentiellement de faire (et je l'ai fait, les détails de l'avant) sont de créer un écran avec plusieurs formes dont le contenu est modifié à l'aide d'un bouton (suivant). Donc, j'ai 3 formulaires, quand j'appuie ensuite, je sauvegardez le premier formulaire et je passe à l'autre, ce que je faisais jusqu'à présent fonctionne quand je mets ma classe à Main.Dart. Par exemple, voici mon contenu principal.DART.

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Entries'),
    );
  }
}
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  static final _scafoldKey = new GlobalKey<ScaffoldState>();
  final firstForm = new FirstForm(title: "First Form");
  final secondForm = new SecondForm(title: "First Form",);
  final thirdForm = new ThirdForm(title: "First Form");
  final Map<int,Widget> forms = {};
  final Map<String,Map> allValues = new Map();

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

class _MyHomePageState extends State<MyHomePage> {

  Widget currentForm;
  final Firestore _fireStore = Firestore.instance;
  Map<String,Map> thirdFormNested = new Map();
  int thirdFormPos = 1;

  @override
  void initState() {
    widget.forms[0] = widget.firstForm;
    widget.forms[1] = widget.secondForm;
    widget.forms[2] = widget.thirdForm;
    currentForm = widget.forms[0];
    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: MyHomePage._scafoldKey,
      appBar: AppBar(
        title: Text(widget.title),
        leading: IconButton(
          icon: Icon(Icons.arrow_back),
          onPressed: () => Navigator.of(context).pop(),
        ),
      ),
      body: SingleChildScrollView(
        child: currentForm,
      ),
      floatingActionButton: Stack(
        children: <Widget>[
          Align(
            alignment: Alignment(0.4, 1),
            child: SizedBox(
              width: 75,
              child: FloatingActionButton(
                shape: RoundedRectangleBorder(),
                child: Text("NEXT"),
                onPressed: () {
                  GlobalKey<FormState> key;
                  if(currentForm is FirstForm) {
                    key= widget.firstForm.formKey;
                    if(key.currentState.validate()){
                      key.currentState.save();
                      widget.allValues["First Form"] = widget.firstForm.values;
                      setState(() {
                        currentForm = widget.forms[1];
                      });
                    }
                  }
                  else if(currentForm is SecondForm) {
                    setState(() {
                      currentForm = widget.forms[2];
                    });
                  }
                  else if (currentForm is ThirdForm) {
                    key = widget.thirdForm.formKey;
                    if(key.currentState.validate()){
                      key.currentState.save();
                      var tmp = widget.thirdForm.values;
                      widget.thirdForm.values = <String,String>{};
                      thirdFormNested.addAll({"Bit $thirdFormPos":tmp});
                      key.currentState.reset();
                      widget.thirdForm.build(context);
                      thirdFormPos++;
                    }
                  }
                },
              ),
            ),
          ),
          Align(
            alignment: Alignment(1, 1),
            child: SizedBox(
              width: 75,
              child: FloatingActionButton(
                shape: RoundedRectangleBorder(),
                child: Text("SUBMIT"),
                onPressed: () async {
                  GlobalKey<FormState> key;
                  if(currentForm is FirstForm) {
                    key= widget.firstForm.formKey;
                    if(key.currentState.validate()) {
                      key.currentState.save();
                      await _fireStore.collection('form').document(Uuid().v1()).setData(widget.firstForm.values);
                    }
                  }else if(currentForm is SecondForm) {
                    await _fireStore.collection('form').document(Uuid().v1()).setData(widget.firstForm.values);
                  }else if(currentForm is ThirdForm) {
                    key= widget.thirdForm.formKey;
                    if(key.currentState.validate()){
                      key.currentState.save();
                      if(thirdFormNested.length == 0) {
                        var tmp = widget.thirdForm.values;
                        widget.thirdForm.values = <String,String>{};
                        thirdFormNested.addAll({"Bit 1":tmp});
                      }else {
                        var tmp = widget.thirdForm.values;
                        widget.thirdForm.values = <String,String>{};
                        thirdFormNested.addAll({"Bit $thirdFormPos":tmp});
                      }
                      widget.allValues["Third Form"] = thirdFormNested;
                      await _fireStore.collection('form').document(Uuid().v1()).setData(widget.allValues);
                    }
                  }
                },
              ),
            ),
          ),
        ],
      )
    );
  }
}

Et c'est la première forme (d'autres formes sont les mêmes, les intrants sont la seule différence):

class FirstForm extends StatelessWidget {
  FirstForm({Key key, this.title}) : super(key: key);
  final String title;
  final GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
  Map<String,String> _values = new Map();

  get formKey => _formKey;
  get values => _values;
  set values(v) => _values = v;

  @override
  Widget build(BuildContext context) {
    print("FORM KEY: ${_formKey.currentState}");
    _values['Spud Date'] = _formatDate(DateTime.now().toString());
    _values['TD Date'] = _formatDate(DateTime.now().toString());
    return Form(
      key: _formKey,
      child: Column(
        children: <Widget>[
          ListTile(
            leading: Text(
              "API#",
              style: TextStyle(
                fontSize: 18,
              ),
            ),
            title: TextFormField(
              decoration: InputDecoration(
                hintText: "<api>"
              ),
              validator: (value) {
                if(value.isEmpty) {
                  return "Please enter API value!";
                }
                return null;
              },
              onSaved: (value) {
                _values['API#'] = value;
              },
            ),
          ..... Other inputs
          Padding(
            padding: EdgeInsets.symmetric(vertical: 36.0),
          )
        ],
      ),
    );
  }

  String _formatDate(String date) {
    DateTime d = DateTime.parse(date);
    return DateFormat("dd-MM-yyyy").format(d);
  }
}

Donc, chaque formulaire a un formidiste que je passe au formulaire (clé: ..). J'instire chaque forme dans la classe MyHomePage et j'utilise widget.firstForm.formKey Pour récupérer la clé de formulaire pour l'utiliser pour la validation.

jusqu'à présent, cela fonctionne parfaitement. Cependant, lorsque j'ai essayé d'adapter mon travail dans une application existante. Cela ne signifie plus.

Dans l'application existante, j'ai un tiroir. Un élément du tiroir s'appelle des formulaires, ce qui me emmène au "myhomepage" que j'ai renommé de se former maintenant. Donc, le code est toujours le même, la seule chose que j'ai supprimée est la classe RunApp () et MyApp qui se trouvent dans un fichier différent maintenant. Ceci est le coeur du tiroir:

ListTile(
                title: new Text('Forms'),
                onTap: () {
                  Navigator.Push(
                    context,
                    MaterialPageRoute(
                      maintainState: true,
                      builder: (context) => Forms(title: "Forms")
                    ),
                  );
                },
              ),

Maintenant, en utilisant cet approche. Lorsque vous cliquez sur SUIVANT dans l'écran Formulaires, je reçois l'erreur:

valider () appelé sur null

Ainsi, après quelques heures, j'ai compris que c'est en fait la Globalkey de mes formes qui renvoie un état nul. La question est pourquoi? Et comment puis-je résoudre ce problème.

TL; DR: J'ai 3 formes avec leurs globalités respectives. Les touches sont utilisées pour valider les formulaires dans un autre widget, qui enveloppe ces formulaires. La façon dont j'ai approché leur création fonctionne lorsque j'ai le widget Wrapper dans la classe MyHomePage par défaut dans Main.Dart. Cependant, lors de la déplacement de la même classe d'emballage dans un fichier externe et de la renommer la classe à un nom plus approprié, les états GlobalKeys sont maintenant NULL.

EDIT: Un comportement extra gênant que j'ai remarqué, est que lorsque je remplisse le formulaire (Firstform) et appuyez sur Suivant pour la première fois lorsque j'exécute l'application, je reçois l'erreur NULL. Cependant, lors de la première exécution de l'application, si j'appuyais sur Suivant sans dépôt du formulaire, le validateur fonctionne et cela me montre les erreurs pour remplir les entrées.

11
Amine

Si quelqu'un a le même problème. Je n'ai pas trouvé de solution à cela ou au moins une explication sur la raison pour laquelle cela se produit. Cependant, je suis passé à partir de ma propre approche pour utiliser un widget héréditaire pour transporter mes données de formulaire tout en conservant la validation à l'intérieur de chaque formulaire séparé (je suis validation à l'intérieur des formes de classe wrapper, je valide maintenant chaque formulaire dans sa propre classe).

2
Amine

J'ai eu un problème très similaire, j'utilisais une liste slivivérimated avec un GlobalKey<SliverAnimatedListState> La première fois que je frappe l'arborescence du widget entier, le widget dessin est parfait. C'était parce que avant que les données ne soient venues, je dessine une liste vide et cette action a initialisé le GlobalKey.

Maintenant, la deuxième fois que j'ai eu l'actuel constitut NULL, j'ai déjà eu les données, mais le widget n'était pas visible.

Je déteste la résolution de mon problème de cette façon, mais ... j'introduit un petit retard de 10ms

Future.delayed(
                  Duration(milliseconds: 10), () => _loadItems(state.meals));
            }

J'ai essayé avec 1 ms et travaille aussi.

Mon code de widget entier juste au cas où il peut vous aider.

return BlocBuilder(
        condition: (previous, current) =>
                       return previous != current
        ,
        bloc: sl.get<DailyactionitemsBloc>(),
        builder: (context, state) {
         var list = SliverAnimatedList(
            itemBuilder: _buildItem,
            initialItemCount: 0,
            key: _listKey,
          );
          if (state is DailyActionFetched) {

              //BUG I have research this thing a lot
              // https://stackoverflow.com/questions/57209096/flutter-globalkey-current-state-is-null
              Future.delayed(
                  Duration(milliseconds: 10), () => _loadItems(state.meals));

          }
          return list;
        });
1
Alejandro Serret

La solution est assez simple. Votre clé ne sera d'initialiser que lorsque votre construction sera exécutée au moins une fois. Vous devez donc mettre votre code à l'intérieur de l'inteste comme celui-ci.

 @override
    void initState()   {
    Future.delayed(const Duration(milliseconds: 16), () {
    // Anything that use your key will go here.
    });
    super.initState();
    }
0
wVV