Skip to content

Commit 3b928c7

Browse files
committed
update
1 parent 2a39bf5 commit 3b928c7

7 files changed

+546
-189
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## [0.0.47] - Chat functions
2+
3+
- Document update on chat functions.
4+
15
## [0.0.46] - Big break on user data events
26

37
This is going to be a big break.

README.md

+137-39
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ A free, open source, complete, rapid development package for creating Social app
6868
- [Forum Management](#forum-management)
6969
- [Forum Category Management](#forum-category-management)
7070
- [Developer Coding Guidelines](#developer-coding-guidelines)
71+
- [Preview of Development](#preview-of-development)
7172
- [General Setup](#general-setup)
7273
- [FireFlutter global variable](#fireflutter-global-variable)
7374
- [FireFlutter Initialization](#fireflutter-initialization)
@@ -117,10 +118,11 @@ A free, open source, complete, rapid development package for creating Social app
117118
- [Chat](#chat)
118119
- [Preview of chat functionality](#preview-of-chat-functionality)
119120
- [Firestore structure of chat](#firestore-structure-of-chat)
120-
- [Logic and Scenario of chat](#logic-and-scenario-of-chat)
121+
- [Common Scenario of Chat](#common-scenario-of-chat)
122+
- [Overview of chat functions](#overview-of-chat-functions)
121123
- [Pitfalls of chat logic](#pitfalls-of-chat-logic)
122124
- [Code of chat](#code-of-chat)
123-
- [Preparation for chat](#preparation-for-chat)
125+
- [Creating chat room list instace on global space](#creating-chat-room-list-instace-on-global-space)
124126
- [Chat Room List](#chat-room-list)
125127
- [Chat room](#chat-room)
126128
- [Begin chat with a user](#begin-chat-with-a-user)
@@ -1162,6 +1164,11 @@ To create a creategory,
11621164

11631165
# Developer Coding Guidelines
11641166

1167+
## Preview of Development
1168+
1169+
- As you may know, Firebase permission denied exception would cause a break as an `Unhandled Exception` on `VSCode` and it is the nature of fireflutter to work with permission denied exception. For instance, when the app meets permission denied error happens, the app would do something else. But the editor(like VScode) may produce `Uncaught Exception` and would stop the runtime pointing where it happens. This is normal. You can simply continue the runtime.
1170+
- When you work with firebase, you should have something in mind that firebase works on offline and that causes reading twice.
1171+
11651172
## General Setup
11661173

11671174
- Add latest version of [FireFlutter](https://pub.dev/packages/fireflutter) in pubspec.yaml
@@ -2094,23 +2101,24 @@ The settings are
20942101

20952102
# Chat
20962103

2104+
(Revised on Dec 30, 2020)
2105+
20972106
If you are looking for a package that support a complete chat functionality, fireflutter is for you. Unlike other sample (tutorial) code on the Internet, it has really complete features like
20982107

2099-
- listing my room list (chat list or friend list).
2100-
- group chat
2101-
- creating chat room with one user or multip users.
2102-
- adding user.
2103-
- blocking user.
2104-
- search user.
2105-
- sending photo
2106-
- changing settings of the room like room title update.
2107-
- and much more.
2108+
- Listing my room list (chat list or friend list).
2109+
- Listening my room events globally ( or only in chat room list ).
2110+
- Group chat (It can be one and one chat ).
2111+
- Creating chat room with one user or multip users.
2112+
- Adding a user(or multiple users) to existing room.
2113+
- Blocking user.
2114+
- Changing settings of the room like room title update.
2115+
- And much more.
21082116

21092117
## Preview of chat functionality
21102118

2111-
- All chat functionality works on user login. That means, to use chat, the user must logged in.
2119+
- User must login to use chat functionalities.
21122120
- Most of chat related methods throw permission error when a user tries something that is not permitted.
2113-
- To use chat functionality, `openProfile` option of `ff.init()` must be set to access user's displayName and photoURL.
2121+
- ~~To use chat functionality, `openProfile` option of `ff.init()` must be set to access user's displayName and photoURL.~~ TODO: https://github.com/thruthesky/fireflutter/issues/24
21142122

21152123
## Firestore structure of chat
21162124

@@ -2119,24 +2127,24 @@ Before we begin, let's put an assumption.
21192127
- There are 4 users. User `A`, user `B`, user `C`, and user `D`. User A is the moderator which means he is the one who created the room.
21202128
- The room that moderator A created is `RoomA` and there are two users in the room. User A, B.
21212129
- The UIDs of user A, B, C, D is A, B, C, D respectively.
2122-
- The room id of RoomA is RoomA.
2130+
- The room id of `RoomA` is `RoomA`.
21232131

21242132
Firestore structure and its data are secured by Firestore security rules.
21252133

2126-
- `/chat/info/room-list/{roomId}` is where each room information(setting) is stored. It's called global room list.
2127-
- When a room is created, the `roomId` will be autogenrated by Firestore by adding a document under global room list collection - `/chat/info/room-list`.
2134+
- `/chat/global/room-list/{roomId}` is where each room information(setting) is stored. It's called global room list.
2135+
- When a room is created, the `roomId` will be autogenrated by Firestore by adding a document under global room list collection - `/chat/global/room-list`.
21282136
- Document properties
21292137
- `moderators` is an array of user's uid. Users in this array are the moderators.
21302138
- `users` is an array of participant users' uid.
21312139
- `blockedUsers` is an array of blocked users' uid.
21322140
- `createdAt` has the time when the chat room was created.
21332141
- `title` is the title of chat room.
21342142
- When `{users: [ ... ]}` is updated to add or remove user, other properties cannot be edited.
2135-
- `/chat/my-room-list/{uid}/{roomId}` is where each user's room list are stored. The document has information about the room and last message. It's called private room.
2136-
- If a user has unread messages, it has no of new messages.
2143+
- `/chat/my-room-list/{uid}/{roomId}` is where each user's chat room information is stored. It's called private room. The document has the room information inlcuding last message of the room.
2144+
- If a user has unread messages, it has the number of new messages.
21372145
- It has last message information of who sent what, time, and more.
2138-
- It may have information about who added, blocked.
2139-
When A chats in RoomA, the (last) message goes to `/chat/info/room-list/RoomA` and to all the users in the room which are
2146+
- It may have information about who were added or blocked.
2147+
When A chats in RoomA, the (last) message goes to `/chat/global/room-list/RoomA` and to all the users in the room which are
21402148
- `/chat/my-room-list/A/RoomA`
21412149
- `/chat/my-room-list/B/RoomA`
21422150
- Document properties
@@ -2153,55 +2161,71 @@ Firestore structure and its data are secured by Firestore security rules.
21532161
}
21542162
```
21552163

2156-
- `/chat/messages/{roomId}/{message}` is where all the chat messages for the room are stored.
2164+
- `/chat/messages/{roomId}/{message}` is where all the chat messages for each chat room are stored.
2165+
2166+
- By default, when a user begins to chat with another user, it will always create a new room. If `hatch: false` option is given, it may not create a new room for the same users, instead it will use the exising room.
2167+
2168+
## Common Scenario of Chat
21572169

2158-
- By default, when a user begins to chat with another user, it will always create a new room. If `hatch` option is given, it may not create a new room for the same users.
2170+
- User may enter(create) many chat rooms.
2171+
- The app needs to get(or realtime update) all of login user's chat room in `/chat/my-room-list/{uid}`.
2172+
- And the app needs to listen to all of login user's incomng chat room events.
2173+
- And apply to the list.
2174+
2175+
- When the user touches on the room of the room list, the user enters the room
2176+
- and gets some of last messages of the room
2177+
- and listen to the events of the room and display
2178+
2179+
- The user can create a new chat room with other users.
2180+
- The user can add another users.
2181+
- If the user is one of the moderators, he can kickout/block other users.
2182+
2183+
2184+
## Overview of chat functions
21592185

2160-
## Logic and Scenario of chat
21612186

21622187
- User who begin(or create) to chat becomes the moderator.
21632188
- Moderator can add another moderator.
2164-
- When a user enters `chat room list screen`, the app should display all of the user's chat room list. It is a recommended but costomisable.
2165-
- User may search another user by openning a `user search screen` and select a user (or multiple users) to begin chat. Then the app should redirect the user to `chat room screen` when the use chosen other users to begin chat.
2189+
- When a user enters `chat room list screen`, the app should display all of the user's chat room list. It is a recommended but customisable.
2190+
- User may search another user by openning a `user search screen` and select a user (or multiple users) to begin chat. Then the app should redirect the user to `chat room screen` when the login user chosen other users to begin chat.
21662191
- User can enter chat room by selecting a chat room in his chat room list.
21672192
- User can add other users by selecting add user button in the chat room.
21682193
- User can create a chat room with the same user(s) over again. That means, A can begin chat with B by creating a room. And then, A can begin chat with B again by creating another room. `hatch` option can prevent creating new room upon creating a chat room with same users.
21692194
- When a room is created, `ChatProtocol.roomCreated` message will devlivered to all users.
21702195
- This protocol message can be useful to display that there is no more messages or this is the first message when user scrolls up to view previous messages.
21712196
- When a user is added, `ChatProtocol.enter` message (with user information) will devlivered to all users and property `users` has the names of the addedusers.
21722197
- When a user leaves a room, `ChatProtocol.leave` message (with user information) will devlivered to all users and property `userName` has the name of the left user.
2173-
- When a user is blocked, `ChatProtocol.block` message will (with user information) devlivered to all users. Only moderator can blocks a user and the user's uid will be saved in `{ blockedUsers: [ ... ]}` array. And `users` will hold the names of bloked users.
2198+
- When a user is blocked, `ChatProtocol.block` message will (with user information) devlivered to all users. Only moderator can blocks a user and the user's uid will be saved in `{ blockedUsers: [ ... ]}` array. And `users` property will hold the names of bloked users.
21742199
- When a room is created or a user is added, protocol message will be delivered to newly added users. And the room list should be appears on their room list.
21752200
- Blocked users will not be added to the room until moderator remove the user from `{ blockedUsers: [ ... ]}` array.
21762201
- When a user(or a moderator) leaves the room and there is no user left in the room, then that's it. The chat room is left as ghost chat room.
21772202
- When a user logs out or logs into another account while listening room list will causes permission error. Especially on testing, you would not open chat screen since testing uses several accounts at the same time.
21782203
- Logically, a user can search himself on search screen and begin chat with himself. You may add some logic to prevent it if you want.
21792204
- When a user is blocked by moderator, the user received no more messages except the `ChatProtocol.blocked` message.
2205+
- Chat protocol messages should be translated into user's language by the translation functionality. You may customise to translate it by yourself.
21802206

21812207
- You would code like below to enter a chat room.
21822208

21832209
- if `id` (as chat room id) is given, it will enter the chat room and listens all the event of the room.
21842210
- Or if `id` is null, then a room will be created with the `users` of UIDs list.
21852211
- If both of `id` and `users` are null(or empty), then a room will be created without any users except the login user himself. He will be alone in the room.
2186-
- If both of `id` and `users` have value, then, it enters the room if the room of the `id` exists. Or it will create a room with the `id` and with the users.
2187-
- This will be a good option for 1:1 chat. If the app only allows 1:1 chat, or the user chats to admin for help, this will be a good option.
2188-
- The `id` can be an md5 string of the login user's uid(A) and other user's uid(B).
2189-
- When it creates the room, it will create a room for A and B, and next time A or B try to chat each other again, it will not create a new room. Instead, it will use previously created room.
2212+
21902213

21912214
- If the app must inform new messages to the user when the user is not in room list screen,
21922215

2193-
- The app can listen `my-room-list` collection on app screen (or homescreen)
2216+
- The app can listen `my-room-list` collection on app screen (or homescreen, or in global space).
21942217
- And when a new message arrives, the app can show snackbar.
21952218

21962219
- You may put the logic of the app like below
21972220
- Declare `ChatMyRoomList` and `ChatRoom` instances as global variables.
21982221
- Listen to chat room update on home screen and display updates on chat icon.
21992222
- When somebody chats, the user will get push notification. and ignore push notifications if it's my chat or someone that I am talking to.
22002223

2224+
22012225
## Pitfalls of chat logic
22022226

22032227
- User cannot remove(or block) another user. Only moderator can do it.
2204-
- A add B.
2228+
- A adds B.
22052229
- B goes offline.
22062230
- B add C. Since Firebase works offline, B can still add user C even if he has no connection.
22072231
- A add D.
@@ -2212,18 +2236,92 @@ Firestore structure and its data are secured by Firestore security rules.
22122236

22132237
## Code of chat
22142238

2215-
### Preparation for chat
2239+
### Creating chat room list instace on global space
22162240

2217-
- By default, app can search users by name in `/meta/user/public/{uid}`. You may extend to search by gender and age.
2218-
- And it requires `openProfile` option to be set in `fireflutter` initialization like below.
2219-
- This option updates user's profile name and photo under `/meta/user/public/{uid}` and a user can search other users name and photo.
2241+
- Depending on your app structure, you may declare login user's chat room list instance in global scope.
2242+
- The declaration below could be a separate service(library) file.
22202243

22212244
```dart
2222-
ff.init({
2223-
'openProfile': true,
2224-
})
2245+
final FireFlutter ff = FireFlutter();
2246+
2247+
/// [myRoomList] is the instance of ChatMyRoomList. It will be instanciated in
2248+
/// main.dart
2249+
ChatMyRoomList myRoomList;
2250+
2251+
/// [myRoomListChanges] will be fired whenever/whatever events happens for my
2252+
/// chat room list.
2253+
BehaviorSubject myRoomListChanges = BehaviorSubject.seeded(null);
2254+
2255+
/// [chat] is the chat room instance.
2256+
///
2257+
/// The reason why it is declared in global scope is that the app needs to know
2258+
/// if the user is in a chat room. So, when he gets a push notification from the
2259+
/// chat room where he is in, the push messge will be ignored.
2260+
ChatRoom chat;
22252261
```
22262262

2263+
- Then, you need to create an instance of `ChatMyRoomList` in your main.dart (or in root screen) like below.
2264+
2265+
```dart
2266+
// When user login/logout. (Update my room list when user change account.)
2267+
ff.authStateChanges.listen((user) {
2268+
// When user is not logged in, or logged out, clear the chat room list.
2269+
if (user == null) {
2270+
if (myRoomList != null) {
2271+
myRoomList.leave();
2272+
myRoomList = null;
2273+
}
2274+
return;
2275+
}
2276+
2277+
// When user just logged in, set room list.
2278+
if (myRoomList == null) {
2279+
myRoomList = ChatMyRoomList(
2280+
inject: ff,
2281+
render: () {
2282+
// When there are changes(events) on my chat room list,
2283+
// notify to listeners.
2284+
myRoomListChanges.add(myRoomList.rooms);
2285+
},
2286+
);
2287+
}
2288+
});
2289+
```
2290+
2291+
2292+
- Then, on chat room list screen(create one if you don't have), add the following code to list my chat room list.
2293+
2294+
2295+
- Then, on chat room, you can do the following.
2296+
- The thing you want to focus is that the app needs to create an instance of `ChatRoom` and enter into it.
2297+
2298+
2299+
```dart
2300+
chat = ChatRoom(
2301+
inject: ff,
2302+
render: () {
2303+
setState(() {});
2304+
if (chat.messages.isNotEmpty) {
2305+
if (chat.page == 1) {
2306+
scrollToBottom(ms: 10);
2307+
} else if (atBottom) {
2308+
scrollToBottom();
2309+
}
2310+
}
2311+
},
2312+
globalRoomChange: () {
2313+
// print('global room change');
2314+
},
2315+
);
2316+
try {
2317+
await chat.enter(id: args['roomId'], users: [args['uid']], hatch: false);
2318+
} catch (e) {
2319+
app.error(e);
2320+
}
2321+
```
2322+
2323+
2324+
22272325
### Chat Room List
22282326

22292327
- On chat room list screen, you may code like below

lib/base.dart

+31-16
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,17 @@ class Base {
44
/// Check if Firebase has initialized.
55
bool isFirebaseInitialized = false;
66

7-
/// Fires after Firebase has initialized or if already initialized.
8-
/// The true event will be fired only once when Firebase initialized.
7+
/// Firebase intialized event
8+
///
9+
/// This event is fires after Firebase has initialized(created instance) or if
10+
/// it has already initialized.
11+
///
12+
/// When this event is fired, `initUser()` is already called which means,
13+
/// `authStateChanages` and `userChanges` are ready to be consumed.
14+
///
15+
/// The arguemnt will be true only after when Firebase initialized.
16+
///
17+
// ignore: close_sinks
918
BehaviorSubject<bool> firebaseInitialized = BehaviorSubject.seeded(false);
1019

1120
/// Returns Firestore instance. Firebase database instance.
@@ -50,22 +59,28 @@ class Base {
5059

5160
bool enableNotification;
5261

53-
/// [authStateChange] is a link to `FirebaseAuth.instance.authStateChanges()`
62+
/// [authStateChange] is fired whenever user logs in or out.
5463
///
55-
/// Use this to know if the user has logged in or not.
64+
/// Use this to know if the user has logged in or not. Since this is
65+
/// `BehaviorSubject` that delivers Firebase `User` object, it can be used
66+
/// outside of `ff.firebaseInitialized.listen()`
5667
///
5768
/// You can do the following with [authStateChanges]
58-
/// ```
69+
/// ```dart
5970
/// StreamBuilder(
6071
/// stream: ff.authStateChanges,
6172
/// builder: (context, snapshot) { ... });
6273
/// ```
63-
Stream<User> authStateChanges;
74+
///
75+
/// ```dart
76+
/// ff.authStateChanges.listen((user) { ... }
77+
/// ```
78+
BehaviorSubject<User> authStateChanges = BehaviorSubject.seeded(null);
6479

65-
/// [userChange] is a simple alias of `FirebaseAuth.instance.userChanges()`
80+
/// [userChanges] is a simple alias of `FirebaseAuth.instance.userChanges()`
6681
///
6782
/// It will be fired when user changes `dispolayName` or `photoURL`.
68-
Stream<User> userChange;
83+
Stream<User> userChanges;
6984

7085
/// Firebase User instance
7186
///
@@ -183,11 +198,12 @@ class Base {
183198
String get photoURL => user == null ? null : user.photoURL;
184199

185200
initUser() {
186-
authStateChanges = FirebaseAuth.instance.authStateChanges();
187-
userChange = FirebaseAuth.instance.userChanges();
201+
userChanges = FirebaseAuth.instance.userChanges();
188202

189203
/// Note: listen handler will called twice if Firestore is working as offline mode.
190-
authStateChanges.listen((User user) {
204+
FirebaseAuth.instance.authStateChanges().listen((User user) {
205+
authStateChanges.add(user);
206+
191207
/// [userChange] event fires when user is logs in or logs out.
192208
// userChange.add(UserChangeData(UserChangeType.auth, user: user));
193209

@@ -231,17 +247,16 @@ class Base {
231247

232248
/// Initialize Firebase
233249
///
234-
/// Firebase is initialized asynchronously. It does not block the app by async/await.
250+
/// Firebase is initialized asynchronously. Meaning, it does not block the app
251+
/// while it's intializaing.
252+
///
253+
/// Invoked by `ff.init()`.
235254
initFirebase() {
236255
// WidgetsFlutterBinding.ensureInitialized();
237256
return Firebase.initializeApp().then((firebaseApp) {
238-
isFirebaseInitialized = true;
239-
firebaseInitialized.add(isFirebaseInitialized);
240257
FirebaseFirestore.instance.settings =
241258
Settings(cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED);
242259

243-
usersCol = FirebaseFirestore.instance.collection('users');
244-
postsCol = FirebaseFirestore.instance.collection('posts');
245260
return firebaseApp;
246261
});
247262
}

0 commit comments

Comments
 (0)