Flutter BloC 模式:基于另一个 BloC 的流更新 BloC 流
Flutter BloC Pattern: Update BloC Streams Based Another BloC's Stream
场景
我正在尝试创建一个具有两个屏幕的 Flutter 应用程序:ContactsScreen
和 EditContactScreen
。在 ContactsScreen
中,用户将看到 DropdownButton
和 Text
。 DropdownButton
包含通过 api 获取的 Contact
个对象的列表。每当用户从 DropdownButton
中选择 Contact
时,Text
对象将显示有关该特定联系人的信息。此外,在选择 Contact
后,将出现 RaisedButton
,单击后会将用户定向到 EditContactScreen
以编辑所选 Contact
。我正在使用 BloC 模式。我创建了两个 BloC,每个屏幕一个:ContactsScreenBloc
和 EditContactScreenBloc
。 ContactsScreenBloc
持有一个 Stream<Contact>
和一个 Sink<Contact>
用于管理选定的 Contact
。而 EditContactScreenBloc
保存 Contact
字段的流和汇。最后,我有一个包含 Contact
列表的 GlobalBloc
。 GlobalBloc
是包含 MaterialApp
的 InheritedWidget
。该应用程序过于简单并且是更大应用程序的一部分,因此,我无法合并 ContactsScreenBloc
和 EditContactScreenBloc
,并且应该有一个 GlobalBloc
包含 [= 的列表23=]s.
问题
我实际上是 Flutter 的新手,所以我不确定我的方法是否合理。如果是,那么当用户导航到 EditContactScreen
并成功更新 Contact
时,我如何才能将其反映回 ContactsScreen
中选择的 Contact
?
代码片段
contact.dart
class Contact {
final String id;
final String firstName;
final String lastName;
final String phoneNumber;
Contact({this.id, this.firstName, this.lastName, this.phoneNumber});
Contact.fromJson(Map<String, dynamic> parsedJson)
: id = parsedJson['id'],
firstName = parsedJson['firstName'],
lastName = parsedJson['lastName'],
phoneNumber = parsedJson['phoneNumber'];
copyWith({String firstName, String lastName, String phoneNumber}) => Contact(
id: id,
firstName: firstName ?? this.firstName,
lastName: lastName ?? this.lastName,
phoneNumber: phoneNumber ?? this.phoneNumber
);
@override
bool operator ==(other) => other.id == this.id;
@override
int get hashCode => id.hashCode;
}
global.bloc.dart
class GlobalBloc {
final _repo = Repo();
final _contacts = BehaviorSubject<List<Contact>>(seedValue: []);
Stream<List<Contact>> get contacts => _contacts.stream;
Function(List<Contact>) get updateContacts => _contacts.sink.add;
Future refreshContacts() async{
final contacts = await _repo.getContacts();
updateContacts(contacts);
}
}
contacts_screen.bloc.dart
class ContactsScreenBloc {
final _selectedContact = BehaviorSubject<Contact>(seedValue: null);
Stream<Contact> get selectedContact => _selectedContact.stream;
Function(Contact) get changeSelectedContact => _selectedContact.sink.add;
}
edit_contacts_screen.bloc.dart
class ContactsScreenBloc {
final _selectedContact = BehaviorSubject<Contact>(seedValue: null);
Stream<Contact> get selectedContact => _selectedContact.stream;
Function(Contact) get changeSelectedContact => _selectedContact.sink.add;
}
global.provider.dart
class GlobalProvider extends InheritedWidget {
final bloc = GlobalBloc();
static GlobalBloc of(BuildContext context) => (context.inheritFromWidgetOfExactType(GlobalProvider) as GlobalProvider).bloc;
bool updateShouldNotify(_) => true;
}
contacts.screen.dart
class ContactsScreen extends StatelessWidget {
final bloc = ContactsScreenBloc();
@override
Widget build(BuildContext context) {
final globalBloc = GlobalProvider.of(context);
return Column(
children: <Widget>[
StreamBuilder<List<Contact>>(
stream: globalBloc.contacts,
builder: (context, listSnapshot) {
return StreamBuilder<Contact>(
stream: bloc.selectedContact,
builder: (context, itemSnapshot) {
return DropdownButton<Contact>(
items: listSnapshot.data
?.map(
(contact) => DropdownMenuItem<Contact>(
value: contact,
child: Text(contact.firstName + ' ' + contact.lastName),
),
)
?.toList(),
onChanged: bloc.changeSelectedContact,
hint: Text('Choose a contact.'),
value: itemSnapshot.hasData ? itemSnapshot.data : null,
);
},
);
},
), // end for DropdownButton StreamBuilder
StreamBuilder<Contact>(
stream: bloc.selectedContact,
builder: (context, snapshot) {
return snapshot.hasData
? Row(children: <Widget>[
Text(snapshot.data.firstName + ' ' + snapshot.data.lastName + ' ' + snapshot.data.phoneNumber),
FlatButton(
child: Text('Edit Contact'),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
EditContactScreen(contact: snapshot.data)));
},
),
],
)
: null;
}, // End for text description
)
],
);
}
}
edit_contact.screen.dart
class EditContactScreen extends StatelessWidget {
final bloc = EditContactScreenBloc();
final Contact contact;
EditContactScreen({this.contact});
@override
Widget build(BuildContext context) {
final globalBloc = GlobalProvider.of(context);
return Column(
children: <Widget>[
TextField(onChanged: (firstName) => bloc.updateContact(contact.copyWith(firstName: firstName))),
TextField(onChanged: (lastName) => bloc.updateContact(contact.copyWith(firstName: lastName))),
TextField(onChanged: (phoneNumber) => bloc.updateContact(contact.copyWith(firstName: phoneNumber))),
RaisedButton(child: Text('Update'), onPressed: () async {
await bloc.update();
await globalBloc.refreshContacts();
Navigator.of(context).pop();
},)
],
);
}
}
好的,我能够解决我的问题:
在contacts_screen.bloc.dart
中,我添加了如下方法:
void updateContactInfo(List<Contact> contacts) {
final contact = _selectedContact.value;
if (contact == null) return;
final updatedContact = contacts.firstWhere((a) => a.id == contact.id);
if (updatedContact == null) return;
changeSelectedContact(updatedContact);
}
并更新 StreamBuilder<List<Contact>>
以构建 DropdownButton
如下:
StreamBuilder<List<Contact>>(
stream: globalBloc.contacts,
builder: (context, listSnapshot) {
bloc.updateContactInfo(listSnapshot.data);
return StreamBuilder<Contact>(
stream: bloc.selectedContact,
builder: (context, itemSnapshot) {
return DropdownButton<Contact>(
items: listSnapshot.data
?.map(
(contact) => DropdownMenuItem<Contact>(
value: contact,
child: Text(
contact.firstName + ' ' + contact.lastName),
),
)
?.toList(),
onChanged: bloc.changeSelectedContact,
hint: Text('Choose a contact.'),
value: itemSnapshot.hasData ? itemSnapshot.data : null,
);
},
);
},
)
场景
我正在尝试创建一个具有两个屏幕的 Flutter 应用程序:ContactsScreen
和 EditContactScreen
。在 ContactsScreen
中,用户将看到 DropdownButton
和 Text
。 DropdownButton
包含通过 api 获取的 Contact
个对象的列表。每当用户从 DropdownButton
中选择 Contact
时,Text
对象将显示有关该特定联系人的信息。此外,在选择 Contact
后,将出现 RaisedButton
,单击后会将用户定向到 EditContactScreen
以编辑所选 Contact
。我正在使用 BloC 模式。我创建了两个 BloC,每个屏幕一个:ContactsScreenBloc
和 EditContactScreenBloc
。 ContactsScreenBloc
持有一个 Stream<Contact>
和一个 Sink<Contact>
用于管理选定的 Contact
。而 EditContactScreenBloc
保存 Contact
字段的流和汇。最后,我有一个包含 Contact
列表的 GlobalBloc
。 GlobalBloc
是包含 MaterialApp
的 InheritedWidget
。该应用程序过于简单并且是更大应用程序的一部分,因此,我无法合并 ContactsScreenBloc
和 EditContactScreenBloc
,并且应该有一个 GlobalBloc
包含 [= 的列表23=]s.
问题
我实际上是 Flutter 的新手,所以我不确定我的方法是否合理。如果是,那么当用户导航到 EditContactScreen
并成功更新 Contact
时,我如何才能将其反映回 ContactsScreen
中选择的 Contact
?
代码片段
contact.dart
class Contact {
final String id;
final String firstName;
final String lastName;
final String phoneNumber;
Contact({this.id, this.firstName, this.lastName, this.phoneNumber});
Contact.fromJson(Map<String, dynamic> parsedJson)
: id = parsedJson['id'],
firstName = parsedJson['firstName'],
lastName = parsedJson['lastName'],
phoneNumber = parsedJson['phoneNumber'];
copyWith({String firstName, String lastName, String phoneNumber}) => Contact(
id: id,
firstName: firstName ?? this.firstName,
lastName: lastName ?? this.lastName,
phoneNumber: phoneNumber ?? this.phoneNumber
);
@override
bool operator ==(other) => other.id == this.id;
@override
int get hashCode => id.hashCode;
}
global.bloc.dart
class GlobalBloc {
final _repo = Repo();
final _contacts = BehaviorSubject<List<Contact>>(seedValue: []);
Stream<List<Contact>> get contacts => _contacts.stream;
Function(List<Contact>) get updateContacts => _contacts.sink.add;
Future refreshContacts() async{
final contacts = await _repo.getContacts();
updateContacts(contacts);
}
}
contacts_screen.bloc.dart
class ContactsScreenBloc {
final _selectedContact = BehaviorSubject<Contact>(seedValue: null);
Stream<Contact> get selectedContact => _selectedContact.stream;
Function(Contact) get changeSelectedContact => _selectedContact.sink.add;
}
edit_contacts_screen.bloc.dart
class ContactsScreenBloc {
final _selectedContact = BehaviorSubject<Contact>(seedValue: null);
Stream<Contact> get selectedContact => _selectedContact.stream;
Function(Contact) get changeSelectedContact => _selectedContact.sink.add;
}
global.provider.dart
class GlobalProvider extends InheritedWidget {
final bloc = GlobalBloc();
static GlobalBloc of(BuildContext context) => (context.inheritFromWidgetOfExactType(GlobalProvider) as GlobalProvider).bloc;
bool updateShouldNotify(_) => true;
}
contacts.screen.dart
class ContactsScreen extends StatelessWidget {
final bloc = ContactsScreenBloc();
@override
Widget build(BuildContext context) {
final globalBloc = GlobalProvider.of(context);
return Column(
children: <Widget>[
StreamBuilder<List<Contact>>(
stream: globalBloc.contacts,
builder: (context, listSnapshot) {
return StreamBuilder<Contact>(
stream: bloc.selectedContact,
builder: (context, itemSnapshot) {
return DropdownButton<Contact>(
items: listSnapshot.data
?.map(
(contact) => DropdownMenuItem<Contact>(
value: contact,
child: Text(contact.firstName + ' ' + contact.lastName),
),
)
?.toList(),
onChanged: bloc.changeSelectedContact,
hint: Text('Choose a contact.'),
value: itemSnapshot.hasData ? itemSnapshot.data : null,
);
},
);
},
), // end for DropdownButton StreamBuilder
StreamBuilder<Contact>(
stream: bloc.selectedContact,
builder: (context, snapshot) {
return snapshot.hasData
? Row(children: <Widget>[
Text(snapshot.data.firstName + ' ' + snapshot.data.lastName + ' ' + snapshot.data.phoneNumber),
FlatButton(
child: Text('Edit Contact'),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
EditContactScreen(contact: snapshot.data)));
},
),
],
)
: null;
}, // End for text description
)
],
);
}
}
edit_contact.screen.dart
class EditContactScreen extends StatelessWidget {
final bloc = EditContactScreenBloc();
final Contact contact;
EditContactScreen({this.contact});
@override
Widget build(BuildContext context) {
final globalBloc = GlobalProvider.of(context);
return Column(
children: <Widget>[
TextField(onChanged: (firstName) => bloc.updateContact(contact.copyWith(firstName: firstName))),
TextField(onChanged: (lastName) => bloc.updateContact(contact.copyWith(firstName: lastName))),
TextField(onChanged: (phoneNumber) => bloc.updateContact(contact.copyWith(firstName: phoneNumber))),
RaisedButton(child: Text('Update'), onPressed: () async {
await bloc.update();
await globalBloc.refreshContacts();
Navigator.of(context).pop();
},)
],
);
}
}
好的,我能够解决我的问题:
在contacts_screen.bloc.dart
中,我添加了如下方法:
void updateContactInfo(List<Contact> contacts) {
final contact = _selectedContact.value;
if (contact == null) return;
final updatedContact = contacts.firstWhere((a) => a.id == contact.id);
if (updatedContact == null) return;
changeSelectedContact(updatedContact);
}
并更新 StreamBuilder<List<Contact>>
以构建 DropdownButton
如下:
StreamBuilder<List<Contact>>(
stream: globalBloc.contacts,
builder: (context, listSnapshot) {
bloc.updateContactInfo(listSnapshot.data);
return StreamBuilder<Contact>(
stream: bloc.selectedContact,
builder: (context, itemSnapshot) {
return DropdownButton<Contact>(
items: listSnapshot.data
?.map(
(contact) => DropdownMenuItem<Contact>(
value: contact,
child: Text(
contact.firstName + ' ' + contact.lastName),
),
)
?.toList(),
onChanged: bloc.changeSelectedContact,
hint: Text('Choose a contact.'),
value: itemSnapshot.hasData ? itemSnapshot.data : null,
);
},
);
},
)