如何防止 TextFormField 重定向到上一个屏幕?

How to prevent TextFormField redirecting to previous screen?

我正在尝试创建表单。 我设法在其中创建了每个小部件,但每次我尝试打开 TextFormField 时,我都会毫无错误地重定向回我的 MainMenuScreen。

我正在使用 BLoC 和路由。我认为这个问题可能与使用命名路由有关。 在更改为命名路由之前未发现问题

MainMenuScreen 片段:

           CategoryCard(
              categoryName: 'Main dishes',
              assetPath: 'assets/images/main_dish.png',
              onPressed: () => Navigator.pushReplacement(context,
                  MaterialPageRoute(builder: (BuildContext context) {
                return BlocProvider.value(
                  value: BlocProvider.of<RecipesBloc>(context)
                    ..add(LoadRecipesEvent())
                    ..category = 'main_dish',
                  child: RecipesScreen(),
                );
              })),
            ),

我从 MainMenuScreen 重定向到 RecipesScreen

重定向到 RecipeCreateForm 的 RecipesScreen 片段:

      floatingActionButton: FloatingActionButton(
        onPressed: () => Navigator.pushReplacement(
          context,
          MaterialPageRoute(builder: (BuildContext context) {
            return RecipeCreateForm();
          }),
        ),

然后我重定向到我正在使用 TextFormFields 的 RecipeCreateForm。 每当我尝试使用 TextFormField 时,我都会被重定向回 MainMenuScreen。

class RecipeCreateForm extends StatefulWidget {
  @override
  _RecipeCreateFormState createState() => _RecipeCreateFormState();
}

class _RecipeCreateFormState extends State<RecipeCreateForm> {
  final _recipeNameController = TextEditingController();
  final _imageUrl = TextEditingController();
  String? _difficultyValue;
  late int _ingredientsQuantity;
  late int _preparationStepsQuantity;
  late List<Ingredient> _ingredientsValues;
  late List<PreparationStep> _preparationStepsValues;
  late double _preparationTime;
  String? _portions;

  @override
  void initState() {
    _ingredientsQuantity = 1;
    _preparationStepsQuantity = 1;
    _ingredientsValues = [];
    _preparationStepsValues = [];
    _preparationTime = 0;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: true,
      appBar: AppBar(
        leading: IconButton(
          onPressed: () {
            Navigator.of(context).pop();
          },
          icon: Icon(
            Icons.arrow_back,
            color: Colors.white,
          ),
        ),
      ),
      body: Scrollbar(
        thickness: 10,
        hoverThickness: 2,
        child: SingleChildScrollView(
          child: Container(
            color: Colors.lightGreen.shade100,
            child: Column(
              children: <Widget>[
                Padding(
                  padding: EdgeInsets.only(top: 15),
                ),
                Text(
                  'Recipe name',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
                ),
                TextFormField(
                  style: TextStyle(
                      color: Colors.black,
                      fontSize: 18,
                      fontStyle: FontStyle.italic),
                  controller: _recipeNameController,
                ),
                Padding(
                  padding: EdgeInsets.only(top: 15),
                ),
                Text(
                  'Image',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
                ),
                TextFormField(
                  style: TextStyle(
                      color: Colors.black,
                      fontSize: 18,
                      fontStyle: FontStyle.italic),
                  controller: _imageUrl,
                ),
                Padding(
                  padding: EdgeInsets.only(top: 15),
                ),
                Text(
                  'Difficulty',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
                ),
                DropdownButton(
                  hint: _difficultyValue == null
                      ? Text(
                          'Select difficulty',
                          style: TextStyle(
                              color: Colors.black,
                              fontSize: 18,
                              fontStyle: FontStyle.italic),
                        )
                      : Text(
                          _difficultyValue!,
                          style: TextStyle(
                              color: Colors.black,
                              fontSize: 18,
                              fontStyle: FontStyle.italic),
                        ),
                  isExpanded: true,
                  iconSize: 30.0,
                  style: TextStyle(
                      color: Colors.black,
                      fontSize: 18,
                      fontStyle: FontStyle.italic),
                  items: ['Easy', 'Medium', 'Hard'].map(
                    (val) {
                      return DropdownMenuItem<String>(
                        value: val,
                        child: Text(val),
                      );
                    },
                  ).toList(),
                  onChanged: (val) {
                    setState(
                      () {
                        _difficultyValue = val as String;
                      },
                    );
                  },
                ),
                Padding(
                  padding: EdgeInsets.only(top: 15),
                ),
                Text(
                  'Preparation time',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
                ),
                Slider(
                  value: _preparationTime,
                  onChanged: (newPreparationTime) {
                    setState(() => _preparationTime = newPreparationTime);
                  },
                  label: _preparationTime.toStringAsFixed(0),
                  min: 0,
                  max: 360,
                  divisions: 24,
                ),
                Padding(
                  padding: EdgeInsets.only(top: 15),
                ),
                Text(
                  'Ingredients',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
                ),
                SizedBox(
                  height: 175,
                  child: Scrollbar(
                    child: ListView.builder(
                        itemCount: _ingredientsQuantity,
                        itemBuilder: (context, index) {
                          return _ingredientRow(index);
                        }),
                  ),
                ),
                Row(
                  children: [
                    IconButton(
                        icon: Icon(Icons.add),
                        onPressed: () async {
                          setState(() {
                            _ingredientsQuantity++;
                          });
                        }),
                    IconButton(
                        icon: Icon(Icons.delete),
                        onPressed: () async {
                          setState(() {
                            _ingredientsQuantity = 1;
                            _ingredientsValues.clear();
                          });
                        })
                  ],
                ),
                Padding(
                  padding: EdgeInsets.only(top: 15),
                ),
                Text(
                  'Preparation steps',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
                ),
                Scrollbar(
                  child: SizedBox(
                    height: 100,
                    child: ListView.builder(
                        shrinkWrap: true,
                        itemCount: _preparationStepsQuantity,
                        itemBuilder: (context, index) {
                          return _preparationStepRow(index);
                        }),
                  ),
                ),
                Row(
                  children: [
                    IconButton(
                        icon: Icon(Icons.add),
                        onPressed: () async {
                          setState(() {
                            _preparationStepsQuantity++;
                          });
                        }),
                    IconButton(
                        icon: Icon(Icons.delete),
                        onPressed: () async {
                          setState(() {
                            _preparationStepsQuantity = 1;
                            _preparationStepsValues.clear();
                          });
                        }),
                  ],
                ),
                Padding(
                  padding: EdgeInsets.only(top: 15),
                ),
                Text(
                  'Portions',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
                ),
                DropdownButton(
                  hint: _portions == null
                      ? Text(
                          'Select number of portions',
                          style: TextStyle(
                              color: Colors.black,
                              fontSize: 18,
                              fontStyle: FontStyle.italic),
                        )
                      : Text(
                          _portions!,
                          style: TextStyle(
                              color: Colors.black,
                              fontSize: 18,
                              fontStyle: FontStyle.italic),
                        ),
                  isExpanded: true,
                  iconSize: 30.0,
                  style: TextStyle(
                      color: Colors.black,
                      fontSize: 18,
                      fontStyle: FontStyle.italic),
                  items: ['1', '2', '3', '4', '5', '6', '7'].map(
                    (val) {
                      return DropdownMenuItem<String>(
                        value: val,
                        child: Text(val),
                      );
                    },
                  ).toList(),
                  onChanged: (val) {
                    setState(
                      () {
                        _portions = val as String;
                      },
                    );
                  },
                ),
                ElevatedButton(
                  onPressed: () {
                    BlocProvider.of<RecipesBloc>(context).add(
                      AddRecipeEvent(
                        Recipe(
                          name: _recipeNameController.text,
                          image:
                              'https://www.thespruceeats.com/thmb/dA8o8EZpjJyeocYZNpzfknoKh2s=/4351x3263/smart/filters:no_upscale()/baked-stuffed-potatoes-482217-hero-01-850f2d87fe80403f923e140dbf5f1bf3.jpg',
                          ingredients: _ingredientsValues,
                          difficulty: _difficultyValue,
                          preparationTime: _preparationTime,
                          preparationSteps: _preparationStepsValues,
                          type: BlocProvider.of<RecipesBloc>(context)
                              .category
                              .toString(),
                          portions: _portions,
                        ),
                      ),
                    );
                    Navigator.of(context).pop();
                  },
                  child: Text('Submit'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  _ingredientRow(int key) {
    return IntrinsicHeight(
      child: Row(
        children: [
          Padding(padding: EdgeInsets.only(left: 10)),
          SizedBox(
            width: 225,
            child: TextFormField(
              maxLength: 35,
              onChanged: (val) {
                setState(() {
                  _onIngredientUpdate(key,name: val);
                });
              },
            ),
          ),
          VerticalDivider(
            width: 20,
            thickness: 1,
            color: Colors.black,
            indent: 30,
            endIndent: 10,
          ),
          SizedBox(
            width: 55,
            child: TextFormField(
              maxLength: 7,
              initialValue: '0',
              onChanged: (val) {
                setState(() {
                  _onIngredientUpdate(key, quantity: val);
                });
              },
            ),
          ),
          Padding(padding: EdgeInsets.only(left: 10)),
          DropdownButton(
            hint: Text('pcs'),
            items: ['pcs', 'ml', 'g'].map(
              (val) {
                return DropdownMenuItem<String>(
                  value: val,
                  child: Text(val),
                );
              },
            ).toList(),
            onChanged: (val) {
              setState(() {
                _onIngredientUpdate(key,measurement: val.toString());
              });
            },
          )
        ],
      ),
    );
  }

  _onIngredientUpdate(int key, {String? name, String? measurement, String? quantity}) {
    int foundKey = -1;
    _ingredientsValues.forEach((element) {
      if (element.id.contains(key.toString())) {
        foundKey = key;
      }
    });

    if (-1 != foundKey) {
      _ingredientsValues.removeWhere((map) {
        return map.id == foundKey.toString();
      });
    }
    Map<String, dynamic> json = {'id': key, 'name': name, 'measurement': measurement, 'quantity':quantity};
    _ingredientsValues.add(json as Ingredient);
  }

  _preparationStepRow(int key) {
    return IntrinsicHeight(
      child: Row(
        children: [
          Padding(padding: EdgeInsets.only(left: 10)),
          SizedBox(
            width: 225,
            height: 50,
            child: TextFormField(
              maxLength: 35,
              onChanged: (val) => {
                _onPreparationUpdate(key,val)
              },
            ),
          ),
        ],
      ),
    );
  }

  _onPreparationUpdate(int key, String val) {
    int foundKey = -1;
    _preparationStepsValues.forEach((element) {
      if (element.id.contains(key.toString())) {
        foundKey = key;
      }
    });

    if (-1 != foundKey) {
      _preparationStepsValues.removeWhere((map) {
        return map.id == foundKey.toString();
      });
    }
    Map<String, dynamic> json = {'id': key, 'step': val};
    _preparationStepsValues.add(json as PreparationStep);
  }
}

发布GIF:

编辑: 问题与形式无关。我只用一个字段替换了整个表单,没有任何逻辑,问题仍然存在。 估计跟命名路由有关。

正如我所想,问题与命名路由的使用有关。 我设法通过使用 Future.delayed 和 pushNamedAndRemoveUntil

绕过了这个问题

在 main_menu_screen 中,我创建了稍后用于重定向到类别的方法。

void redirectToCategory(BuildContext context, String categoryName) {
Future.delayed(Duration.zero, () {
  Navigator.pushNamedAndRemoveUntil(
    context,
    '/recipeScreen',
    (_) => false,
    arguments: BlocProvider.value(
      value: BlocProvider.of<RecipesBloc>(context)
        ..add(LoadRecipesEvent())
        ..category = categoryName,
      child: RecipesScreen(),
    ),
  );
});