如果子窗口小部件正在被处理,那么显示子窗口小部件对话框的最佳选择是什么?



我有一个ChangeNotifier模型,它公开了一个包含N个状态的enum。我有一个StatelessWidget,它表示屏幕的根小部件。对于模型的N种状态中的每一种,我在根小部件页面中显示不同的UI。为了使这段代码紧凑且易于管理,我让根小部件显示不同的StatelessWidgets,每个都对应于一个特定的状态。

对于状态A, B, C,我有StatelessWidgetA, StatelessWidgetB和StatelessWidgetC。根小部件在状态变化时在这些子小部件之间切换。

这些子部件中的每一个都在按下按钮时显示嵌套对话框。例如. .

dialog1 -> "Are you sure you want to do this operation?"
dialog2 -> "Operation succeeded!!"

在显示第二个和第一个对话框之间,状态改变了,显示对话框的子小部件(和上下文)被处理了,因为状态改变在根小部件上传播一个构建。

当这种情况发生时,我得到以下错误…

FlutterError (Looking up a deactivated widget's ancestor is unsafe.
At this point the state of the widget's element tree is no longer stable.
To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.)

我的问题不是关于哪里出了问题(这是很明显的),而是关于这个页面的新设计是什么,不会导致这个错误?

  1. 我喜欢有一堆无状态的子部件来按状态组织代码。
  2. 理想情况下,我不希望为每个子部件传递回调函数,因为这会破坏使顶层构建更简洁的目的。

是否有一种干净的方式来让这些子部件显示对话框,其上下文范围为顶级根部件?

我提供了一个单一的文件扑动应用程序,这是一个最小的可重复的例子…

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
void showAlertDialog({
required BuildContext context,
required String title,
required String body,
required bool showCancelOption,
Function? onConfirm,
}) {
showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
List<Widget> actions = [
TextButton(
child: const Text("OK"),
onPressed: () {
Navigator.pop(context);
if (onConfirm != null) {
onConfirm();
}
},
),
];
if (showCancelOption) {
actions.add(
TextButton(
child: const Text("Cancel"),
onPressed: () => Navigator.pop(context),
),
);
}
return AlertDialog(
title: Text(title),
content: Text(body),
actions: actions,
);
},
);
}
enum MyState {
one,
transition,
two,
}
class MyStateModel extends ChangeNotifier {
MyState _state = MyState.one;
MyState get state => _state;
Future<bool> transitionState() async {
var newState = _state == MyState.one ? MyState.two : MyState.one;
_state = MyState.transition;
notifyListeners();
await Future.delayed(const Duration(milliseconds: 500)); // fake some request operation
_state = newState;
notifyListeners();
return true;
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyStateModel>(
create: (context) => MyStateModel(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var model = Provider.of<MyStateModel>(context);
Widget body;
// this build method is nice and small, and
switch (model.state) {
case MyState.one:
body = const StateOneWidget();
break;
case MyState.transition:
body = StateTransitionWidget();
break;
case MyState.two:
body = const StateTwoWidget();
break;
}
return Scaffold(
body: body,
);
}
}
class StateOneWidget extends StatelessWidget {
const StateOneWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
child: const Text("Goto State Two"),
onPressed: () {
showAlertDialog(
context: context,
title: "Goto State Two?",
body: "This will go to state two.",
showCancelOption: true,
onConfirm: () async {
var model = Provider.of<MyStateModel>(context, listen: false);
var success = await model.transitionState();
if (success) {
showAlertDialog(
context: context,
title: "Got State Two",
body: "Success!!",
showCancelOption: false,
);
}
},
);
},
),
);
}
}
class StateTwoWidget extends StatelessWidget {
const StateTwoWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
child: const Text("Goto State One"),
onPressed: () {
showAlertDialog(
context: context,
title: "Goto State One?",
body: "This will go to state one.",
showCancelOption: true,
onConfirm: () async {
var model = Provider.of<MyStateModel>(context, listen: false);
var success = await model.transitionState();
if (success) {
showAlertDialog(
context: context,
title: "Got State One",
body: "Success!!",
showCancelOption: false,
);
}
},
);
},
),
);
}
}
class StateTransitionWidget extends StatelessWidget {
const StateTransitionWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Center(
child: CircularProgressIndicator(),
);
}
}

您可以将dialogContext添加到您的类中,并在构造函数中传递my主页的构建上下文。然后在showAlertDialog函数中使用这个dialogContext作为上下文:

case MyState.one:
body = StateOneWidget(dialogContext: context);
class StateOneWidget extends StatelessWidget {
BuildContext dialogContext;
StateOneWidget({required this.dialogContext, Key? key}) : super(key: key);
showAlertDialog(
context: dialogContext,

最新更新