Flutter 如何使用提供程序创建提交/放弃更改模式?
在flutter中,基于提供者的模态小部件状态管理的最佳实践是什么?当用户进行编辑更改时,在用户确认/关闭模态小部件之前,不会传播到父页面。或者,用户可以选择放弃更改 简言之: 具有确定和取消操作的模态小部件,或 模态小部件,当模态关闭时应用更改 目前,我的解决方案如下所示 创建当前状态的副本 调用flatter的show____;函数,并使用.value构造函数与提供程序包装小部件,以公开状态的副本 如果需要,在模式窗口小部件关闭时更新原始状态 案例2的示例:Flutter 如何使用提供程序创建提交/放弃更改模式?,flutter,flutter-provider,state-management,flutter-state,Flutter,Flutter Provider,State Management,Flutter State,在flutter中,基于提供者的模态小部件状态管理的最佳实践是什么?当用户进行编辑更改时,在用户确认/关闭模态小部件之前,不会传播到父页面。或者,用户可以选择放弃更改 简言之: 具有确定和取消操作的模态小部件,或 模态小部件,当模态关闭时应用更改 目前,我的解决方案如下所示 创建当前状态的副本 调用flatter的show____;函数,并使用.value构造函数与提供程序包装小部件,以公开状态的副本 如果需要,在模式窗口小部件关闭时更新原始状态 案例2的示例: Future<void&g
Future<void> showEditDialog() async {
// Create a copy of the current state
final orgState = context.read<MeState>();
final tmpState = MeState.from(orgState);
// show modal widget with new provider
await showDialog<void>(
context: context,
builder: (_) => ChangeNotifierProvider<MeState>.value(
value: tmpState,
builder: (context, _) => _buildEditDialogWidgets(context)),
);
// update original state (no discard option to keep it simple)
orgState.update(tmpState);
}
提供FiltersState实例
用户可以通过打开模式底部表单来编辑这些过滤器,但对过滤器的更改不得反映在应用程序中,直到通过在scrim上录音关闭底部表单。编辑时,更改在底部图纸上可见
Foo过滤器在底部的页面上显示为芯片。Bar和baz过滤器在从底部工作表打开的嵌套对话框窗口中进行编辑。编辑Bar或Baz过滤器集合时,更改必须仅反映在嵌套的对话框窗口内。确认嵌套对话框后,更改现在会反映在底部工作表上。如果取消嵌套对话框,更改不会传输到底部图纸。与以前一样,在关闭底部工作表之前,这些更改在应用程序内部不可见
为了避免不必要的小部件重建,选择器小部件用于显示过滤器值
通过与yellowgray的讨论,我认为应该将所有非依赖值移出代理提供程序。所以,临时代理提供程序可以创建完全独立于原始状态对象的新临时状态对象。而对于其他对象,临时状态是从原始状态生成的,并传递给值构造函数,如上面的示例所示。最简单的方法是,您可以在弹出对话框时提供一个结果,并在更新提供程序时使用该结果
import 'dart:collection';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class Item {
Item(this.name);
String name;
Item clone() => Item(name);
}
class MyState extends ChangeNotifier {
List<Item> _items = <Item>[];
UnmodifiableListView<Item> get items => UnmodifiableListView<Item>(_items);
void add(Item item) {
if (item == null) {
return;
}
_items.add(item);
notifyListeners();
}
void update(Item oldItem, Item newItem) {
final int indexOfItem = _items.indexOf(oldItem);
if (newItem == null || indexOfItem < 0) {
return;
}
_items[indexOfItem] = newItem;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
@override
Widget build(_) {
return ChangeNotifierProvider<MyState>(
create: (_) => MyState(),
builder: (_, __) => MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Builder(
builder: (BuildContext context) => Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
FlatButton(
onPressed: () => _addItem(context),
child: const Text('Add'),
),
Expanded(
child: Consumer<MyState>(
builder: (_, MyState state, __) {
final List<Item> items = state.items;
return ListView.builder(
itemCount: items.length,
itemBuilder: (_, int index) => GestureDetector(
onTap: () => _updateItem(context, items[index]),
child: ListTile(
title: Text(items[index].name),
),
),
);
},
),
),
],
),
),
),
),
),
);
}
Future<void> _addItem(BuildContext context) async {
final Item item = await showDialog<Item>(
context: context,
builder: (BuildContext context2) => AlertDialog(
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.pop(context2),
child: const Text('Cancel'),
),
FlatButton(
onPressed: () => Navigator.pop(
context2,
Item('New Item ${Random().nextInt(100)}'),
),
child: const Text('ADD'),
),
],
),
);
Provider.of<MyState>(context, listen: false).add(item);
}
Future<void> _updateItem(BuildContext context, Item item) async {
final Item updatedItem = item.clone();
final Item tempItem = await showModalBottomSheet<Item>(
context: context,
builder: (_) {
final TextEditingController controller = TextEditingController();
controller.text = updatedItem.name;
return Container(
height: 300,
child: Column(
children: <Widget>[
Text('Original: ${item.name}'),
TextField(
controller: controller,
enabled: false,
),
TextButton(
onPressed: () {
updatedItem.name = 'New Item ${Random().nextInt(100)}';
controller.text = updatedItem.name;
},
child: const Text('Change name'),
),
TextButton(
onPressed: () => Navigator.pop(context, updatedItem),
child: const Text('UPDATE'),
),
TextButton(
onPressed: () => Navigator.pop(context, Item(null)),
child: const Text('Cancel'),
),
],
),
);
},
);
if (tempItem != null && tempItem != updatedItem) {
// Do not update if "Cancel" is pressed.
return;
}
// Update if "UPDATE" is pressed or dimissed.
Provider.of<MyState>(context, listen: false).update(item, updatedItem);
}
}
最简单的方法是,您可以在弹出对话框时提供一个结果,并在更新提供程序时使用该结果
import 'dart:collection';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class Item {
Item(this.name);
String name;
Item clone() => Item(name);
}
class MyState extends ChangeNotifier {
List<Item> _items = <Item>[];
UnmodifiableListView<Item> get items => UnmodifiableListView<Item>(_items);
void add(Item item) {
if (item == null) {
return;
}
_items.add(item);
notifyListeners();
}
void update(Item oldItem, Item newItem) {
final int indexOfItem = _items.indexOf(oldItem);
if (newItem == null || indexOfItem < 0) {
return;
}
_items[indexOfItem] = newItem;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
@override
Widget build(_) {
return ChangeNotifierProvider<MyState>(
create: (_) => MyState(),
builder: (_, __) => MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Builder(
builder: (BuildContext context) => Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
FlatButton(
onPressed: () => _addItem(context),
child: const Text('Add'),
),
Expanded(
child: Consumer<MyState>(
builder: (_, MyState state, __) {
final List<Item> items = state.items;
return ListView.builder(
itemCount: items.length,
itemBuilder: (_, int index) => GestureDetector(
onTap: () => _updateItem(context, items[index]),
child: ListTile(
title: Text(items[index].name),
),
),
);
},
),
),
],
),
),
),
),
),
);
}
Future<void> _addItem(BuildContext context) async {
final Item item = await showDialog<Item>(
context: context,
builder: (BuildContext context2) => AlertDialog(
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.pop(context2),
child: const Text('Cancel'),
),
FlatButton(
onPressed: () => Navigator.pop(
context2,
Item('New Item ${Random().nextInt(100)}'),
),
child: const Text('ADD'),
),
],
),
);
Provider.of<MyState>(context, listen: false).add(item);
}
Future<void> _updateItem(BuildContext context, Item item) async {
final Item updatedItem = item.clone();
final Item tempItem = await showModalBottomSheet<Item>(
context: context,
builder: (_) {
final TextEditingController controller = TextEditingController();
controller.text = updatedItem.name;
return Container(
height: 300,
child: Column(
children: <Widget>[
Text('Original: ${item.name}'),
TextField(
controller: controller,
enabled: false,
),
TextButton(
onPressed: () {
updatedItem.name = 'New Item ${Random().nextInt(100)}';
controller.text = updatedItem.name;
},
child: const Text('Change name'),
),
TextButton(
onPressed: () => Navigator.pop(context, updatedItem),
child: const Text('UPDATE'),
),
TextButton(
onPressed: () => Navigator.pop(context, Item(null)),
child: const Text('Cancel'),
),
],
),
);
},
);
if (tempItem != null && tempItem != updatedItem) {
// Do not update if "Cancel" is pressed.
return;
}
// Update if "UPDATE" is pressed or dimissed.
Provider.of<MyState>(context, listen: false).update(item, updatedItem);
}
}
一,。我应该在哪里处理tmpState
我认为就你的情况而言,你不需要担心。tmpState就像函数showEditDialog中的临时变量
二,。ProxyProvider没有.value构造函数
不需要,因为它已经是了。ProxyProvider:T是需要侦听的提供程序。在你的情况下,这是组织状态。但是我认为orgState不会改变这个函数之外的值,所以我不知道你为什么需要它
三,。若临时状态是在提供者的create:中创建的,那个么当model关闭时,我如何安全地访问该临时状态呢
您仍然可以访问_buildEditDialogWidgets中的orgState,并通过context.read进行更新。但我认为不应该在同一个提供者树中两次使用同一类型
实际上,当我第一次看到您的代码时,我会想,为什么您需要将tmpState包装为另一个提供者您的_buildEditDialogWidgets包含更复杂的子树或其他需要在许多不同的widget中使用值的东西?。这是我能想到的更简单的版本
Future<void> showEditDialog() async {
// Create a copy of the current state
final orgState = context.read<MeState>();
// show modal widget with new provider
await showDialog<void>(
context: context,
builder: (_) => _buildEditDialogWidgets(context,MeState.from(orgState)),
);
}
...
Widget _buildEditDialogWidgets(context, model){
...
onSubmit(){
context.read<MeState>().update(updatedModel)
}
...
}
一,。我应该在哪里处理tmpState
我认为就你的情况而言,你不需要担心。tmpState就像函数showEditDialog中的临时变量
二,。ProxyProvider没有.value构造函数
不需要,因为它已经是了。ProxyProvider:T是需要侦听的提供程序。在你的情况下,这是组织状态。但是我认为orgState不会改变这个函数之外的值,所以我不知道你为什么需要它
三,。若临时状态是在提供者的create:中创建的,那个么当model关闭时,我如何安全地访问该临时状态呢
您仍然可以访问_buildEditDialogWidgets中的orgState,并通过context.read进行更新。但我认为不应该在同一个提供者树中两次使用同一类型
实际上,当我第一次看到您的代码时,我会想,为什么您需要将tmpState包装为另一个提供者您的_buildEditDialogWidgets包含更复杂的子树或其他需要在许多不同的widget中使用值的东西?。这是我能想到的更简单的版本
Future<void> showEditDialog() async {
// Create a copy of the current state
final orgState = context.read<MeState>();
// show modal widget with new provider
await showDialog<void>(
context: context,
builder: (_) => _buildEditDialogWidgets(context,MeState.from(orgState)),
);
}
...
Widget _buildEditDialogWidgets(context, model){
...
onSubmit(){
context.read<MeState>().update(updatedModel)
}
...
}
但是,如果用户在scrim上点击关闭了底部纸张,我如何提供结果,例如在showModalBottomSheet的情况下?@zigzag您的意思是当底部纸张被关闭时,对吗?如果是,您想要的结果不等于NULL?两个问题都是。@zigzag我更新了示例代码。请参阅_updateItem。。。。回答您的问题就足够了吗?这个答案的问题是,在showModalBottomSheet中,您不再使用提供者访问状态,而是直接传递对状态的引用。通过参数将状态传递到复杂的小部件tre
不建议使用e,因为它会造成维护问题。但是,如果用户在scrim上点击关闭了底部工作表,我如何提供结果,例如在showModalBottomSheet的情况下?@zigzag您的意思是当底部工作表关闭时,对吗?如果是,您想要的结果不等于NULL?两个问题都是。@zigzag我更新了示例代码。请参阅_updateItem。。。。回答您的问题就足够了吗?这个答案的问题是,在showModalBottomSheet中,您不再使用提供者访问状态,而是直接传递对状态的引用。不建议通过参数将状态传递到复杂的小部件树中,因为这样会产生维护问题。在实际代码中,我并不是只使用一个提供程序和一个状态对象,而是使用多个提供程序和多个状态类。其中一个是包含activeFiltersCount属性的ProxyProvider,它依赖于其他提供程序来计算它。因此,在我的ChangeNotifierProxy ProviderValue3示例中,当为模式T构建临时状态时,不是orgState1,而是tmpState1…2。续:问题在于临时R状态,因为它应该包括原始R状态的其他属性。不过,您给了我一个想法,将ProxyProvider的R状态拆分为两个状态类。其中一个只包含activeFiltersCount,因此temp ProxyProvider的值不依赖于原始的R。然后通过普通的提供程序提供另一个类。在buildEditDialogWidgets内部,我并不总是知道模式何时关闭。例如,在showModalBottomSheet的情况下,如果用户在scrim上点击关闭了底部工作表,则更简单的版本将不起作用,因为最终orgState=context.read;不创建副本,但返回对当前状态的引用。此外,不建议通过参数将状态传递到复杂的小部件树中,因为这样会产生维护问题。从临时状态创建一个新的提供者的优点是,在buildEditDialogWidgets中,什么都不需要更改。里面的小部件不知道它们正在读取/更新状态类的临时副本。我没有注意到它只通过参考,你是对的。在实际代码中,我并不是只使用一个提供程序和一个状态对象,而是使用多个提供程序和多个状态类。其中一个是包含activeFiltersCount属性的ProxyProvider,它依赖于其他提供程序来计算它。因此,在我的ChangeNotifierProxy ProviderValue3示例中,当为模式T构建临时状态时,不是orgState1,而是tmpState1…2。续:问题在于临时R状态,因为它应该包括原始R状态的其他属性。不过,您给了我一个想法,将ProxyProvider的R状态拆分为两个状态类。其中一个只包含activeFiltersCount,因此temp ProxyProvider的值不依赖于原始的R。然后通过普通的提供程序提供另一个类。在buildEditDialogWidgets内部,我并不总是知道模式何时关闭。例如,在showModalBottomSheet的情况下,如果用户在scrim上点击关闭了底部工作表,则更简单的版本将不起作用,因为最终orgState=context.read;不创建副本,但返回对当前状态的引用。此外,不建议通过参数将状态传递到复杂的小部件树中,因为这样会产生维护问题。从临时状态创建一个新的提供者的优点是,在buildEditDialogWidgets中,什么都不需要更改。里面的小部件不知道它们正在读取/更新状态类的临时副本。我没有注意到它只传递参考,你是对的。