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
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:
We’ll be making these corrections and also add more Events and States to the ChatBloc
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!
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} {
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
}
}
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 }
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 }
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
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 }
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
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 }
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 }
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
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.
We’re loading all the messages at once, We’ll also need to implement pagination for
the messages.
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
Follow me on Github.
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 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 42–45 : Creating the Home Page & Quick Peek BottomSheet for
Messages
60 Days of Flutter : Day 48–50 : Creating the Settings Page using Bloc
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!
https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-30-32-firebase-chat-ui-using-stream-and-bloc-5d0e5f3e914b 22/22