Flutter BloC 模式:基于另一个 BloC 的流更新 BloC 流

Posted

技术标签:

【中文标题】Flutter BloC 模式:基于另一个 BloC 的流更新 BloC 流【英文标题】:Flutter BloC Pattern: Update BloC Streams Based Another BloC's Stream 【发布时间】:2019-05-23 21:57:10 【问题描述】:

场景

我正在尝试创建一个具有两个屏幕的 Flutter 应用程序:ContactsScreenEditContactScreen。在ContactsScreen 中,用户将看到DropdownButtonTextDropdownButton 包含通过 api 获取的 Contact 对象列表。每当用户从DropdownButton 中选择Contact 时,Text 对象将显示有关该特定联系人的信息。此外,在选择Contact 时,将出现一个RaisedButton,单击该RaisedButton 将引导用户到EditContactScreen 以编辑选定的Contact。我正在使用 BloC 模式。我创建了两个 BloC,每个屏幕一个:ContactsScreenBlocEditContactScreenBlocContactsScreenBloc 拥有一个Stream<Contact> 和一个Sink<Contact>,用于管理选定的Contact。而EditContactScreenBloc 保存Contact 字段的流和接收器。最后,我有一个GlobalBloc,其中包含Contacts 的列表。 GlobalBloc 是一个 InheritedWidget,它包含了 MaterialApp。该应用程序过于简单,是更大应用程序的一部分,因此,我无法合并ContactsScreenBlocEditContactScreenBloc,应该有一个GlobalBloc 包含Contacts 的列表。

问题

我实际上是 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();
        ,)
      ],
    );
  

【问题讨论】:

为什么不创建一个单独的联系人组而不是将其分隔为contact_screen.blocedit_contacts_screen.bloc @nonybrighto 是的,如果应用程序只是关于这些屏幕,那么你是对的,但正如我所说,这只是一个更大的应用程序的 sn-p,两个块应该分开。跨度> 【参考方案1】:

好的,我能够解决我的问题:

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&lt;List&lt;Contact&gt;&gt; 以构建 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 BloC 模式:基于另一个 BloC 的流更新 BloC 流的主要内容,如果未能解决你的问题,请参考以下文章

使用 flutter_bloc 库有啥缺点

在使用 flutter_bloc 库调度事件之前等待一些结果

将模型从一个屏幕传递到另一个 FLUTTER 的更好方法

Dart/Flutter 中 BLoC 模式的最佳实践

什么时候应该在 Flutter 中使用 BLoC 模式?

Bloc 模式是不是适合在 Flutter 应用中管理导航?