Project: Chat App
សាងសង់ Chat App
បង្កើត Real-time Chat Application ដោយប្រើ Firebase Cloud Firestore និង Firebase Authentication។ Project នេះបង្ហាញពី real-time data synchronization, user authentication, និង advanced UI patterns សម្រាប់ messaging apps។
🏗️ Real-Time Chat Architecture
Firebase Chat Architecture:
Chat App Architecture (Firebase)
┌────────────────────────────────────────┐
│ Presentation Layer │
│ - LoginScreen (Authentication UI) │
│ - UsersListScreen (All users) │
│ - ChatScreen (Messages + Input) │
│ - MessageBubble (Chat bubble widget) │
└───────────┬────────────────────────────┘
│
↓ User Actions & Real-time Updates
│
┌───────────┴────────────────────────────┐
│ Firebase Services Layer │
│ - Firebase Auth (User management) │
│ - Firestore (Real-time database) │
│ - Cloud Storage (Images/files) │
│ - FCM (Push notifications) │
└───────────┬────────────────────────────┘
│
↓ Real-time Synchronization
│
┌───────────┴────────────────────────────┐
│ Firestore Data Structure │
│ /users/{userId} │
│ - name, email, isOnline, avatar │
│ │
│ /chats/{chatId}/messages/{messageId} │
│ - senderId, text, timestamp │
│ │
│ chatId = sorted([user1Id, user2Id]) │
└────────────────────────────────────────┘
Real-time Flow:
1. User A sends message
2. Firestore.add() → Cloud
3. Firestore triggers update
4. User B's StreamBuilder receives
5. UI rebuilds automatically!
Why Firebase for Chat Apps?
Feature | Firebase Solution | Benefit for Chat |
---|---|---|
Real-time Sync | Firestore snapshots() | Messages appear instantly for all users |
Authentication | Firebase Auth | Secure login with email/Google/phone |
Offline Support | Firestore cache | Messages load even without internet |
Scalability | Auto-scaling | Handles millions of messages |
Security | Security Rules | Users can only read their own chats |
Cloud Storage | Firebase Storage | Store images, videos, files |
Push Notifications | FCM | Notify users of new messages |
🎯 មុខងារ (Features)
- 🔐 Authentication: ចុះឈ្មោះ និង ចូលដោយប្រើ email/password
- 👥 Users List: បង្ហាញអ្នកប្រើប្រាស់ទាំងអស់
- 💬 Real-time Messaging: ផ្ញើ និង ទទួលសារភ្លាមៗ
- 🟢 Online Status: បង្ហាញស្ថានភាព online/offline
- ⏰ Timestamps: បង្ហាញពេលវេលាផ្ញើសារ
- ✓✓ Read Receipts: ដឹងថាសារត្រូវបានអាន
- 🔄 Auto-scroll: Scroll to bottom when new message
- 💾 Offline Support: Messages cached locally
📝 Complete Chat Implementation
Below is the complete implementation with all features integrated:
// main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(ChatApp());
}
// Authentication Service
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
Future<void> signUp(String email, String password, String name) async {
try {
UserCredential userCredential = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
await _firestore.collection('users').doc(userCredential.user!.uid).set({
'name': name,
'email': email,
'createdAt': FieldValue.serverTimestamp(),
'isOnline': true,
});
} on FirebaseAuthException catch (e) {
throw Exception('Sign up failed: ' + e.message.toString());
}
}
Future<void> signIn(String email, String password) async {
try {
UserCredential userCredential = await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
await _firestore.collection('users').doc(userCredential.user!.uid).update({
'isOnline': true,
});
} on FirebaseAuthException catch (e) {
throw Exception('Login failed: ' + e.message.toString());
}
}
Future<void> signOut() async {
String? userId = _auth.currentUser?.uid;
if (userId != null) {
await _firestore.collection('users').doc(userId).update({
'isOnline': false,
'lastSeen': FieldValue.serverTimestamp(),
});
}
await _auth.signOut();
}
}
// Chat Screen
class ChatScreen extends StatefulWidget {
final String userId;
final String userName;
const ChatScreen({
required this.userId,
required this.userName,
});
@override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final TextEditingController _messageController = TextEditingController();
final FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
String get currentUserId => _auth.currentUser!.uid;
String getChatId() {
List<String> ids = [currentUserId, widget.userId];
ids.sort();
return ids.join('_');
}
Future<void> sendMessage() async {
if (_messageController.text.trim().isEmpty) return;
final chatId = getChatId();
await _firestore
.collection('chats')
.doc(chatId)
.collection('messages')
.add({
'senderId': currentUserId,
'receiverId': widget.userId,
'text': _messageController.text.trim(),
'timestamp': FieldValue.serverTimestamp(),
'isRead': false,
});
_messageController.clear();
}
@override
Widget build(BuildContext context) {
final chatId = getChatId();
return Scaffold(
appBar: AppBar(
title: Row(
children: [
CircleAvatar(
radius: 18,
child: Text(widget.userName[0]),
),
SizedBox(width: 12),
Text(widget.userName),
],
),
),
body: Column(
children: [
Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: _firestore
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timestamp', descending: true)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
final messages = snapshot.data!.docs;
if (messages.isEmpty) {
return Center(
child: Text('No messages yet'),
);
}
return ListView.builder(
reverse: true,
itemCount: messages.length,
itemBuilder: (context, index) {
final message = messages[index];
final isMe = message['senderId'] == currentUserId;
return MessageBubble(
text: message['text'],
isMe: isMe,
timestamp: message['timestamp']?.toDate(),
);
},
);
},
),
),
Container(
padding: EdgeInsets.all(8),
child: Row(
children: [
Expanded(
child: TextField(
controller: _messageController,
decoration: InputDecoration(
hintText: 'Type a message...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(24),
),
),
),
),
SizedBox(width: 8),
CircleAvatar(
child: IconButton(
icon: Icon(Icons.send),
onPressed: sendMessage,
color: Colors.white,
),
),
],
),
),
],
),
);
}
@override
void dispose() {
_messageController.dispose();
super.dispose();
}
}
// Message Bubble Widget
class MessageBubble extends StatelessWidget {
final String text;
final bool isMe;
final DateTime? timestamp;
const MessageBubble({
required this.text,
required this.isMe,
this.timestamp,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Align(
alignment: isMe ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.7,
),
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: isMe ? Colors.blue : Colors.grey[300],
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
text,
style: TextStyle(
color: isMe ? Colors.white : Colors.black,
),
),
if (timestamp != null)
Padding(
padding: EdgeInsets.only(top: 4),
child: Text(
timestamp!.hour.toString() + ':' +
timestamp!.minute.toString().padLeft(2, '0'),
style: TextStyle(
fontSize: 10,
color: isMe ? Colors.white70 : Colors.black54,
),
),
),
],
),
),
),
);
}
}
🔥 Firestore Security Rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if request.auth != null;
allow write: if request.auth.uid == userId;
}
match /chats/{chatId}/messages/{messageId} {
allow read: if request.auth != null &&
chatId.matches('.*' + request.auth.uid + '.*');
allow create: if request.auth != null &&
request.resource.data.senderId == request.auth.uid;
}
}
}
🚀 Advanced Features to Add
- 📸 Image Messaging: Upload and send images via Firebase Storage
- 👥 Group Chats: Multi-user conversations
- 🔔 Push Notifications: FCM for new message alerts
- ⌨️ Typing Indicator: Show when user is typing
- 🔍 Search Messages: Full-text search in chats
- 🎤 Voice Messages: Record and send audio
- 📎 File Sharing: Share documents and files
- ❤️ Message Reactions: Emoji reactions to messages
- 🗑️ Delete Messages: Remove sent messages
- ✏️ Edit Messages: Modify sent messages
💡 Best Practices:
- ✅ StreamBuilder: Use for real-time data updates
- ✅ Sorted Chat IDs: Prevent duplicate conversations
- ✅ Server Timestamps: Use FieldValue.serverTimestamp()
- ✅ Security Rules: Protect user privacy
- ✅ Offline Cache: Firestore handles offline automatically
- ✅ Dispose Controllers: Always clean up resources
- ✅ Pagination: Limit messages loaded (use .limit())
- ✅ Online Status: Update on login/logout
⚠️ Common Mistakes:
- ❌ Not sorting user IDs (creates duplicate chats)
- ❌ Using DateTime.now() instead of serverTimestamp
- ❌ No security rules (privacy vulnerability!)
- ❌ Forgetting to dispose TextEditingController
- ❌ Not handling loading/error states
- ❌ Sending empty messages (no validation)
- ❌ Not updating online status on logout
✅ លទ្ធផល: អ្នកបានបង្កើត Real-time Chat App ពេញលេញដោយប្រើ Firebase! Project នេះបង្ហាញពី real-time synchronization, authentication, StreamBuilder patterns, security rules។ បន្ថែម features ដូចជា group chat, voice/video calls, file sharing, typing indicators, push notifications។ នេះជា foundation ល្អសម្រាប់ production messaging apps!