如何使用 BLoC 模式管理表单状态?
How to manage form state with BLoC pattern?
我目前正在做一个辅助项目来学习 Rx 和 BLoC 模式。
我想在不使用任何 setState()
.
的情况下管理表单状态
我已经有一个 BLoC 来管理我的 'events',它存储在 SQLite 数据库中并在验证此表单后添加。
我是否需要专门为此 UI 部分创建需求 BLoC,如何创建?保留这样的代码可以吗?我应该更改我的实际 BLoC 吗?
您可以在这里找到我当前的代码:
class _EventsAddEditScreenState extends State<EventsAddEditScreen> {
bool hasDescription = false;
bool hasLocation = false;
bool hasChecklist = false;
DateTime eventDate;
TextEditingController eventNameController = new TextEditingController();
TextEditingController descriptionController = new TextEditingController();
@override
Widget build(BuildContext context) {
final eventBloc = BlocProvider.of<EventsBloc>(context);
return BlocBuilder(
bloc: eventBloc,
builder: (BuildContext context, EventsState state) {
return Scaffold(
body: Stack(
children: <Widget>[
Column(children: <Widget>[
Expanded(
child: ListView(
shrinkWrap: true,
children: <Widget>[
_buildEventImage(context),
hasDescription ? _buildDescriptionSection(context) : _buildAddSection('description'),
_buildAddSection('location'),
_buildAddSection('checklist'),
//_buildDescriptionSection(context),
],
))
]),
new Positioned(
//Place it at the top, and not use the entire screen
top: 0.0,
left: 0.0,
right: 0.0,
child: AppBar(
actions: <Widget>[
IconButton(icon: Icon(Icons.check), onPressed: () async{
if(this._checkAllField()){
String description = hasDescription ? this.descriptionController.text : null;
await eventBloc.dispatch(AddEvent(Event(this.eventNameController.text, this.eventDate,"balbla", description: description)));
print('Saving ${this.eventDate} ${eventNameController.text}');
}
},)
],
backgroundColor: Colors.transparent, //No more green
elevation: 0.0, //Shadow gone
),
),
],
),
);
},
);
}
Widget _buildAddSection(String sectionName) {
TextStyle textStyle = TextStyle(
color: Colors.black87, fontSize: 18.0, fontWeight: FontWeight.w700);
return Container(
alignment: Alignment.topLeft,
padding:
EdgeInsets.only(top: 20.0, left: 40.0, right: 40.0, bottom: 20.0),
child: FlatButton(
onPressed: () {
switch(sectionName){
case('description'):{
this.setState((){hasDescription = true;});
}
break;
case('checklist'):{
this.setState((){hasChecklist = true;});
}
break;
case('location'):{
this.setState((){hasLocation=true;});
}
break;
default:{
}
break;
}
},
padding: EdgeInsets.only(top: 0.0, left: 0.0),
child: Text(
'+ Add $sectionName',
style: textStyle,
),
),
);
}
让我们逐步解决这个问题。
你的第一个问题:
我是否需要专门为此 UI 部分创建需求 BLoC?
好吧,这与您的需求和您的应用程序有关。如果需要,您可以为每个屏幕设置一个 BLoC,但您也可以为 2 或 3 个小部件设置一个 BLoC,对此没有任何规定。如果您认为在这种情况下,为您的屏幕实现另一个 BLoC 是一种好方法,因为代码将更具可读性、组织性和可扩展性,您可以这样做,或者如果您认为最好只制作一个包含所有内容的 bloc,您就可以了这也是。
你的第二个问题:如何?
好吧,在你的代码中我只看到 setState
调用 _buildAddSection
所以让我们改变这个写一个新的 BLoc class 并用 RxDart 流处理状态变化。
class LittleBloc {
// Note that all stream already start with an initial value. In this case, false.
final BehaviorSubject<bool> _descriptionSubject = BehaviorSubject.seeded(false);
Observable<bool> get hasDescription => _descriptionSubject.stream;
final BehaviorSubject<bool> _checklistSubject = BehaviorSubject.seeded(false);
Observable<bool> get hasChecklist => _checklistSubject.stream;
final BehaviorSubject<bool> _locationSubject = BehaviorSubject.seeded(false);
Observable<bool> get hasLocation => _locationSubject.stream;
void changeDescription(final bool status) => _descriptionSubject.sink.add(status);
void changeChecklist(final bool status) => _checklistSubject.sink.add(status);
void changeLocation(final bool status) => _locationSubject.sink.add(status);
dispose(){
_descriptionSubject?.close();
_locationSubject?.close();
_checklistSubject?.close();
}
}
现在我将在您的小部件中使用此 BLoc。我将把整个 build
方法代码和更改一起放在下面。基本上我们将使用 StreamBuilder
在小部件树中构建小部件。
final LittleBloc bloc = LittleBloc(); // Our instance of bloc
@override
Widget build(BuildContext context) {
final eventBloc = BlocProvider.of<EventsBloc>(context);
return BlocBuilder(
bloc: eventBloc,
builder: (BuildContext context, EventsState state) {
return Scaffold(
body: Stack(
children: <Widget>[
Column(children: <Widget>[
Expanded(
child: ListView(
shrinkWrap: true,
children: <Widget>[
_buildEventImage(context),
StreamBuilder<bool>(
stream: bloc.hasDescription,
builder: (context, snapshot){
hasDescription = snapshot.data; // if you want hold the value
if (snapshot.data)
return _buildDescriptionSection(context);//we got description true
return buildAddSection('description'); // we have description false
}
),
_buildAddSection('location'),
_buildAddSection('checklist'),
//_buildDescriptionSection(context),
],
),
),
]
),
new Positioned(
//Place it at the top, and not use the entire screen
top: 0.0,
left: 0.0,
right: 0.0,
child: AppBar(
actions: <Widget>[
IconButton(icon: Icon(Icons.check),
onPressed: () async{
if(this._checkAllField()){
String description = hasDescription ? this.descriptionController.text : null;
await eventBloc.dispatch(AddEvent(Event(this.eventNameController.text, this.eventDate,"balbla", description: description)));
print('Saving ${this.eventDate} ${eventNameController.text}');
}
},
),
],
backgroundColor: Colors.transparent, //No more green
elevation: 0.0, //Shadow gone
),
),
],
),
);
},
);
}
您的 _buildAddSection
中不再有 setState
个电话。只需要更改一个 switch
语句。 changes...
调用将更新 BLoc class 中的流,这将重建正在侦听流的小部件。
switch(sectionName){
case('description'):
bloc.changeDescription(true);
break;
case('checklist'):
bloc.changeChecklist(true);
break;
case('location'):
bloc.changeLocation(true);
break;
default:
// you better do something here!
break;
}
并且不要忘记在 WidgetState dispose
方法中调用 bloc.dispose()
。
我目前正在做一个辅助项目来学习 Rx 和 BLoC 模式。
我想在不使用任何 setState()
.
我已经有一个 BLoC 来管理我的 'events',它存储在 SQLite 数据库中并在验证此表单后添加。
我是否需要专门为此 UI 部分创建需求 BLoC,如何创建?保留这样的代码可以吗?我应该更改我的实际 BLoC 吗?
您可以在这里找到我当前的代码:
class _EventsAddEditScreenState extends State<EventsAddEditScreen> {
bool hasDescription = false;
bool hasLocation = false;
bool hasChecklist = false;
DateTime eventDate;
TextEditingController eventNameController = new TextEditingController();
TextEditingController descriptionController = new TextEditingController();
@override
Widget build(BuildContext context) {
final eventBloc = BlocProvider.of<EventsBloc>(context);
return BlocBuilder(
bloc: eventBloc,
builder: (BuildContext context, EventsState state) {
return Scaffold(
body: Stack(
children: <Widget>[
Column(children: <Widget>[
Expanded(
child: ListView(
shrinkWrap: true,
children: <Widget>[
_buildEventImage(context),
hasDescription ? _buildDescriptionSection(context) : _buildAddSection('description'),
_buildAddSection('location'),
_buildAddSection('checklist'),
//_buildDescriptionSection(context),
],
))
]),
new Positioned(
//Place it at the top, and not use the entire screen
top: 0.0,
left: 0.0,
right: 0.0,
child: AppBar(
actions: <Widget>[
IconButton(icon: Icon(Icons.check), onPressed: () async{
if(this._checkAllField()){
String description = hasDescription ? this.descriptionController.text : null;
await eventBloc.dispatch(AddEvent(Event(this.eventNameController.text, this.eventDate,"balbla", description: description)));
print('Saving ${this.eventDate} ${eventNameController.text}');
}
},)
],
backgroundColor: Colors.transparent, //No more green
elevation: 0.0, //Shadow gone
),
),
],
),
);
},
);
}
Widget _buildAddSection(String sectionName) {
TextStyle textStyle = TextStyle(
color: Colors.black87, fontSize: 18.0, fontWeight: FontWeight.w700);
return Container(
alignment: Alignment.topLeft,
padding:
EdgeInsets.only(top: 20.0, left: 40.0, right: 40.0, bottom: 20.0),
child: FlatButton(
onPressed: () {
switch(sectionName){
case('description'):{
this.setState((){hasDescription = true;});
}
break;
case('checklist'):{
this.setState((){hasChecklist = true;});
}
break;
case('location'):{
this.setState((){hasLocation=true;});
}
break;
default:{
}
break;
}
},
padding: EdgeInsets.only(top: 0.0, left: 0.0),
child: Text(
'+ Add $sectionName',
style: textStyle,
),
),
);
}
让我们逐步解决这个问题。
你的第一个问题: 我是否需要专门为此 UI 部分创建需求 BLoC?
好吧,这与您的需求和您的应用程序有关。如果需要,您可以为每个屏幕设置一个 BLoC,但您也可以为 2 或 3 个小部件设置一个 BLoC,对此没有任何规定。如果您认为在这种情况下,为您的屏幕实现另一个 BLoC 是一种好方法,因为代码将更具可读性、组织性和可扩展性,您可以这样做,或者如果您认为最好只制作一个包含所有内容的 bloc,您就可以了这也是。
你的第二个问题:如何?
好吧,在你的代码中我只看到 setState
调用 _buildAddSection
所以让我们改变这个写一个新的 BLoc class 并用 RxDart 流处理状态变化。
class LittleBloc {
// Note that all stream already start with an initial value. In this case, false.
final BehaviorSubject<bool> _descriptionSubject = BehaviorSubject.seeded(false);
Observable<bool> get hasDescription => _descriptionSubject.stream;
final BehaviorSubject<bool> _checklistSubject = BehaviorSubject.seeded(false);
Observable<bool> get hasChecklist => _checklistSubject.stream;
final BehaviorSubject<bool> _locationSubject = BehaviorSubject.seeded(false);
Observable<bool> get hasLocation => _locationSubject.stream;
void changeDescription(final bool status) => _descriptionSubject.sink.add(status);
void changeChecklist(final bool status) => _checklistSubject.sink.add(status);
void changeLocation(final bool status) => _locationSubject.sink.add(status);
dispose(){
_descriptionSubject?.close();
_locationSubject?.close();
_checklistSubject?.close();
}
}
现在我将在您的小部件中使用此 BLoc。我将把整个 build
方法代码和更改一起放在下面。基本上我们将使用 StreamBuilder
在小部件树中构建小部件。
final LittleBloc bloc = LittleBloc(); // Our instance of bloc
@override
Widget build(BuildContext context) {
final eventBloc = BlocProvider.of<EventsBloc>(context);
return BlocBuilder(
bloc: eventBloc,
builder: (BuildContext context, EventsState state) {
return Scaffold(
body: Stack(
children: <Widget>[
Column(children: <Widget>[
Expanded(
child: ListView(
shrinkWrap: true,
children: <Widget>[
_buildEventImage(context),
StreamBuilder<bool>(
stream: bloc.hasDescription,
builder: (context, snapshot){
hasDescription = snapshot.data; // if you want hold the value
if (snapshot.data)
return _buildDescriptionSection(context);//we got description true
return buildAddSection('description'); // we have description false
}
),
_buildAddSection('location'),
_buildAddSection('checklist'),
//_buildDescriptionSection(context),
],
),
),
]
),
new Positioned(
//Place it at the top, and not use the entire screen
top: 0.0,
left: 0.0,
right: 0.0,
child: AppBar(
actions: <Widget>[
IconButton(icon: Icon(Icons.check),
onPressed: () async{
if(this._checkAllField()){
String description = hasDescription ? this.descriptionController.text : null;
await eventBloc.dispatch(AddEvent(Event(this.eventNameController.text, this.eventDate,"balbla", description: description)));
print('Saving ${this.eventDate} ${eventNameController.text}');
}
},
),
],
backgroundColor: Colors.transparent, //No more green
elevation: 0.0, //Shadow gone
),
),
],
),
);
},
);
}
您的 _buildAddSection
中不再有 setState
个电话。只需要更改一个 switch
语句。 changes...
调用将更新 BLoc class 中的流,这将重建正在侦听流的小部件。
switch(sectionName){
case('description'):
bloc.changeDescription(true);
break;
case('checklist'):
bloc.changeChecklist(true);
break;
case('location'):
bloc.changeLocation(true);
break;
default:
// you better do something here!
break;
}
并且不要忘记在 WidgetState dispose
方法中调用 bloc.dispose()
。