问题描述
应用说明
我的应用程序由 3 个主要页面组成,可在我的底部导航栏中访问。 当我在菜单之间导航时,我希望在每次调用时初始化我的页面。
在带有表单的Menu1中,我可以提交表单。这将打开一个带有 pushNamed 的页面,我们将其称为 SubMenu1。此页面显示基于表单数据计算的结果。在此页面上,我可以使用 pop 返回并且什么也不做,或者我有一个按钮,必须更新我的表单的某些值。
问题
如何从SubMenu1更新Menu1表单的值? 实际上,当您推送一个页面时,它最终处于MaterialApp 级别,因此无法从Menu1 访问我的通知程序。解决方案是通过 MaterialApp 上面的 Notifier 然后。 但是突然间,我的表单不再在每次调用 Menu1 时初始化,而是在应用程序启动时只初始化一次...
代码
在提供的代码中,我将通知放在 MaterialApp 上方。 因此,我可以访问 SubMenu1 中的通知程序,并且可以修改 Menu1 中字段的值,但是当我从导航栏中更改页面并返回值仍然存在,页面尚未初始化。
AppScreen:
class AppScreen extends StatelessWidget {
const AppScreen({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<LicenseNotifier>(
create: (_) => LicenseNotifier(),child: ChangeNotifierProvider<BottomNavigationBarNotifier>( // Here my navigation
create: (BuildContext context) => BottomNavigationBarNotifier(),child: ChangeNotifierProvider<Menu1Notifier>( // Here my form Notifier for Menu 1
create: (BuildContext context) => Menu1Notifier(),child: MaterialApp(
title: AppConfig.APPLICATION_NAME,debugShowCheckedModeBanner: false,theme: AppTheme().data,initialRoute: AppRoutes.HOME,onGenerateRoute: RoutesClass.generate,),)
)
);
}
}
我的BottomNavigationBarNotifier :
class BottomNavigationBarNotifier with ChangeNotifier {
int currentIndex = AppConfig.NAVIGATION_DEFAULT_INDEX;
BottomNavigationBarNotifier();
Future<void> navigationScreenIndex({int index}) async{
currentIndex = index;
notifyListeners();
}
Widget loadScreenWithNavigation()
{
switch (currentIndex)
{
case 0:
return Menu1Screen(title: 'Menu 1');
break;
case 1:
return Menu2Screen(title: 'Menu 2');
break;
case 2:
return Menu3Screen(title: 'Menu 3');
break;
default:
return Menu1Screen(title: 'Menu 1');
break;
}
}
}
我的 Menu1Screen 通知程序:
class Menu1Notifier extends FormNotifier {
TextEditingController controllerTest;
Menu1Notifier (){
_initialise();
}
Future _initialise() async{
controllerTest = TextEditingController();
notifyListeners();
}
}
解决方法
我不确定我从上面的代码片段中完全理解你在做什么,但我想我理解要求:
- Menu1 有状态
- SubMenu1 应该能够读取和更新 Menu1 状态
- Menu1 调用 SubMenu1 作为“子”屏幕,没有其他路径到 SubMenu1
处理这种简单状态的最简单方法是让 Menu1 在显示它时将其状态传递给 SubMenu1(作为构造函数参数),并让 SubMenu1 直接更新它。我已经在下面显示了示例代码。当然,这是两个屏幕之间的紧耦合,但这似乎是一个紧耦合的要求。
Provider 包有助于将状态与 UI 分开。您可以使用这种方法。你说“但突然间,我的表单不再在每次调用 Menu1 时初始化,而是在应用程序启动时只初始化一次”。当然,Menu1 可以在调用时调用一个方法来初始化 Provider 吗?我为 Provider 方法添加了示例代码。一些简短的说明:
- 我使用 MultiProvider,尽管我只有一个 ChangeNotifierProvider,但这会让您的代码更易于阅读
- ChangeNotifier FormData 保存表单状态
- 父表单初始化表单状态
- 两种表单都调用 FormData 方法来设置和获取状态
长示例代码直接状态如下:
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255,18,32,47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),debugShowCheckedModeBanner: false,home: Scaffold(
body: Center(
child: FormTest(),),);
}
}
class FormTest extends StatefulWidget {
@override
_FormTestState createState() => _FormTestState();
}
class _FormTestState extends State<FormTest> {
int _selectedIndex = 0;
final List<Widget> _options = [
FormWidget(),PlaceholderWidget(Colors.deepOrange),PlaceholderWidget(Colors.green)
];
void _onItemTap(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Example'),backgroundColor: Colors.teal),body: Center(
child: _options.elementAt(_selectedIndex),bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),label: 'Home',BottomNavigationBarItem(
icon: Icon(Icons.person),label: 'Profile',backgroundColor: Colors.cyan),BottomNavigationBarItem(
icon: Icon(Icons.settings),label: 'Settings',backgroundColor: Colors.lightBlue,],type: BottomNavigationBarType.shifting,currentIndex: _selectedIndex,selectedItemColor: Colors.white,unselectedItemColor: Colors.grey,iconSize: 40,onTap: _onItemTap,elevation: 5),);
}
}
class PlaceholderWidget extends StatelessWidget {
final Color color;
PlaceholderWidget(this.color);
@override
Widget build(BuildContext context) {
return Container(
color: color,);
}
}
class FormWidget extends StatefulWidget {
@override
FormWidgetState createState() {
return FormWidgetState();
}
}
class FormWidgetState extends State<FormWidget> {
final _formKey = GlobalKey<FormState>();
final _formData = {};
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,child: Column(
children: <Widget>[
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},onSaved: (value) {
_formData['parent'] = value;
},Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),child: ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Parent text: ' + _formData['parent'])));
Navigator.push(
context,MaterialPageRoute(
builder: (context) => SubFormWidget(_formData)),);
}
},child: Text('SubForm'),child: ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Subform text: ' + _formData['child'])));
}
},child: Text('Submit'),);
}
}
class SubFormWidget extends StatefulWidget {
final Map _formData;
SubFormWidget(this._formData);
@override
SubFormWidgetState createState() {
return SubFormWidgetState();
}
}
class SubFormWidgetState extends State<SubFormWidget> {
final _subFormKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Form(
key: _subFormKey,child: Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Example'),body: Column(
children: <Widget>[
Text(widget._formData['parent']),TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},onSaved: (value) {
widget._formData['child'] = value;
},Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),child: ElevatedButton(
onPressed: () {
if (_subFormKey.currentState!.validate()) {
_subFormKey.currentState!.save();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
'Subform text: ' + widget._formData['child'])));
Navigator.pop(context);
}
},);
}
}
提供者方法的长示例代码:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<FormData>(
create: (ctx) => FormData(),child: MaterialApp(
title: 'Test App',theme: ThemeData(
primarySwatch: Colors.blue,home: FormTest2(),);
}
}
class FormTest2 extends StatefulWidget {
FormTest2({Key key}) : super(key: key);
@override
_FormTest2State createState() => _FormTest2State();
}
class _FormTest2State extends State<FormTest2> {
int _selectedIndex = 0;
final List<Widget> _options = [
FormWidget2(),PlaceholderWidget(Colors.green)
];
void _onItemTap(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Example 2'),);
}
}
class FormWidget2 extends StatefulWidget {
@override
FormWidget2State createState() {
return FormWidget2State();
}
}
class FormData extends ChangeNotifier {
var _formData = <String,String>{};
void init() {
_formData = <String,String>{};
// No notifyListeners() needed in this use case
}
void setEntry(String key,String value) {
_formData[key] = value;
// notifyListeners() may or may not be needed in this use case
notifyListeners();
}
String getEntry(String key) {
var value = _formData[key];
return value;
}
}
class FormWidget2State extends State<FormWidget2> {
final _formKey = GlobalKey<FormState>();
@override
void initState() {
Provider.of<FormData>(context,listen: false).init();
super.initState();
}
@override
Widget build(BuildContext context) {
return Consumer<FormData>(
builder: (context,formData,_) => Form(
key: _formKey,child: Column(
children: <Widget>[
TextFormField(
initialValue: formData.getEntry('parent'),validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},onSaved: (value) {
formData.setEntry('parent',value);
},child: ElevatedButton(
onPressed: () {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
'Parent text: ' + formData.getEntry('parent'))));
Navigator.push(
context,MaterialPageRoute(builder: (context) => SubFormWidget2()),);
}
},child: ElevatedButton(
onPressed: () {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
'Subform text: ' + formData.getEntry('child'))));
}
},);
}
}
class SubFormWidget2 extends StatefulWidget {
@override
SubFormWidget2State createState() {
return SubFormWidget2State();
}
}
class SubFormWidget2State extends State<SubFormWidget2> {
final _subFormKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Form(
key: _subFormKey,child: Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Example 2'),body: Consumer<FormData>(
builder: (context,_) => Column(
children: <Widget>[
Text(formData.getEntry('parent')),TextFormField(
initialValue: formData.getEntry('child'),validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},onSaved: (value) {
formData.setEntry('child',value);
},Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),child: ElevatedButton(
onPressed: () {
if (_subFormKey.currentState.validate()) {
_subFormKey.currentState.save();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
'Subform text: ' + formData.getEntry('child'))));
Navigator.pop(context);
}
},);
}
}