이번 글은 안드로이드를 공부하고, 프로젝트를 진행하면서 필요한 기능 중 하나인 채팅 기능을
쉽게 구현할 수 있는 Stream사의 Stream-chat-android 라이브러리를 사용하여 채팅 시스템을 구현해볼 것이다.
기존의 다른 해당 라이브러리를 사용한 글들이나, 회사에서 설명해주는 글과는 다르게
현재 Android Studio에서 지원하는 Kotlin DSL을 통하여 세팅해줄 예정이다.
1. Project 생성
2. settings.gradle.kts & build.gradle.kts 설정
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io")}
}
}
rootProject.name = "Chat_Practice"
include(":app")
기존의 setting.gradle 파일에 'Jitpack'을 추가한다.
해당 SDK는 MavenCentral에서 제공되며,
일부 종속성이 Jitpack에서 호스팅 되기 때문에 해당 내용을 꼭 추가해주어야 한다.
dependencies {
implementation("androidx.activity:activity-ktx:1.8.1")
implementation ("io.getstream:stream-chat-android-ui-components:6.0.2")
implementation ("io.getstream:stream-chat-android-offline:6.0.2")
implementation ("io.coil-kt:coil:2.4.0")
}
다음으로 build.gradle으로 이동하여 Stream Chat SDK와 Coil을 프로젝트에 종속성을 추가해준다.
또한, ViewBinding 설정까지 해준 후, 동기화 해준다.
해당 설정까지 완료해주면 앱을 만들어보기 전 기본적인 사전 설정은 끝이다.
3. Stream Chat Dashboard에서 API Key 추출
Stream 사이트에 들어가서 Account를 만들고(Github를 통해서도 가능) DashBoard에 들어가면
다음과 같은 화면을 볼 수 있는데,
여기서 Create App을 클릭하여 우리가 만들 앱에 대한 정보를 입력할 수 있다.
Feeds Server Location과 Chat Data Storage Location은 말그대로 서버 지역에 관한 설정이고,
위에 보듯이, 최초 1회에 한하여 설정이 가능하다고 하다.
많은 국내 앱을 서비스 하는 곳들은 대부분 Tokyo 리전을 사용하고 있다고 한다.
이제 해당 앱을 등록하여 DashBoard에 들어오면
Overview에서 해당 앱에 대한 Key를 확인하는 것이 가능하고,
Explorer을 통하여 Channel, User, Message에 대한 설정을 해주는 것이 가능하다.
해당 화면에서 Chatting Group 타입, Realtime Chatting 등 기능에 대한 전반적인 설정이 가능하며,
이번 예제는 Authenication을 사용하지 않기 때문에, 다음과 같이 비활성화 해주었다.
4. MainActivity 설정
MainActivity에서는 채팅의 목록, 즉 현재 사용자에게 활성화 되어있는 채널을 리스트로 구현하는 곳이다.
Stream에서는 메시지 인터페이스를 쉽게 구현하기 위해,
하위 수준 클라이언트, 오프라인 지원 라이브러리, UI를 제공해주기 때문에, 채널 목록 레이아웃 세팅이 간단하다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<io.getstream.chat.android.ui.feature.channels.list.ChannelListView
android:id="@+id/channelListView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
그냥 일반적인 CustomView를 하나 집어넣는다는 느낌으로 집어넣고 나면, 우리가 만들고 있을 때는 보이지 않지만,
앱을 구동했을 때, 위에서 보여준 채팅목록 화면이 나오게 된다.
그 후에, MainActivity는 다음과 같이 설정할 수 있다.
package com.woojugoing.chat_practice
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels
import com.woojugoing.chat_practice.databinding.ActivityMainBinding
import io.getstream.chat.android.client.ChatClient
import io.getstream.chat.android.client.logger.ChatLogLevel
import io.getstream.chat.android.models.Filters
import io.getstream.chat.android.models.User
import io.getstream.chat.android.offline.plugin.factory.StreamOfflinePluginFactory
import io.getstream.chat.android.state.plugin.config.StatePluginConfig
import io.getstream.chat.android.state.plugin.factory.StreamStatePluginFactory
import io.getstream.chat.android.ui.viewmodel.channels.ChannelListViewModel
import io.getstream.chat.android.ui.viewmodel.channels.ChannelListViewModelFactory
import io.getstream.chat.android.ui.viewmodel.channels.bindView
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val offlinePluginFactory = StreamOfflinePluginFactory(appContext = this)
val statePluginFactory = StreamStatePluginFactory(
config = StatePluginConfig(backgroundSyncEnabled = true, userPresence = true),
appContext = this
)
val user = User(
id = "tutorial-droid2",
name = "Tutorial Droid2",
image = "https://bit.ly/2TIt8NR"
)
val client = ChatClient.Builder("<API KEY>", applicationContext)
.withPlugins(offlinePluginFactory, statePluginFactory)
.logLevel(ChatLogLevel.ALL)
.build()
client.connectUser(user = user, token = client.devToken(user.id)).enqueue() {
if (it.isSuccess) {
val filter = Filters.and(
Filters.eq("type", "messaging"),
Filters.`in`("members", listOf(user.id))
)
val viewModelFactory = ChannelListViewModelFactory(
filter,
ChannelListViewModel.DEFAULT_SORT
)
val viewModel: ChannelListViewModel by viewModels { viewModelFactory }
viewModel.bindView(binding.channelListView, this)
binding.channelListView.setChannelItemClickListener { channel ->
startActivity(ChannelActivity.newIntent(this, channel))
}
}
}
}
}
StreamOfflinePluginFactory 클래스를 통하여 오프라인 지원에도 제공되게 하며,
ChatClient에 API키를 등록하여 초기화하여 Stream과 연결시킨다.
또한, 사용자를 인증하기 위해서 User의 인스턴스를 생성하고, 사용자 Token과 함께 전달하고,filter는 시간순으로 정렬해주는 변수이다.
해당 화면을 다 만들었을 때, 아직 만들지 않은 ChannelActivity을 만들어보자.
5. ChannelActivity 설정
여기도 레이아웃 같은 경우는 지원해주는 CustomView를 사용한다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<io.getstream.chat.android.ui.feature.messages.header.MessageListHeaderView
android:id="@+id/messageListHeaderView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<io.getstream.chat.android.ui.feature.messages.list.MessageListView
android:id="@+id/messageListView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/messageComposerView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/messageListHeaderView" />
<io.getstream.chat.android.ui.feature.messages.composer.MessageComposerView
android:id="@+id/messageComposerView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
후에 ChannelActivity에 대한 설정이다.
package com.woojugoing.chat_practice
import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.addCallback
import androidx.activity.viewModels
import com.woojugoing.chat_practice.databinding.ActivityChannelBinding
import io.getstream.chat.android.models.Channel
import io.getstream.chat.android.ui.common.state.messages.Edit
import io.getstream.chat.android.ui.common.state.messages.MessageMode
import io.getstream.chat.android.ui.viewmodel.messages.MessageComposerViewModel
import io.getstream.chat.android.ui.viewmodel.messages.MessageListHeaderViewModel
import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModel
import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModelFactory
import io.getstream.chat.android.ui.viewmodel.messages.bindView
class ChannelActivity : AppCompatActivity() {
private lateinit var binding: ActivityChannelBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityChannelBinding.inflate(layoutInflater)
setContentView(binding.root)
val cid = checkNotNull(intent.getStringExtra(CID_KEY)) {
"채널 활동을 시작할 때, 채널의 ID 지정이 필요합니다."
}
val factory = MessageListViewModelFactory(this, cid)
val messageListHeaderViewModel: MessageListHeaderViewModel by viewModels { factory }
val messageListViewModel: MessageListViewModel by viewModels { factory }
val messageComposerViewModel: MessageComposerViewModel by viewModels { factory }
messageListHeaderViewModel.bindView(binding.messageListHeaderView, this)
messageListViewModel.bindView(binding.messageListView, this)
messageComposerViewModel.bindView(binding.messageComposerView, this)
messageListViewModel.mode.observe(this) { mode ->
when (mode) {
is MessageMode.MessageThread -> {
messageListHeaderViewModel.setActiveThread(mode.parentMessage)
messageComposerViewModel.setMessageMode(
MessageMode.MessageThread(mode.parentMessage)
)
}
is MessageMode.Normal -> {
messageListHeaderViewModel.resetThread()
messageComposerViewModel.leaveThread()
}
}
}
binding.messageListView.setMessageEditHandler { message ->
messageComposerViewModel.performMessageAction(Edit(message))
}
messageListViewModel.state.observe(this) { state ->
if (state is MessageListViewModel.State.NavigateUp) {
finish()
}
}
val backHandler = {
messageListViewModel.onEvent(MessageListViewModel.Event.BackButtonPressed)
}
binding.messageListHeaderView.setBackButtonClickListener(backHandler)
onBackPressedDispatcher.addCallback(this) { backHandler() }
}
companion object {
private const val CID_KEY = "key:cid"
fun newIntent(context: Context, channel: Channel): Intent = Intent(
context, ChannelActivity::class.java
).putExtra(CID_KEY, channel.cid)
}
}
해당 화면을 구성하기 위해서는 3가지 ViewModel을 설정해주어야 한다.
1) MessageListHeaderViewModel : 채널에 대한 정보 제공
2) MessageListViewModel : 채널의 메시지를 로드 & 현재 상태에 대한 정보 제공
3) MessageComposerViewModel : 새로운 메시지 작성, 전송
Thread를 열면서, 해당 VIew들에 대한 정보를 알려주고,
메시지를 편집할 때, 메시지 입력 정보를 알려준다.
6. Explorer Setting
해당 코드를 따라왔다면 그에 대한 결과물은 mainActivity에서 user만 설정해주었기 때문에,
앱을 빌드했을 때, 유저에 대한 정보만 따라오게 된다.
테스트를 위해서 채널을 만들고, 해당 유저를 채널안에 넣어주는 작업을 해주면, 해당 대화방에 유저가 들어오게 된다.
[참고자료]
https://velog.io/@skydoves/android-chat-tutorial-stream-chat-sdk
안드로이드 채팅 앱 만들기 - Stream Chat SDK
Stream이번 포스트에서는 Stream Chat SDK for Android를 사용하여 누구나 빠르고 쉽게 안드로이드 채팅 앱을 개발하는 방법을 살펴봅니다.먼저, 안드로이드 스튜디오에서 New Project -> Empty Activity를 선택
velog.io
https://getstream.io/tutorials/android-chat/
Android Chat Tutorial with Java & Kotlin
Android Chat Tutorial: How to build a chat app
getstream.io
'Study' 카테고리의 다른 글
[Android] 안드로이드 스튜디오 무선 디버깅 연결하는 법 (0) | 2024.02.22 |
---|---|
[Android] Project에 ktlint 적용하기 (0) | 2024.02.19 |
[Android] BuildSrc과 Kotlin DSL을 이용한 Dependency 관리 (0) | 2024.01.02 |
[Android] Hilt (0) | 2023.10.27 |
[Android] MVVM (0) | 2023.08.07 |