0% found this document useful (0 votes)
168 views22 pages

60 Days of Flutter - Building A Messenger - Day 30-32 - Firebase Chat UI Using Stream and Bloc

This document summarizes updates made to the code for a Firebase chat application built with Flutter. Specifically, it details: 1) Updates to the data model to support bidirectional contact mapping so both users are added as contacts of each other. 2) Additions to the ContactsBloc and related providers to implement this reverse contact mapping. 3) Updates to support refreshing chat data when switching between conversations by adding new events and states to the ChatBloc.

Uploaded by

Nguyen Anh
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
168 views22 pages

60 Days of Flutter - Building A Messenger - Day 30-32 - Firebase Chat UI Using Stream and Bloc

This document summarizes updates made to the code for a Firebase chat application built with Flutter. Specifically, it details: 1) Updates to the data model to support bidirectional contact mapping so both users are added as contacts of each other. 2) Additions to the ContactsBloc and related providers to implement this reverse contact mapping. 3) Updates to support refreshing chat data when switching between conversations by adding new events and states to the ChatBloc.

Uploaded by

Nguyen Anh
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 22

10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc

60 Days of Flutter :Building a


Messenger : Day 30–32 : Firebase Chat
UI using Stream and Bloc
Aditya Gurjar
Sep 18 · 5 min read

In the last post we created the Bloc for the Chat Page. In today’s post we’re gonna wire it
with our UI. I got stuck at few decisions I made related to the schema I created and I
learned a few things:

Chhichhore is a overrated movie.

For every contact created a reverse mapping is also required, so if A adds B as a


contact, we need to add both of them in the contactlist for each other.

We’ll be making these corrections and also add more Events and States to the ChatBloc

based on the UI changes.

https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 1/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc

If you remember from this earlier post, we had built a UI where one could swipe
left/right to switch between conversations. Today we’ll be writing the business logic for
it as well. Let’s start!

Updating FireStore Rules


For now we’re gonna allow anyone who is signed in, to make modifications. We’ll later
add authentication for all the collections. I’ll write a dedicated post for it at the end of
the series.

match /users/{document=**} {

function isSignedIn() {
return request.auth.uid != null;
}
//Allow creating a new user to anyone who is authenticated
allow create: if isSignedIn();
//Allow read if signed in
allow read: if isSignedIn();
// Allow update only if the uid matches (same user)
allow update: if isSignedIn();// && request.auth.uid ==
resource.data.uid;
// Allow delete only if the uid matches (same user)
allow delete: if isSignedIn();// && request.auth.uid ==
resource.data.uid;
match /contacts/{contacts} {

allow create, read, update, delete: if isSignedIn();

https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 2/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc

}
}

Reverse Mapping of Contacts


The first correction we need is the logic for adding contacts. The new flow will be.

When A adds B as a contact, here’s what will happen:

1. Add B to /users/uid_A/contacts array.

2. Add A to /users/uid_B/contacts array.

3. Create a new Document at /chats/chat_id_ab .

4. Add B : chat_id_ab to /user/uid_A/chats map.

5. Add A : chat_id_ab to /users/uid_B/chats map.

Here’s the modifications in the ContactsBloc , and the ChatProvider and


UserDataProvider it uses.

1 Stream<ContactsState> mapAddContactEventToState(String username) async* {


2 try {
3 yield AddContactProgressState();
4 await userDataRepository.addContact(username);
5 User user = await userDataRepository.getUser(username);
6 await chatRepository.createChatIdForContact(user);
7 yield AddContactSuccessState();
8 } on MessioException catch (exception) {
9 print(exception.errorMessage());
10 yield AddContactFailedState(exception);
11 }
12 }

ContactsBloc.dart hosted with ❤ by GitHub view raw

1 @override
2 Future<void> addContact(String username) async {
3 User contactUser = await getUser(username);
4 //create a node with the username provided in the contacts collection
5 CollectionReference collectionReference =
6 fireStoreDb.collection(Paths.usersPath);
7 DocumentReference ref = collectionReference
https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 3/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc

8 .document(SharedObjects.prefs.getString(Constants.sessionUid));
9
10
11 //await to fetch user details of the username provided and set data
12 final documentSnapshot = await ref.get();
13 List<String> contacts = documentSnapshot.data['contacts'] != null
14 ? List.from(documentSnapshot.data['contacts'])
15 : List();
16 if (contacts.contains(username)) {
17 throw ContactAlreadyExistsException();
18 }
19 //add contact
20 contacts.add(username);
21 await ref.setData({'contacts': contacts}, merge: true);
22 //contact should be added in the contactlist of both the users. Adding to the secon
23 String sessionUsername = SharedObjects.prefs.getString(Constants.sessionUsername);
24 DocumentReference contactRef = collectionReference.document(contactUser.documentId)
25 final contactSnapshot = await contactRef.get();
26 contacts = contactSnapshot.data['contacts'] != null
27 ? List.from(contactSnapshot.data['contacts'])
28 : List();
29 if (contacts.contains(sessionUsername)) {
30 throw ContactAlreadyExistsException();
31 }
32 contacts.add(sessionUsername);
33 await contactRef.setData({'contacts': contacts}, merge: true);
34 }

UserDataProvider.dart hosted with ❤ by GitHub view raw

1 @override
2 Future<void> createChatIdForContact(User user) async {
3 String contactUid = user.documentId;
4 String contactUsername = user.username;
5 String uId = SharedObjects.prefs.getString(Constants.sessionUid);
6 String selfUsername = SharedObjects.prefs.getString(Constants.sessionUsername);
7 CollectionReference usersCollection = fireStoreDb.collection(Paths.usersPath);
8 DocumentReference userRef = usersCollection.document(uId);
9 DocumentReference contactRef = usersCollection.document(contactUid);
10 DocumentSnapshot userSnapshot = await userRef.get();
11 if(userSnapshot.data['chats']==null|| userSnapshot.data['chats'][contactUsername]==
12 String chatId = await createChatIdForUsers(selfUsername, contactUsername);
13 await userRef.setData({
14 'chats': {contactUsername: chatId}
15 },merge:true );
16 await contactRef.setData({
17 'chats': {selfUsername: chatId}
https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 4/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc
17 chats : {selfUsername: chatId}
18 },merge: true);
19 }
20 }
21 Future<String> createChatIdForUsers(
22 String selfUsername, String contactUsername) async {
23 CollectionReference collectionReference =
24 fireStoreDb.collection(Paths.chatsPath);
25 DocumentReference documentReference = await collectionReference.add({
26 'members': [selfUsername, contactUsername]
27 });
28 return documentReference.documentID;
29 }

ChatProvider.dart hosted with ❤ by GitHub view raw

Adding States and Events


In the last post we had built all the basic States and Events for the ChatBloc . We’ll
add more Events and States for PageChanged because we want to refresh the chat data
on page change. I’ve also made some changes in the ChatProvider . We’ve also created a

method to receive all the chats of the user as a stream. We’ll dispatch this event in
main.dart at the very start.

1 import 'dart:io';
2
3 import 'package:equatable/equatable.dart';
4 import 'package:file_picker/file_picker.dart';
5 import 'package:messio/models/Chat.dart';
6 import 'package:messio/models/Message.dart';
7 import 'package:meta/meta.dart';
8
9 @immutable
10 abstract class ChatEvent extends Equatable {
11 ChatEvent([List props = const <dynamic>[]]) : super(props);
12 }
13
14
15 //triggered to fetch list of chats
16 class FetchChatListEvent extends ChatEvent {
17 @override
18 String toString() => 'FetchChatListEvent';
19 }
20
21 //triggered when stream containing list of chats has new data
22 class ReceivedChatsEvent extends ChatEvent {
https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 5/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc

23 final List<Chat> chatList;


24
25 ReceivedChatsEvent(this.chatList);
26
27 @override
28 String toString() => 'ReceivedChatsEvent';
29 }
30
31 //triggered to get details of currently open conversation
32 class FetchConversationDetailsEvent extends ChatEvent {
33 final Chat chat;
34
35 FetchConversationDetailsEvent(this.chat) : super([chat]);
36
37 @override
38 String toString() => 'FetchConversationDetailsEvent';
39 }
40
41 //triggered to fetch messages of chat
42 class FetchMessagesEvent extends ChatEvent {
43 final Chat chat;
44
45 FetchMessagesEvent(this.chat) : super([chat]);
46
47 @override
48 String toString() => 'FetchMessagesEvent';
49 }
50
51 //triggered when messages stream has new data
52 class ReceivedMessagesEvent extends ChatEvent {
53 final List<Message> messages;
54
55 ReceivedMessagesEvent(this.messages) : super([messages]);
56
57 @override
58 String toString() => 'ReceivedMessagesEvent';
59 }
60
61 //triggered to send new text message
62 class SendTextMessageEvent extends ChatEvent {
63 final String message;
64
65 SendTextMessageEvent(this.message) : super([message]);
66
67 @override
68 String toString() => 'SendTextMessageEvent';
69 }
70
https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 6/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc
70
71 //triggered to send attachment
72 class SendAttachmentEvent extends ChatEvent {
73 final String chatId;
74 final File file;
75 final FileType fileType;
76
77 SendAttachmentEvent(this.chatId, this.file, this.fileType)
78 : super([chatId, file, fileType]);
79
80 @override
81 String toString() => 'SendAttachmentEvent';
82 }
83
84 //triggered on page change
85 class PageChangedEvent extends ChatEvent {
86 final int index;
87 final Chat activeChat;
88 PageChangedEvent(this.index, this.activeChat) : super([index, activeChat]);
89
90 @override
91 String toString() => 'PageChangedEvent';
92 }

Ch E d h d i h ❤ b Gi H b view raw

1 import 'package:equatable/equatable.dart';
2 import 'package:messio/models/Chat.dart';
3 import 'package:messio/models/Message.dart';
4 import 'package:messio/models/User.dart';
5 import 'package:meta/meta.dart';
6
7 @immutable
8 abstract class ChatState extends Equatable {
9 ChatState([List props = const <dynamic>[]]) : super(props);
10 }
11
12 class InitialChatState extends ChatState {}
13
14 class FetchedChatListState extends ChatState {
15 final List<Chat> chatList;
16
17 FetchedChatListState(this.chatList) : super([chatList]);
18
19 @override
20 String toString() => 'FetchedChatListState';
21 }
22
23 l F t h dM St t t d Ch tSt t {
https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 7/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc
23 class FetchedMessagesState extends ChatState {
24 final List<Message> messages;
25
26 FetchedMessagesState(this.messages) : super([messages]);
27
28 @override
29 String toString() => 'FetchedMessagesState';
30 }
31
32 class ErrorState extends ChatState {
33 final Exception exception;
34
35 ErrorState(this.exception) : super([exception]);
36
37 @override
38 String toString() => 'ErrorState';
39 }
40
41 class FetchedContactDetailsState extends ChatState {
42 final User user;
43
44 FetchedContactDetailsState(this.user) : super([user]);
45
46 @override
47 String toString() => 'FetchedContactDetailsState';
48 }
49
50 class PageChangedState extends ChatState {
51 final int index;
52 final Chat activeChat;
53 PageChangedState(this.index, this.activeChat) : super([index, activeChat]);
54
55 @override
56 String toString() => 'PageChangedState';
57 }

ChatState.dart hosted with ❤ by GitHub view raw

1 import 'dart:async';
2 import 'package:bloc/bloc.dart';
3
4 import 'package:messio/blocs/chats/Bloc.dart';
5 import 'package:messio/config/Constants.dart';
6 import 'package:messio/config/Paths.dart';
7 import 'package:messio/models/Message.dart';
8 import 'package:messio/models/User.dart';
9 import 'package:messio/repositories/ChatRepository.dart';
10 import 'package:messio/repositories/StorageRepository.dart';
https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 8/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc

11 import 'package:messio/repositories/UserDataRepository.dart';
12 import 'package:messio/utils/Exceptions.dart';
13 import 'package:messio/utils/SharedObjects.dart';
14
15 class ChatBloc extends Bloc<ChatEvent, ChatState> {
16 final ChatRepository chatRepository;
17 final UserDataRepository userDataRepository;
18 final StorageRepository storageRepository;
19 StreamSubscription messagesSubscription;
20 StreamSubscription chatsSubscription;
21 String activeChatId;
22 ChatBloc(
23 {this.chatRepository, this.userDataRepository, this.storageRepository})
24 : assert(chatRepository != null),
25 assert(userDataRepository != null),
26 assert(storageRepository != null);
27
28 @override
29 ChatState get initialState => InitialChatState();
30
31 @override
32 Stream<ChatState> mapEventToState(
33 ChatEvent event,
34 ) async* {
35 print(event);
36 if (event is FetchChatListEvent) {
37 yield* mapFetchChatListEventToState(event);
38 }
39 if (event is ReceivedChatsEvent) {
40 yield FetchedChatListState(event.chatList);
41 }
42 if (event is PageChangedEvent) {
43 activeChatId = event.activeChat.chatId;
44 }
45 if (event is FetchConversationDetailsEvent) {
46 dispatch(FetchMessagesEvent(event.chat));
47 yield* mapFetchConversationDetailsEventToState(event);
48 }
49 if (event is FetchMessagesEvent) {
50 yield* mapFetchMessagesEventToState(event);
51 }
52 if (event is ReceivedMessagesEvent) {
53 print(event.messages);
54 yield FetchedMessagesState(event.messages);
55 }
56 if (event is SendTextMessageEvent) {
57 Message message = TextMessage(
58 event.message,
https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 9/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc
58 event.message,
59 DateTime.now().millisecondsSinceEpoch,
60 SharedObjects.prefs.getString(Constants.sessionName),
61 SharedObjects.prefs.getString(Constants.sessionUsername));
62 await chatRepository.sendMessage(activeChatId, message);
63 }
64 if (event is SendAttachmentEvent) {
65 await mapPickedAttachmentEventToState(event);
66 }
67 }
68
69 Stream<ChatState> mapFetchChatListEventToState(
70 FetchChatListEvent event) async* {
71 try {
72 chatsSubscription?.cancel();
73 chatsSubscription = chatRepository
74 .getChats()
75 .listen((chats) => dispatch(ReceivedChatsEvent(chats)));
76 } on MessioException catch (exception) {
77 print(exception.errorMessage());
78 yield ErrorState(exception);
79 }
80 }
81
82 Stream<ChatState> mapFetchMessagesEventToState(
83 FetchMessagesEvent event) async* {
84 try {
85 yield InitialChatState();
86 String chatId =
87 await chatRepository.getChatIdByUsername(event.chat.username);
88 messagesSubscription?.cancel();
89 messagesSubscription = chatRepository
90 .getMessages(chatId)
91 .listen((messages) => dispatch(ReceivedMessagesEvent(messages)));
92 } on MessioException catch (exception) {
93 print(exception.errorMessage());
94 yield ErrorState(exception);
95 }
96 }
97
98 Stream<ChatState> mapFetchConversationDetailsEventToState(
99 FetchConversationDetailsEvent event) async* {
100 User user = await userDataRepository.getUser(event.chat.username);
101 print(user);
102 yield FetchedContactDetailsState(user);
103 }
104
105 Future mapPickedAttachmentEventToState(SendAttachmentEvent event) async {

https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 10/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc
106 String url = await storageRepository.uploadFile(
107 event.file, Paths.imageAttachmentsPath);
108 String username = SharedObjects.prefs.getString(Constants.sessionUsername);
109 String name = SharedObjects.prefs.getString(Constants.sessionName);
110 Message message = VideoMessage(
111 url, DateTime.now().millisecondsSinceEpoch, name, username);
112 await chatRepository.sendMessage(event.chatId, message);
113 }
114
115 @override
116 void dispose() {
117 messagesSubscription.cancel();
118 super.dispose();
119 }
120 }

1 import 'dart:async';
2
3 import 'package:cloud_firestore/cloud_firestore.dart';
4 import 'package:messio/config/Constants.dart';
5 import 'package:messio/config/Paths.dart';
6 import 'package:messio/models/Chat.dart';
7 import 'package:messio/models/Message.dart';
8 import 'package:messio/models/User.dart';
9 import 'package:messio/utils/SharedObjects.dart';
10
11 import 'BaseProviders.dart';
12
13 class ChatProvider extends BaseChatProvider {
14 final Firestore fireStoreDb;
15
16 ChatProvider({Firestore fireStoreDb})
17 : fireStoreDb = fireStoreDb ?? Firestore.instance;
18
19 @override
20 Stream<List<Chat>> getChats() {
21 String uId = SharedObjects.prefs.getString(Constants.sessionUid);
22 return fireStoreDb
23 .collection(Paths.usersPath)
24 .document(uId)
25 .snapshots()
26 .transform(StreamTransformer<DocumentSnapshot, List<Chat>>.fromHandlers(
27 handleData: (DocumentSnapshot documentSnapshot,
28 EventSink<List<Chat>> sink) =>
29 mapDocumentToChat(documentSnapshot, sink)));
30 }
31
https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 11/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc
31
32 void mapDocumentToChat(
33 DocumentSnapshot documentSnapshot, EventSink sink) async {
34 List<Chat> chats = List();
35 Map data = documentSnapshot.data['chats'];
36 if(data!=null){
37 data.forEach((key, value) => chats.add(Chat(key, value)));
38 sink.add(chats);
39 }
40 }
41
42 @override
43 Stream<List<Message>> getMessages(String chatId) {
44 DocumentReference chatDocRef =
45 fireStoreDb.collection(Paths.chatsPath).document(chatId);
46 CollectionReference messagesCollection =
47 chatDocRef.collection(Paths.messagesPath);
48 return messagesCollection
49 .orderBy('timeStamp', descending: true)
50 .snapshots()
51 .transform(StreamTransformer<QuerySnapshot, List<Message>>.fromHandlers(
52 handleData:
53 (QuerySnapshot querySnapshot, EventSink<List<Message>> sink) =>
54 mapDocumentToMessage(querySnapshot, sink)));
55 }
56
57 void mapDocumentToMessage(QuerySnapshot querySnapshot, EventSink sink) async {
58 List<Message> messages = List();
59 for (DocumentSnapshot document in querySnapshot.documents) {
60 messages.add(Message.fromFireStore(document));
61 }
62 sink.add(messages);
63 }
64
65 @override
66 Future<void> sendMessage(String chatId, Message message) async {
67 DocumentReference chatDocRef =
68 fireStoreDb.collection(Paths.chatsPath).document(chatId);
69 CollectionReference messagesCollection =
70 chatDocRef.collection(Paths.messagesPath);
71 messagesCollection.add(message.toMap());
72 await chatDocRef.updateData({'latestMessage': message.toMap()});
73 }
74
75 @override
76 Future<String> getChatIdByUsername(String username) async {
77 String uId = SharedObjects.prefs.getString(Constants.sessionUid);
78 String selfUsername =
https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 12/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc
79 SharedObjects.prefs.getString(Constants.sessionUsername);
80 DocumentReference userRef =
81 fireStoreDb.collection(Paths.usersPath).document(uId);
82 DocumentSnapshot documentSnapshot = await userRef.get();
83 String chatId = documentSnapshot.data['chats'][username];
84 if (chatId == null) {
85 chatId = await createChatIdForUsers(selfUsername, username);
86 userRef.updateData({
87 'chats': {username: chatId}
88 });
89 }
90 return chatId;
91 }
92 @override
93 Future<void> createChatIdForContact(User user) async {
94 String contactUid = user.documentId;
95 String contactUsername = user.username;
96 String uId = SharedObjects.prefs.getString(Constants.sessionUid);
97 String selfUsername = SharedObjects.prefs.getString(Constants.sessionUsername);
98 CollectionReference usersCollection = fireStoreDb.collection(Paths.usersPath);
99 DocumentReference userRef = usersCollection.document(uId);
100 DocumentReference contactRef = usersCollection.document(contactUid);
101 DocumentSnapshot userSnapshot = await userRef.get();
102 if(userSnapshot.data['chats']==null|| userSnapshot.data['chats'][contactUsername]=
103 String chatId = await createChatIdForUsers(selfUsername, contactUsername);
104 await userRef.setData({
105 'chats': {contactUsername: chatId}
106 },merge:true );
107 await contactRef.setData({
108 'chats': {selfUsername: chatId}
109 },merge: true);
110 }
111 }
112 Future<String> createChatIdForUsers(
113 String selfUsername, String contactUsername) async {
114 CollectionReference collectionReference =
115 fireStoreDb.collection(Paths.chatsPath);
116 DocumentReference documentReference = await collectionReference.add({
117 'members': [selfUsername, contactUsername]
118 });
119 return documentReference.documentID;
120 }
121 }

We now have the Bloc ready. Let’s wire it with the UI.

https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 13/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc

The Chat Page


When the user clicks on any contact he’ll be redirected to the ConversationPageSlide ,

which is basically a PageView holding all the chats, so that he can swipe to navigate.

1 @override
2 Widget build(BuildContext context) {
3 return GestureDetector(
4 onTap: () =>
5 BlocProvider.of<ContactsBloc>(context).dispatch(ClickedContactEvent(contact))
6 child: Container(
7 color: Palette.primaryColor,
8 child: Padding(
9 padding: const EdgeInsets.only(left: 30, top: 10, bottom: 10),
10 child: RichText(
11 text: TextSpan(
12 style: TextStyle(
13 fontSize: 14.0,
14 color: Colors.black,
15 ),
16 children: <TextSpan>[
17 TextSpan(text: contact.getFirstName()),
18 TextSpan(
19 text: ' ' + contact.getLastName(),
20 style: TextStyle(fontWeight: FontWeight.bold)),
21 ],
22 ),
23 ))),
24 );
25 }

ContactRowWidget.dart hosted with ❤ by GitHub view raw

In the ConversationPageSlide , we’ll receive the Contact , compare it with the

usernames in the ChatList we have, get the index and scroll the PageView to that
particular index.

1 import 'package:flutter/material.dart';
2 import 'package:flutter_bloc/flutter_bloc.dart';
3 import 'package:messio/blocs/chats/Bloc.dart';
4 import 'package:messio/models/Chat.dart';
5 import 'package:messio/models/Contact.dart';
6 import 'package:messio/widgets/InputWidget.dart';
7 import 'package:rubber/rubber.dart';
8 import 'ConversationBottomSheet.dart';
https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 14/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc
9 import 'ConversationPage.dart';
10
11 class ConversationPageSlide extends StatefulWidget {
12 final Contact startContact;
13
14 @override
15 _ConversationPageSlideState createState() =>
16 _ConversationPageSlideState(startContact);
17
18 const ConversationPageSlide({this.startContact});
19 }
20
21 class _ConversationPageSlideState extends State<ConversationPageSlide>
22 with SingleTickerProviderStateMixin {
23 var controller;
24 PageController pageController = PageController();
25 final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
26 final Contact startContact;
27 ChatBloc chatBloc;
28 List<Chat> chatList = List();
29 bool isFirstLaunch = true;
30
31 _ConversationPageSlideState(this.startContact);
32
33 @override
34 void initState() {
35 chatBloc = BlocProvider.of<ChatBloc>(context);
36 // chatBloc.dispatch(FetchChatListEvent());
37 controller = RubberAnimationController(
38 vsync: this,
39 );
40 super.initState();
41 }
42
43 @override
44 Widget build(BuildContext context) {
45 return SafeArea(
46 child: Scaffold(
47 key: _scaffoldKey,
48 body: Column(
49 children: <Widget>[
50 BlocListener<ChatBloc, ChatState>(
51 bloc: chatBloc,
52 listener: (bc, state) {
53 print('ChatList $chatList');
54 if (isFirstLaunch &&chatList.isNotEmpty) {
55 isFirstLaunch = false;
56 for (int i = 0; i < chatList.length; i++) {
https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 15/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc
( ; g ; ) {
57 if (startContact.username == chatList[i].username) {
58 BlocProvider.of<ChatBloc>(context)
59 .dispatch(PageChangedEvent(i, chatList[i]));
60 pageController.jumpToPage(i);
61 }
62 }
63 }
64 },
65 child: Expanded(child: BlocBuilder<ChatBloc, ChatState>(
66 builder: (context, state) {
67 if (state is FetchedChatListState)
68 chatList = state.chatList;
69 return PageView.builder(
70 controller: pageController,
71 itemCount: chatList.length,
72 onPageChanged: (index) =>
73 BlocProvider.of<ChatBloc>(context).dispatch(
74 PageChangedEvent(index, chatList[index])),
75 itemBuilder: (bc, index) =>
76 ConversationPage(chatList[index]));
77 },
78 ))),
79 Container(
80 child: GestureDetector(
81 child: InputWidget(),
82 onPanUpdate: (details) {
83 if (details.delta.dy < 0) {
84 _scaffoldKey.currentState
85 .showBottomSheet<Null>((BuildContext context) {
86 return ConversationBottomSheet();
87 });
88 }
89 }))
90 ],
91 )));
92 }
93
94 }

i i ❤ i view raw

1 import 'package:flutter/material.dart';
2 import 'package:flutter_bloc/flutter_bloc.dart';
3 import 'package:messio/blocs/chats/Bloc.dart';
4 import 'package:messio/config/Palette.dart';
5 import 'package:messio/models/Chat.dart';
6 import 'package:messio/widgets/ChatAppBar.dart';
7 import 'package:messio/widgets/ChatListWidget.dart';
https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 16/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc
p p g / g / g ;
8
9 class ConversationPage extends StatefulWidget {
10 final Chat chat;
11
12 @override
13 _ConversationPageState createState() => _ConversationPageState(chat);
14
15 const ConversationPage(this.chat);
16 }
17
18 class _ConversationPageState extends State<ConversationPage> {
19 final Chat chat;
20
21 _ConversationPageState(this.chat);
22 ChatBloc chatBloc;
23 @override
24 void initState() {
25 chatBloc = BlocProvider.of<ChatBloc>(context);
26 chatBloc.dispatch(FetchConversationDetailsEvent(chat));
27 super.initState();
28 }
29
30 @override
31 Widget build(BuildContext context) {
32 return Column(children: <Widget>[
33 Expanded(flex: 2, child: ChatAppBar()), // Custom app bar for chat screen
34 Expanded(
35 flex: 11,
36 child: Container(
37 color: Palette.chatBackgroundColor,
38 child: ChatListWidget(),
39 ))
40 ]);
41 }
42
43 }

ConversationPage.dart hosted with ❤ by GitHub view raw

We dispatch FetchConversationDetailsEvent from ChatAppBar and FetchMessagesEvent

from ChatListWidget to get the chat details and the messages respectively. The
ChatListWidget now looks something like this.

1 import 'package:flutter/material.dart';
2 import 'package:flutter_bloc/flutter_bloc.dart';
3 import 'package:messio/blocs/chats/Bloc.dart';
4 import 'package:messio/models/Message dart';
https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 17/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc
4 import 'package:messio/models/Message.dart';
5
6 import 'ChatItemWidget.dart';
7
8 class ChatListWidget extends StatefulWidget {
9 @override
10 _ChatListWidgetState createState() => _ChatListWidgetState();
11 }
12
13 class _ChatListWidgetState extends State<ChatListWidget> {
14 final ScrollController listScrollController = ScrollController();
15 List<Message> messages = List();
16 @override
17 Widget build(BuildContext context) {
18 // TODO: implement build
19 return BlocBuilder<ChatBloc,ChatState>(
20 builder: (context, state) {
21 print('chatlist');
22 print(state);
23 if(state is FetchedMessagesState){
24 messages = state.messages;
25 print(state.messages);
26 }
27 return ListView.builder(
28 padding: EdgeInsets.all(10.0),
29 itemBuilder: (context, index) => ChatItemWidget(messages[index]),
30 itemCount: messages.length,
31 reverse: true,
32 controller: listScrollController,
33 );
34 }
35 );
36 }
37 }
38

ChatListWidget.dart hosted with ❤ by GitHub view raw

The ChatAppBar is pretty basic, check Code Changes if you want to check it out. For
sending messages we dispatch the SendTextMessageEvent in InputWidget .

1
2 void sendMessage(context){
3 BlocProvider.of<ChatBloc>(context).dispatch(SendTextMessageEvent(textEditingControll
4 textEditingController.clear();
5 }

https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 18/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc
InputWidget.dart hosted with ❤ by GitHub view raw

Both sending and receiving of messages is done at this point. Here’s the end result.

So we are able to read and send messages now. But there are few issues:

On clicking the contact, it takes a little bit time before the data is returned, we’ll
need to implement a Local Storage to cache this data locally as well.
https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 19/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc

On swiping between the pages, the state of the page does not persist, they’re getting
rebuilt everytime. Also the messages get jumbled up between the pages. This also
needs to be fixed.

The TextField overflows the AppBar.

We’re loading all the messages at once, We’ll also need to implement pagination for
the messages.

I’ll fix these issues in the next post.

Hit me up in the comments below or Twitter or Linkedin or Instagram if you have any
queries or suggestions or just for a casual tech talk maybe? See you guys in the next
one!

Code Changes
#22 Wired ChatBloc with UI

How Can You Contribute?


Open issues with suggestion of better approaches or ideas for the app.

Connect with me on Twitter or Linkedin or Instagram.

Star the Github repository.

Share the series on Twitter.

Follow me on Github.

Posts In This Series


60 Days Of Flutter : Building a Messenger from Scratch

60 Days of Flutter : Day 1 : Creating the App

60 Days of Flutter : Day 2 : Setting Up A CI With Flutter

60 Days of Flutter : Day 3–4 : Building a Chat Screen in Flutter

60 Days of Flutter : Day 4–5 : Widget Testing With Flutter

https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 20/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc

60 Days of Flutter : Day 6–7 : Implementing a Slideable Widget Using Bottomsheet in


Flutter

60 Days of Flutter : Day 8 : Changing The Launcher Icon and Implementing


GestureDetector

60 Days of Flutter : Day 9–10–11 : Creating Awesome Register Screen in Flutter

60 Days of Flutter : Day 12–14 : Understanding BLoC Pattern in Flutter

60 Days of Flutter : Day 15–17 : Implementing Registration Screen using ‘flutter_bloc’

60 Days of Flutter : Day 18–19 : Unit Testing in Flutter using ‘ mockito’

60 Days of Flutter : Day 20–21 : Unit Testing a Bloc in Flutter

60 Days of Flutter : Day 22–23 : Building a Modern Contacts Page in Flutter

60 Days of Flutter : Day 24–26 : Building a Animated Progress Fab and the Contacts
Bloc in Flutter

60 Days of Flutter : Day 27–29 : Sending and Retrieving Messages from Firebase using
BLOC

60 Days of Flutter : Day 30–32 : Firebase Chat UI using Stream and Bloc

60 Days of Flutter : Day 33–35 : Paginating data from Firestore using Firebase Queries

60 Days of Flutter : Day 36–38 : Seamlessly Upload Files to Firebase Storage

60 Days of Flutter : Day 39–41 : One UI Inspired Attachments Showcase Page

60 Days of Flutter : Day 42–45 : Creating the Home Page & Quick Peek BottomSheet for
Messages

60 Days of Flutter : Day 45–47 : Adding Dark Mode to a Flutter App

60 Days of Flutter : Day 48–50 : Creating the Settings Page using Bloc

Show Your Support

https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 21/22
10/12/2019 60 Days of Flutter :Building a Messenger : Day 30–32 : Firebase Chat UI using Stream and Bloc

Press the clap button below if you liked reading this post. The more you clap the more it
motivates me to write better!

Firebase Flutter Android Technology Mobile App Development

About Help Legal

https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 22/22

You might also like