在不可变状态不实用的情况下使用 Bloc 模式
Posted
技术标签:
【中文标题】在不可变状态不实用的情况下使用 Bloc 模式【英文标题】:Bloc pattern in situations where immutable state is just not practical 【发布时间】:2021-03-09 23:04:00 【问题描述】:对于一个非常简单的页面,我真的很难将我的 MVVM 模式转换为带有 flutter_bloc 的 bloc 模式。我最直接关心的是我应该将执行副作用的函数放在哪里?
我有这个BlocListener
在从块发出状态时执行副作用,并调用 ViewModel:
BlocListener<LoggedOutNicknameCubit, LoggedOutNicknameState>(
listener: (context, state)
state.maybeWhen(
submitted: () => loggedOutNickNameViewModel.submitPressed(context),
orElse: () );
)
是否可以只将 ViewModel 注入到视图中,其中包含执行副作用的函数,这样我就不必直接在视图中执行副作用,因此我可以将业务逻辑与视图分离?我是否也可以将可变状态添加到该 ViewModel 中,该状态根本不具有不可变的意义,因此处于从 bloc 发出的“bloc”状态上没有意义?
我不得不说我确实想在很多事情上使用 bloc 但我只是不想将它用于我的表单验证,这只是个人喜好,因为我喜欢内置的表单验证reactive_forms.
我的 ViewModel 包含我的副作用和可变的表单状态,如下所示:
class LoggedOutNickNameViewModel extends VpViewModel
LoggedOutNickNameViewModel(
this._saveNickNameLocallyUseCase, this._getUserFieldFromLocalUseCase)
: super(null);
FormGroup get form => _form;
String get nickNameKey => _nickNameKey;
FormGroup _form;
final ISaveNickNameLocallyUseCase _saveNickNameLocallyUseCase;
final IGetUserFieldFromLocalUseCase _getUserFieldFromLocalUseCase;
final String _nickNameKey = UserRegistrationFieldKeys.nickName;
@override
void onClose()
_saveNickNameLocallyUseCase
.invoke(_form.control(_nickNameKey).value as String ?? '');
void onCreate()
_form = FormGroup(
UserRegistrationFieldKeys.nickName:
FormControl<String>(validators: [Validators.required]),
);
_form.control(_nickNameKey).value =
_getUserFieldFromLocalUseCase.invoke(_nickNameKey);
_form.markAsDirty();
void submitPressed(BuildContext context)
_saveNickNameLocallyUseCase
.invoke(_form.control(_nickNameKey).value as String ?? '');
Navigator.pushNamed(context, Routes.LOGGED_OUT_EMAIL);
将 bloc 用于大多数与不可变状态更改相关的某些事情,并将我的 ViewModel 注入同一视图以处理表单验证并保持我的副作用执行功能是否有效?
为了完整起见,我将添加与 bloc 相关的代码,这样您就可以批评我是否得到了这个“bloc”的东西。请记住,此页面只有一个文本字段和一个按钮,而且我的表单中没有使用bloc
,因此其他页面将从bloc
中获得比这个更多的好处:
集团:
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'logged_out_nickname_state.dart';
part 'logged_out_nickname_cubit.freezed.dart';
class LoggedOutNicknameCubit extends Cubit<LoggedOutNicknameState>
LoggedOutNicknameCubit() : super(const LoggedOutNicknameState.initialised());
void submitPressed() => emit(const LoggedOutNicknameState.submitted());
集团状态:
part of 'logged_out_nickname_cubit.dart';
@freezed
abstract class LoggedOutNicknameState with _$LoggedOutNicknameState
const factory LoggedOutNicknameState.initialised() = _Initialised;
const factory LoggedOutNicknameState.submitted() = _Submitted;
我的看法:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get/get.dart';
import 'package:vepo/presentation/widgets/display/buttons/elevated_buttons/submit_button_widget.dart';
import 'package:vepo/presentation/widgets/display/containers/form_container_widget.dart';
import 'package:vepo/presentation/widgets/display/text/caption_widget.dart';
import 'package:vepo/presentation/widgets/display/text/subtitle_1_widget.dart';
import 'package:vepo/presentation/widgets/forms/text_field/text_field_widget.dart';
import 'package:vepo/presentation/widgets/pages/form_page_scaffold_widget.dart';
import 'package:vepo/presentation/widgets/pages/logo_header_widget.dart';
import 'cubit/logged_out_nickname_cubit.dart';
import 'logged_out_nick_name_controller.dart';
import 'logged_out_nick_name_cubit.dart';
class LoggedOutNickNameView extends GetView<LoggedOutNickNameController>
@override
Widget build(BuildContext context)
print('building loggedOUtNickName page');
final loggedOutNickNameViewModel =
BlocProvider.of<LoggedOutNickNameViewModel>(context);
final loggedOutNicknameCubit =
BlocProvider.of<LoggedOutNicknameCubit>(context);
return VpFormPageScaffold([
const VpLogoHeader(),
BlocConsumer<LoggedOutNickNameCubit, LoggedOutNickNameState>(
builder: (context, state)
return state.when(
initialised: () =>
buildContent(loggedOutNickNameViewModel, loggedOutNicknameCubit),
submitted: () =>
buildContent(loggedOutNickNameViewModel, loggedOutNicknameCubit),
);
, listener: (context, state)
state.maybeWhen(
submitted: () => loggedOutNickNameViewModel.submitPressed(context),
orElse: () );
),
]);
Widget buildContent(LoggedOutNickNameViewModel loggedOutNickNameViewModel,
LoggedOutNicknameCubit loggedOutNicknameCubit)
return VpFormContainer([
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: const [
VpSubtitle1('So nice to meet you! What do your friends call you?',
TextAlign.center),
VpCaption('You can change this any time in settings...',
TextAlign.center),
]),
VpTextField(
loggedOutNickNameViewModel.nickNameKey,
'Nickname...',
validationMessages: (control) =>
'required': 'Please enter your name / alter ego',
textAlign: TextAlign.center,
maxLength: 20,
),
Center(
child:
VpSubmitButton('CONTINUE', loggedOutNicknameCubit.submitPressed))
], loggedOutNickNameViewModel.form);
【问题讨论】:
我已将 ViewModel 添加到 bloc 状态,这可能更惯用也可能不会更惯用。 【参考方案1】:ViewModel 并不是真正需要的,因为辅助函数和其他任何东西(非 bloc 模式的东西)都可以在 bloc 上运行。因此,诸如表单之类的可变数据可以继续存在。也可以传递到不可变状态(在我的例子中,表单传递到 bloc 状态)。
考虑 ViewModel 中的字段应该去哪里,看起来有点像在发出状态后在 BlocBuilder 中使用的字段应该属于那个状态。其他的东西可以放在任何你认为合适的地方,所以我把它添加到了cubit中,作为模式的一部分,它只是我的视图使用的辅助材料。
所以这是我目前从 MVVM 转换为 bloc 的页面。随着我对 bloc 模式理解的加深,我会更新:
状态:
part of 'logged_out_nickname_cubit.dart';
@freezed
abstract class LoggedOutNickNameState with _$LoggedOutNickNameState
factory LoggedOutNickNameState.initialised(
String nickNameKey, FormGroup form) = _Initialised;
factory LoggedOutNickNameState.submitted(
String nickNameKey, FormGroup form) = _Submitted;
肘:
part 'logged_out_nickname_state.dart';
part 'logged_out_nickname_cubit.freezed.dart';
class LoggedOutNickNameCubit extends Cubit<LoggedOutNickNameState>
LoggedOutNickNameCubit(this.nickNameKey, this.form,
this.getUserFieldFromLocalUseCase, this.saveNickNameLocallyUseCase)
: super(LoggedOutNickNameState.initialised(
nickNameKey: nickNameKey, form: form))
form.control(nickNameKey).value =
getUserFieldFromLocalUseCase.invoke(nickNameKey);
form.markAsDirty();
final String nickNameKey;
final FormGroup form;
final GetUserFieldFromLocalUseCase getUserFieldFromLocalUseCase;
final SaveNickNameLocallyUseCase saveNickNameLocallyUseCase;
// the cubit version of "mapEventToState"
void submitPressed() => emit(
LoggedOutNickNameState.submitted(nickNameKey: nickNameKey, form: form));
void initialise() => emit(
LoggedOutNickNameState.initialised(nickNameKey: nickNameKey, form: form));
// just random helper functions for the view to consume
void onSubmitPressed(BuildContext context)
saveNickNameLocallyUseCase
.invoke(form.control(nickNameKey).value as String ?? '');
Navigator.pushNamed(context, Routes.LOGGED_OUT_EMAIL,
arguments: LoggedOutEmailPageArgs(
form.control(nickNameKey).value as String ?? ''))
.then((value) => initialise());
void onBackPressed()
saveNickNameLocallyUseCase
.invoke(form.control(nickNameKey).value as String ?? '');
close();
观点:
import 'logged_out_nickname_cubit.dart';
class LoggedOutNickNamePage extends StatelessWidget
@override
Widget build(BuildContext context)
print('building loggedOUtNickName page');
final loggedOutNicknameCubit =
BlocProvider.of<LoggedOutNickNameCubit>(context);
return VpFormPageScaffold([
VpLogoHeader(loggedOutNicknameCubit.onBackPressed),
BlocConsumer<LoggedOutNickNameCubit, LoggedOutNickNameState>(
builder: (context, state)
return state.when(
initialised: (nickNameKey, form) =>
_buildContent(nickNameKey, form, loggedOutNicknameCubit),
submitted: (nickNameKey, form) =>
_buildContent(nickNameKey, form, loggedOutNicknameCubit),
);
, listener: (context, state)
state.maybeWhen(
submitted: (nickNameKey, form) =>
loggedOutNicknameCubit.onSubmitPressed(context),
orElse: () );
),
]);
Widget _buildContent(
String nickNameKey, FormGroup form, LoggedOutNickNameCubit cubit)
return VpFormContainer([
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: const [
VpSubtitle1('So nice to meet you! What do your friends call you?',
TextAlign.center),
VpCaption('You can change this any time in settings...',
TextAlign.center),
]),
VpTextField(
nickNameKey,
'Nickname...',
validationMessages: (control) =>
'required': 'Please enter your name / alter ego',
textAlign: TextAlign.center,
maxLength: 20,
),
Center(child: VpSubmitButton('CONTINUE', cubit.submitPressed))
], form);
【讨论】:
以上是关于在不可变状态不实用的情况下使用 Bloc 模式的主要内容,如果未能解决你的问题,请参考以下文章
在不使用 iostream 的情况下保存 c++11 随机生成器的状态
为啥 Python 3.8.0 允许在不使用“非本地”变量的情况下从封闭函数范围更改可变类型?