Flutter 颤振:为什么表单(编辑页面)在提交并保存表单数据之后,在导航到其他地方之前重新加载
我正在编写一个教程,并尝试按原样编写代码。但是,在提交表单之后,就在导航之前,它尝试重新加载表单,而我没有这样的意图。我使用标签创建产品,并加载新页面编辑产品 main.dartFlutter 颤振:为什么表单(编辑页面)在提交并保存表单数据之后,在导航到其他地方之前重新加载,flutter,navigation,widget,Flutter,Navigation,Widget,我正在编写一个教程,并尝试按原样编写代码。但是,在提交表单之后,就在导航之前,它尝试重新加载表单,而我没有这样的意图。我使用标签创建产品,并加载新页面编辑产品 main.dart import 'package:scoped_model/scoped_model.dart'; // import 'package:flutter/rendering.dart'; import './pages/auth.dart'; import './pages/products_admin.dart';
import 'package:scoped_model/scoped_model.dart';
// import 'package:flutter/rendering.dart';
import './pages/auth.dart';
import './pages/products_admin.dart';
import './pages/products.dart';
import './pages/product.dart';
import './scoped-models/products.dart';
void main() {
// debugPaintSizeEnabled = true;
// debugPaintBaselinesEnabled = true;
// debugPaintPointersEnabled = true;
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return ScopedModel<ProductsModel>(
model: ProductsModel(),
child: MaterialApp(
// debugShowMaterialGrid: true,
theme: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.deepOrange,
accentColor: Colors.green,
buttonTheme: ButtonThemeData(
buttonColor: Colors.deepOrange,
textTheme: ButtonTextTheme.primary,
)),
// home: AuthPage(),
routes: {
'/': (BuildContext context) => AuthPage(),
'/products': (BuildContext context) => ProductsPage(),
'/admin': (BuildContext context) => ProductsAdminPage(),
},
onGenerateRoute: (RouteSettings settings) {
final List<String> pathElements = settings.name.split('/');
if (pathElements[0] != '') {
return null;
}
if (pathElements[1] == 'product') {
final int index = int.parse(pathElements[2]);
return MaterialPageRoute<bool>(
builder: (BuildContext context) {
return ProductPage(index);
},
);
}
return null;
},
onUnknownRoute: (RouteSettings settings) {
return MaterialPageRoute(
builder: (BuildContext context) => ProductsPage(),
);
},
),
);
}
}
import 'package:flutter/material.dart';
import '../widgets/navigation/products_admin.dart';
import './product_list.dart';
import 'product_edit.dart';
class ProductsAdminPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
drawer: NavigationProductsAdminPage(),
appBar: AppBar(
title: Text('Manage Products'),
bottom: TabBar(
tabs: [
Tab(
icon: Icon(Icons.create),
text: 'Create Product',
),
Tab(
icon: Icon(Icons.list),
text: 'My Products',
),
],
),
),
body: TabBarView(
children: [
ProductEditPage(),
ProductListPage(),
],
),
),
);
}
}
标签页产品\管理dart
import 'package:scoped_model/scoped_model.dart';
// import 'package:flutter/rendering.dart';
import './pages/auth.dart';
import './pages/products_admin.dart';
import './pages/products.dart';
import './pages/product.dart';
import './scoped-models/products.dart';
void main() {
// debugPaintSizeEnabled = true;
// debugPaintBaselinesEnabled = true;
// debugPaintPointersEnabled = true;
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return ScopedModel<ProductsModel>(
model: ProductsModel(),
child: MaterialApp(
// debugShowMaterialGrid: true,
theme: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.deepOrange,
accentColor: Colors.green,
buttonTheme: ButtonThemeData(
buttonColor: Colors.deepOrange,
textTheme: ButtonTextTheme.primary,
)),
// home: AuthPage(),
routes: {
'/': (BuildContext context) => AuthPage(),
'/products': (BuildContext context) => ProductsPage(),
'/admin': (BuildContext context) => ProductsAdminPage(),
},
onGenerateRoute: (RouteSettings settings) {
final List<String> pathElements = settings.name.split('/');
if (pathElements[0] != '') {
return null;
}
if (pathElements[1] == 'product') {
final int index = int.parse(pathElements[2]);
return MaterialPageRoute<bool>(
builder: (BuildContext context) {
return ProductPage(index);
},
);
}
return null;
},
onUnknownRoute: (RouteSettings settings) {
return MaterialPageRoute(
builder: (BuildContext context) => ProductsPage(),
);
},
),
);
}
}
import 'package:flutter/material.dart';
import '../widgets/navigation/products_admin.dart';
import './product_list.dart';
import 'product_edit.dart';
class ProductsAdminPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
drawer: NavigationProductsAdminPage(),
appBar: AppBar(
title: Text('Manage Products'),
bottom: TabBar(
tabs: [
Tab(
icon: Icon(Icons.create),
text: 'Create Product',
),
Tab(
icon: Icon(Icons.list),
text: 'My Products',
),
],
),
),
body: TabBarView(
children: [
ProductEditPage(),
ProductListPage(),
],
),
),
);
}
}
问题页面product\u edit.dart(提交后,导航前,尝试重新加载表单,并给出材料错误,当前列表索引为空
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import '../models/product.dart';
import '../scoped-models/products.dart';
class ProductEditPage extends StatefulWidget {
@override
_ProductEditPageState createState() => _ProductEditPageState();
}
// good habit to set your variables as private by prefixing with _, inside the state of a widget
class _ProductEditPageState extends State<ProductEditPage> {
final Map<String, dynamic> _formData = {
'title': null,
'description': null,
'price': null,
'image': 'assets/food.jpg',
};
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return ScopedModelDescendant<ProductsModel>(
builder: (BuildContext context, Widget child, ProductsModel model) {
Product selectedProduct = model.selectedProduct;
final Widget pageContent = _buildPageContent(context, selectedProduct);
print('[selected index] ' + model.selectedProductIndex.toString());
return model.selectedProductIndex == null
? pageContent
: Scaffold(
appBar: AppBar(
title: Text('Edit Product'),
),
body: pageContent,
);
},
);
}
Widget _buildTitleTextField(Product selectedProduct) {
print('[just before title text field]');
return TextFormField(
initialValue: selectedProduct == null ? '' : selectedProduct.title,
decoration: InputDecoration(
labelText: 'Product Title',
),
validator: (value) {
if (value.isEmpty || value.length < 5) {
return 'Title is required and should be 5+ characters.';
}
return null;
},
onSaved: (value) {
_formData['title'] = value;
},
);
}
Widget _buildDescriptionTextField(Product selectedProduct) {
print('[just before description text field]');
return TextFormField(
initialValue: selectedProduct == null ? '' : selectedProduct.description,
decoration: InputDecoration(
labelText: 'Product Description',
),
validator: (value) {
if (value.isEmpty || value.length < 10) {
return 'Description is required and should be 10+ characters.';
}
return null;
},
maxLines: 4,
onSaved: (value) {
_formData['description'] = value;
},
);
}
Widget _buildPriceTextField(Product selectedProduct) {
print('[just before price text field]');
return TextFormField(
initialValue:
selectedProduct == null ? '' : selectedProduct.price.toString(),
decoration: InputDecoration(
labelText: 'Product Price',
),
validator: (value) {
if (value.isEmpty ||
!RegExp(r'^(?:[1-9]\d*|0)?(?:\.\d+)?$').hasMatch(value)) {
return 'Price is required and should be a number.';
}
return null;
},
keyboardType: TextInputType.number,
onSaved: (String value) {
_formData['price'] = double.parse(value);
},
);
}
_buildSubmitButton() {
return ScopedModelDescendant<ProductsModel>(
builder: (BuildContext context, Widget child, ProductsModel model) {
return RaisedButton(
child: Text('Save'),
onPressed: () => _submitForm(model.addProduct, model.updateProduct,
model.selectedProductIndex),
);
},
);
}
void _submitForm(Function addProduct, Function updateProduct,
[int selectedProductIndex]) {
if (!_formKey.currentState.validate()) {
// this will force post-validation error messages to show and not submit further
return;
}
_formKey.currentState
.save(); // this will initiate the onSaved event of formfields
if (selectedProductIndex == null) {
addProduct(
Product(
title: _formData['title'],
description: _formData['description'],
price: _formData['price'],
image: _formData['image']),
);
} else {
updateProduct(
Product(
title: _formData['title'],
description: _formData['description'],
price: _formData['price'],
image: _formData['image']),
);
}
// pushReplacementNamed prevents it from going back by pressing BACK buttons
print('[So far so good] before navigation');
Navigator.pushReplacementNamed(context, '/products');
print('[So far so good] after navigation');
}
Widget _buildPageContent(BuildContext context, Product selectedProduct) {
final deviceWidth = MediaQuery.of(context).size.width;
final targetWidth = deviceWidth > 550.0 ? 500.0 : deviceWidth * 0.95;
final targetPadding = deviceWidth - targetWidth;
print('[just before gesture detector]');
return GestureDetector(
onTap: () {
// hide keyboard if container is tapped anywhere other than form
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
padding: EdgeInsets.all(10.0),
// Use ListView.builder only when the listCount is unknown and can grow
child: Form(
key: _formKey,
child: ListView(
// so the leftover space is distributed on left and right evenly
padding: EdgeInsets.symmetric(horizontal: targetPadding / 2),
children: [
_buildTitleTextField(selectedProduct),
_buildDescriptionTextField(selectedProduct),
_buildPriceTextField(selectedProduct),
SizedBox(
height: 20.0,
),
_buildSubmitButton(),
],
),
),
),
);
}
}
IMP代码无论如何都不会中断。只会给出错误,并根据需要导航到路由“/products”。此外,它不会在错误后执行/输出任何打印(“…”)行
更新我注意到它在保存/提交表单后再次进入ScopedModeldeCentent
ScopedModeldScentant的builder()(builder:(BuildContext context,Widget child,ProductsModel model model)
,而不是的build()方法
就在它上面一行。有人能解释一下这里发生了什么吗?这就是我解决问题的方法。我将手势检测器
包装在材料
小部件中
child: GestureDetector(
onTap: () {
// hide keyboard if container is tapped anywhere other than form
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
padding: EdgeInsets.all(10.0),
// Use ListView.builder only when the listCount is unknown and can grow
child: Form(
key: _formKey,
child: ListView(
// so the leftover space is distributed on left and right evenly
padding: EdgeInsets.symmetric(horizontal: targetPadding / 2),
children: [
_buildTitleTextField(selectedProduct),
_buildDescriptionTextField(selectedProduct),
_buildPriceTextField(selectedProduct),
SizedBox(
height: 20.0,
),
_buildSubmitButton(),
],
),
),
),
),
);
我如何在这里捕获异常?虽然修复了一个解决方法,但我不喜欢它。我已经用
材料
包装了手势检测器
(如异常消息中建议的那样).但是我不明白为什么它会两次呈现ScopedModelSchedent
builder..一次是在表单加载时,一次是在表单保存时。有人能指导我吗?
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 1.20.1, on Microsoft Windows [Version 10.0.18362.1082], locale en-US)
[√] Android toolchain - develop for Android devices (Android SDK version 30.0.1)
[√] Android Studio (version 4.0)
[√] VS Code (version 1.48.2)
[√] Connected device (1 available)
• No issues found!
child: GestureDetector(
onTap: () {
// hide keyboard if container is tapped anywhere other than form
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
padding: EdgeInsets.all(10.0),
// Use ListView.builder only when the listCount is unknown and can grow
child: Form(
key: _formKey,
child: ListView(
// so the leftover space is distributed on left and right evenly
padding: EdgeInsets.symmetric(horizontal: targetPadding / 2),
children: [
_buildTitleTextField(selectedProduct),
_buildDescriptionTextField(selectedProduct),
_buildPriceTextField(selectedProduct),
SizedBox(
height: 20.0,
),
_buildSubmitButton(),
],
),
),
),
),
);