Kotlin ve Firebase ile Real-time Chat Uygulaması
mobile
,
android
,
kotlin
,
firebase
,
real-time-database
Modern bir Android mesajlaşma uygulaması geliştirmek istiyorsanız, doğru yerdesiniz! Bu yazıda Kotlin ve Firebase kullanarak gerçek zamanlı bir mesajlaşma uygulaması geliştireceğiz. Material Design 3, MVVM mimarisi ve Jetpack Compose kullanarak modern ve ölçeklenebilir bir uygulama ortaya çıkaracağız.
Neler Öğreneceğiz?
- Jetpack Compose ile modern UI geliştirme
- Firebase Authentication ile kullanıcı yönetimi
- Firebase Realtime Database ile gerçek zamanlı mesajlaşma
- Firebase Cloud Storage ile medya paylaşımı
- MVVM mimarisi ve Clean Architecture
- Dependency Injection (Hilt)
- Coroutines ve Flow
- Push Notifications
Proje Yapısı
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
chat-app/
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/com/mbkayihan/chatapp/
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── ChatApplication.kt
│ │ │ │ ├── di/
│ │ │ │ ├── domain/
│ │ │ │ ├── data/
│ │ │ │ ├── presentation/
│ │ │ │ └── utils/
│ │ │ └── res/
│ │ └── test/
│ └── build.gradle
└── build.gradle
Gerekli Bağımlılıklar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// app/build.gradle.kts
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.compose.ui:ui:1.5.4")
implementation("androidx.compose.material3:material3:1.1.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
implementation("androidx.navigation:navigation-compose:2.7.5")
// Firebase
implementation(platform("com.google.firebase:firebase-bom:32.7.0"))
implementation("com.google.firebase:firebase-auth-ktx")
implementation("com.google.firebase:firebase-database-ktx")
implementation("com.google.firebase:firebase-storage-ktx")
implementation("com.google.firebase:firebase-messaging-ktx")
// Dependency Injection
implementation("com.google.dagger:hilt-android:2.48")
kapt("com.google.dagger:hilt-compiler:2.48")
// Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3")
}
Firebase Kurulumu
- Firebase Console’dan yeni bir proje oluşturun
- Android uygulamanızı kaydedin
google-services.json
dosyasını indiripapp/
dizinine ekleyin- Authentication, Realtime Database ve Storage servislerini aktifleştirin
Veri Modelleri
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
data class User(
val id: String = "",
val name: String = "",
val email: String = "",
val photoUrl: String = "",
val status: String = "offline"
)
data class Message(
val id: String = "",
val senderId: String = "",
val receiverId: String = "",
val content: String = "",
val timestamp: Long = System.currentTimeMillis(),
val type: MessageType = MessageType.TEXT,
val mediaUrl: String = ""
)
enum class MessageType {
TEXT, IMAGE, FILE
}
Repository Katmanı
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
interface ChatRepository {
suspend fun sendMessage(message: Message): Result<Unit>
fun getMessages(chatId: String): Flow<List<Message>>
suspend fun uploadMedia(uri: Uri): Result<String>
fun getUserPresence(userId: String): Flow<Boolean>
suspend fun updateUserStatus(status: String): Result<Unit>
}
class ChatRepositoryImpl @Inject constructor(
private val database: FirebaseDatabase,
private val storage: FirebaseStorage,
private val auth: FirebaseAuth
) : ChatRepository {
override suspend fun sendMessage(message: Message): Result<Unit> = try {
val chatRef = database.getReference("chats")
.child(getChatId(message.senderId, message.receiverId))
chatRef.push().setValue(message).await()
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}
override fun getMessages(chatId: String): Flow<List<Message>> = callbackFlow {
val messagesRef = database.getReference("chats").child(chatId)
val listener = messagesRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val messages = snapshot.children.mapNotNull {
it.getValue<Message>()
}
trySend(messages)
}
override fun onCancelled(error: DatabaseError) {
close(error.toException())
}
})
awaitClose { messagesRef.removeEventListener(listener) }
}
private fun getChatId(userId1: String, userId2: String): String {
return if (userId1 < userId2) "$userId1-$userId2" else "$userId2-$userId1"
}
}
ViewModel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@HiltViewModel
class ChatViewModel @Inject constructor(
private val repository: ChatRepository,
private val auth: FirebaseAuth
) : ViewModel() {
private val _messages = MutableStateFlow<List<Message>>(emptyList())
val messages: StateFlow<List<Message>> = _messages.asStateFlow()
private val _uiState = MutableStateFlow<ChatUiState>(ChatUiState.Initial)
val uiState: StateFlow<ChatUiState> = _uiState.asStateFlow()
fun loadMessages(receiverId: String) {
viewModelScope.launch {
repository.getMessages(getChatId(auth.uid!!, receiverId))
.catch { error ->
_uiState.value = ChatUiState.Error(error.message)
}
.collect { messageList ->
_messages.value = messageList
_uiState.value = ChatUiState.Success
}
}
}
fun sendMessage(content: String, receiverId: String) {
viewModelScope.launch {
val message = Message(
senderId = auth.uid!!,
receiverId = receiverId,
content = content
)
repository.sendMessage(message)
.onSuccess {
_uiState.value = ChatUiState.MessageSent
}
.onFailure { error ->
_uiState.value = ChatUiState.Error(error.message)
}
}
}
}
sealed class ChatUiState {
object Initial : ChatUiState()
object Success : ChatUiState()
object MessageSent : ChatUiState()
data class Error(val message: String?) : ChatUiState()
}
UI Katmanı (Jetpack Compose)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
@Composable
fun ChatScreen(
viewModel: ChatViewModel = hiltViewModel(),
receiverId: String
) {
val messages by viewModel.messages.collectAsState()
val uiState by viewModel.uiState.collectAsState()
LaunchedEffect(Unit) {
viewModel.loadMessages(receiverId)
}
Scaffold(
topBar = {
ChatTopBar(receiverName = "John Doe") // Gerçek kullanıcı adını alın
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
// Mesaj Listesi
LazyColumn(
modifier = Modifier.weight(1f),
reverseLayout = true
) {
items(messages) { message ->
MessageItem(message)
}
}
// Mesaj Gönderme Alanı
MessageInput(
onSendMessage = { content ->
viewModel.sendMessage(content, receiverId)
}
)
}
}
}
@Composable
fun MessageItem(message: Message) {
val isOutgoing = message.senderId == FirebaseAuth.getInstance().uid
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = if (isOutgoing)
Arrangement.End else Arrangement.Start
) {
Surface(
shape = MaterialTheme.shapes.medium,
color = if (isOutgoing)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.surface,
tonalElevation = 2.dp
) {
Column(modifier = Modifier.padding(12.dp)) {
Text(
text = message.content,
style = MaterialTheme.typography.bodyLarge
)
Text(
text = formatTimestamp(message.timestamp),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.align(Alignment.End)
)
}
}
}
}
@Composable
fun MessageInput(
onSendMessage: (String) -> Unit
) {
var text by remember { mutableStateOf("") }
Surface(
tonalElevation = 3.dp,
modifier = Modifier.fillMaxWidth()
) {
Row(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
TextField(
value = text,
onValueChange = { text = it },
modifier = Modifier.weight(1f),
placeholder = { Text("Mesajınızı yazın...") },
colors = TextFieldDefaults.colors(
unfocusedContainerColor = MaterialTheme.colorScheme.surface
)
)
Spacer(modifier = Modifier.width(8.dp))
IconButton(
onClick = {
if (text.isNotBlank()) {
onSendMessage(text)
text = ""
}
}
) {
Icon(
imageVector = Icons.Default.Send,
contentDescription = "Gönder"
)
}
}
}
}
Push Notifications
Firebase Cloud Messaging ile bildirim gönderme:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MessagingService : FirebaseMessagingService() {
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
// Bildirim oluştur
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(message.data["title"])
.setContentText(message.data["body"])
.setSmallIcon(R.drawable.ic_notification)
.setAutoCancel(true)
.build()
// Bildirimi göster
NotificationManagerCompat.from(this)
.notify(System.currentTimeMillis().toInt(), notification)
}
override fun onNewToken(token: String) {
// Yeni token'ı sunucuya gönder
sendRegistrationToServer(token)
}
}
Medya Paylaşımı
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MediaRepository @Inject constructor(
private val storage: FirebaseStorage
) {
suspend fun uploadImage(uri: Uri): Result<String> = try {
val filename = "img_${System.currentTimeMillis()}.jpg"
val imageRef = storage.reference.child("images/$filename")
val uploadTask = imageRef.putFile(uri).await()
val downloadUrl = uploadTask.storage.downloadUrl.await()
Result.success(downloadUrl.toString())
} catch (e: Exception) {
Result.failure(e)
}
}
Sonuç
Bu yazıda modern bir Android mesajlaşma uygulamasının temel bileşenlerini inceledik:
- Firebase Authentication ile kullanıcı yönetimi
- Realtime Database ile gerçek zamanlı mesajlaşma
- Cloud Storage ile medya paylaşımı
- MVVM mimarisi ve Clean Architecture
- Jetpack Compose ile modern UI
- Push Notifications
Sonraki Adımlar
- Grup sohbetleri
- Sesli ve görüntülü arama
- Offline mesajlaşma desteği
- End-to-end encryption
- UI/UX iyileştirmeleri
Sorularınız veya önerileriniz varsa, yorum bırakabilirsiniz. Bir sonraki yazıda görüşmek üzere!