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 Contact
s. 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 Contact
s.
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();
},)
],
);
}
}
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,
);
},
);
},
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With