当我从Form
加载初始数据时,我有一个包含StreamBuilder
和StreamBuilder
的屏幕,TextFormField
显示了预期的数据。
当我点击TextFormField
键盘时,这将导致小部件重建,
然后再一次,在编辑之后,键盘会下降,这会导致再次重新构建小部件,并且在重新构建小部件StreamBuilder
时会再次订阅,并将文本框值替换为初始值。
这是我的代码
Widget _formUI() {
return StreamBuilder<ConcreteEstimationForm>(
stream: _bloc.inputObservable(),
builder: (context, AsyncSnapshot<ConcreteEstimationForm> snapshot) {
if (snapshot.hasData) {
ConcreteEstimationForm form = snapshot.data;
_descriptionController.text = form.description;
return Column(
children: <Widget>[
TextFormField(
decoration: const InputDecoration(labelText: "Description"),
keyboardType: TextInputType.text,
validator: _descriptionValidator,
controller: _descriptionController,
onSaved: (String value) {
_description = value;
},
),
],
);
} else {
return new Center(
child: new CircularProgressIndicator(),
);
}
});
}
有人遇到这个问题吗? 如何解决呢?
谢谢。
答案 0 :(得分:9)
完全有道理并且预期软件键盘打开导致重建。在幕后,MediaQuery
是 updated with view insets。这些 MediaQueryData.viewInsets
确保您的 UI 知道隐藏它的键盘。抽象地说,遮挡屏幕的键盘会导致窗口发生变化,并且大部分时间会导致您的 UI 发生变化,这需要对 UI 进行更改 - 重建。
我可以确信您在 Flutter 应用程序中使用了 Scaffold
。与许多其他框架小部件一样,Scaffold
小部件依赖(参见 InheritedWidget
)在 MediaQuery
(从包含您的应用)使用 MediaQuery.of(context)
。
有关详细信息,请参阅 Window
。
这一切都归结为 Scaffold
依赖于视图插入。这允许它在这些视图插入发生变化时调整大小。基本上,当键盘打开时,视图插入更新,这允许脚手架在底部缩小,删除遮挡空间。
长话短说,适应调整后的视图插图的脚手架需要重新构建脚手架 UI。并且由于您的小部件必然是脚手架(可能是 body
)的子代,因此您的小部件也会在发生这种情况时重建。
您可以使用 MediaQueryData
禁用视图插入调整大小行为。但是,这不一定会停止重建,因为可能仍然依赖于 MediaQuery
。我将在下面解释你应该如何真正思考这个问题。
您应该始终以 build
方法幂等的方式构建 Flutter 小部件。
范例是构建调用可以在任何时间点发生,每秒最多 60 次(如果刷新率更高,则可能更多)。
我所说的幂等构建调用是指当您的小部件配置(在 Scaffold.resizeToAvoidBottomInset
的情况下)或您的状态(在 {{3} }) 更改,生成的小部件树应该完全相同。因此,您不想在 build
中处理任何状态 - 它的唯一职责应该是表示当前配置或状态。
导致重建的软件键盘打开只是一个很好的例子,说明了为什么会这样。其他示例包括旋转设备、在网络上调整大小,但随着您的小部件树开始变得复杂,它实际上可以是任何东西(更多内容见下文)。
StreamBuilder
在重建时重新订阅回到最初的问题:在这种情况下,您的问题是您错误地接近 StreamBuilder
。您不应该不为它提供一个每次构建都重新创建的流。
流构建器的工作方式是订阅初始流,然后在更新流时重新订阅。这意味着当 stream
小部件的 StreamBuilder
属性在两次 build
调用之间不同时,流构建器将取消订阅第一个流并订阅第二个(新)流。>
您可以在 StatelessWidget
s 中看到这一点:
if (oldWidget.stream != widget.stream) {
if (_subscription != null) {
_unsubscribe();
_summary = widget.afterDisconnected(_summary);
}
_subscribe();
}
这里显而易见的解决方案是,当您不想重新订阅时,您将希望在不同的构建调用之间提供相同的流。这可以追溯到幂等构建调用!
例如,StreamController
将始终返回相同的流,这意味着在您的 stream: streamController.stream
中使用 StreamBuilder
是安全的。基本上,所有控制器、行为主体等实现都应该以这种方式运行 - 只要您不重新创建您的流,StreamBuilder
就会妥善处理它!
因此,您案例中的错误函数是 _bloc.inputObservable()
,它每次都会创建一个新流,而不是返回相同的流。
请注意,我说过构建调用可以“在任何时间点”发生。实际上,您可以(技术上)准确控制应用中每次构建的发生时间。然而,一个普通的应用程序会非常复杂,你可能无法控制它,因此,你会希望有幂等的构建调用。
导致重建的键盘就是一个很好的例子。
如果您从高层次考虑,这正是您想要的 - 框架及其小部件(或您创建的小部件)负责响应外部更改并在必要时进行重建。树中的叶小部件不应该关心是否发生重建 - 它们应该可以放置在任何环境中,并且框架会通过相应地重建来负责对该环境的更改做出反应。
我希望我能够为你解决这个问题:)