Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter BloC Pattern: Update BloC Streams Based Another BloC's Stream

Tags:

flutter

dart

bloc

SCENARIO

I'm trying to create a Flutter app that has two screens: ContactsScreen and EditContactScreen. In ContactsScreen, the user will be presented with a DropdownButton and Text. The DropdownButton holds a list of Contact objects that have been fetched through an api. Whenever a user selects a Contact from the DropdownButton, the Text object will show information regarding that particular contact. Moreover, upon Contact selection, a RaisedButton will appear, which when clicked, will direct the user to the EditContactScreen to edit the selected Contact. I'm using the BloC pattern. I created two BloCs, one for each screen: ContactsScreenBloc and EditContactScreenBloc. ContactsScreenBloc holds a Stream<Contact> and a Sink<Contact> for managing the selected Contact. Whereas EditContactScreenBloc holds streams and sinks for the Contact fields. Finally, I have a GlobalBloc that holds the list of Contacts. The GlobalBloc is an InheritedWidget that wraps up the MaterialApp. The app is oversimplified and is part of a larger one, for that reason, I can't merge ContactsScreenBloc and EditContactScreenBloc, and there should be a GlobalBloc that has the list of Contacts.

QUESTION

I'm actually fairly new to Flutter so I'm not sure if my approach is sound. If it is, then when the user navigates to the EditContactScreen and successfully updates the Contact, how can I reflect it back in the selected Contact in ContactsScreen?

CODE SNIPPITS

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();
        },)
      ],
    );
  }
}
like image 691
boring91 Avatar asked Mar 05 '23 20:03

boring91


1 Answers

Okay, I was able to solve my issue:

In the contacts_screen.bloc.dart, I added the following method:

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);
  }

And updated the StreamBuilder<List<Contact>> for building the DropdownButton to be like this:

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,
        );
      },
    );
  },
)
like image 70
boring91 Avatar answered Mar 09 '23 16:03

boring91