안드로이드 채팅 앱 만들기 - Stream Chat SDK

skydoves·2022년 3월 29일
26

Stream Chat SDK

목록 보기
1/2

Stream

이번 포스트에서는 Stream Chat SDK for Android를 사용하여 누구나 빠르고 쉽게 안드로이드 채팅 앱을 개발하는 방법을 살펴봅니다.

안드로이드 프로젝트 생성하기

먼저, 안드로이드 스튜디오에서 New Project -> Empty Activity를 선택하면 아래의 이미지와 같이 프로젝트 구성 화면을 확인할 수 있습니다. 프로젝트 이름을 설정하고 min SDK는 21 보다 높게 설정합니다. 프로젝트 설정이 모두 끝났다면 Finish를 눌러서 프로젝트를 생성합니다.

Android Studio

프로젝트가 생성되었다면, settings.gradle 파일에서 다음과 같이 dependencyResolutionManagement를 설정합니다.

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven { url "https://jitpack.io" }
    }
}

다음으로, build.gradle (Module) 파일에 들어가서 dependency에 아래의 의존성을 추가합니다.

dependencies {
    implementation "io.getstream:stream-chat-android-ui-components:5.0.2"
    implementation "androidx.activity:activity-ktx:1.4.0"
}

마지막으로, build.gradle (Module) 파일의 상단에서 다음과 같이 ViewBinding 설정을 추가합니다. 해당 포스트의 예제에서는 ViewBinding을 사용하고 있습니다. 학습이 필요하신 분들은 안드로이드 문서를 참조해주세요.

android {
    ...
    buildFeatures {
        viewBinding true
    }
}

프로젝트 설정을 모두 마쳤습니다! 🙌

다음으로 Stream Chat 대시보드에서 API Key를 획득하는 방법에 대하여 살펴보도록 하겠습니다.

Stream Chat 대시보드에서 API 키 획득하기

Stream Chat 서비스의 Try-for-free 페이지에 접속하면 아래와 같이 무료 Trial 버전을 시작할 수 있는 화면을 확인할 수 있습니다.

GitHub 계정이 있으신 경우, 하단의 SIGN UP WITH GITHUB 버튼을 누르고 빠르게 대시보드 계정을 생성할 수 있습니다. 또는 email/organization/password 정보를 입력 후, START FREE TRIAL 버튼을 누르면 대시보드 계정이 생성되고 다음과 같이 대시보드 화면으로 이동합니다.

dashboard

대시보드의 우측 상단에 있는 Create App 버튼을 클릭하면 다음과 같이 새로운 앱에 대한 정보를 입력하는 팝업을 확인할 수 있습니다.

create App

다음과 같이 새로운 앱에 대한 정보를 입력하고 Create App 버튼을 눌러 새로운 앱을 생성합니다.

  • App Name: 앱 이름을 입력합니다.
  • Feeds Server Location: 아무거나 선택합니다.
  • Clone Existing App: (선택 없음)
  • Environment: Development

Create App 버튼을 누르셨다면, 다음과 같이 대시보드 메인 화면에 새로운 App의 정보와 API Key를 확인하실 수 있습니다. API Key는 추후에 채팅 기능을 초기화 하는데 반드시 필요하기 때문에, 따로 메모해놓거나 대시보드 화면을 열어두시는 것을 권장 드립니다.

dashboard

Stream Chat 대시보드 설정 및 살펴보기

대시보드 메인화면에서 파란색의 앱 이름을 클릭하면, 아래와 같이 Chat Overview 화면으로 이동합니다. Chat Overview 화면에서는 채팅 그룹(채널) 타입, 리얼타임 서비스 연동 설정 등 채팅 기능에 대하여 전반적인 설정이 가능합니다.

Chat Overview에서 중간까지 스크롤을 내리면 Authentication 항목을 확인할 수 있습니다. 이번 예제에서는 Authentication 기능을 사용하지 않기 때문에, 다음과 같이 Disable Auth ChecksON으로 변경해주신 후 Save 버튼을 눌러서 상태를 저장합니다.

Authentication

좌측 메뉴의 Explorer 메뉴에 들어가면 다음과 같이 채팅 그룹 (채널), 유저, 채팅 메세지 로그 등에 대한 메뉴얼한 조작 (생성, 삭제, 조회) 등이 가능합니다. 추후에 앱 화면 개발을 모두 끝마치고 이곳에서 새로운 유저와 채널 정보를 추가하거나 수정하여 데모 환경을 구축하실 수 있습니다.

explorer

이제 API Key 획득과 대시보드 설정이 모두 완료되었습니다! 🎉

다음은 프로젝트로 돌아가서 채널 목록 화면을 구현해 보도록 하겠습니다.

채널 (채팅방) 목록 화면 구현하기

채널 목록 화면을 구현하기 위해, 먼저 activity_main.xml 파일에서 다음과 같이 ChannelListView를 추가합니다. ChannelListView는 위의 이미지와 같이 그룹 (채널) 목록들을 보여주고 다양한 기능 (새로운 메세지 싱크, 메세지 읽음 처리, 읽지 않은 메세지, 채널 퇴장 등)을 지원합니다.

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <io.getstream.chat.android.ui.channel.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>

다음으로, 채팅 기능의 전반을 담당하는 ChatClient를 초기화함과 동시에 ChannelListView에 로그인한 유저 정보를 전달합니다. MainActivity에 아래의 코드를 복사 및 붙여넣기를 통해 추가합니다. 또한, 이전에 대시보드에서 획득한 API Key를 복사하여 YOUR API KEY 부분에 붙여넣기 합니다.

import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import io.getstream.chat.android.client.ChatClient
import io.getstream.chat.android.client.logger.ChatLogLevel
import io.getstream.chat.android.client.models.User
import io.getstream.chat.android.offline.plugin.factory.StreamOfflinePluginFactory
import io.getstream.chat.android.ui.channel.list.viewmodel.ChannelListViewModel
import io.getstream.chat.android.ui.channel.list.viewmodel.bindView
import io.getstream.chat.android.ui.channel.list.viewmodel.factory.ChannelListViewModelFactory
import io.getstream.chatapplication.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Step 0 - ViewBinding 초기화
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Step 1 - 오프라인 메세지 로드 및 전송 등 오프라인 기능 초기화
        val offlinePluginFactory = StreamOfflinePluginFactory(
            config = io.getstream.chat.android.offline.plugin.configuration.Config(),
            appContext = applicationContext,
        )

        // Step 2 - ChatClient 초기화
        val client = ChatClient.Builder("YOUR API KEY", applicationContext)
            .withPlugin(offlinePluginFactory)
            .logLevel(ChatLogLevel.ALL)
            .build()

        // Step 3 - 유저 정보 초기화
        val user = User(
            id = "marvel",
            name = "Iron Man"
            image = "https://bit.ly/2TIt8NR"
        )
        val token = client.devToken(user.id) // developer 토큰 생성
        client.connectUser( // 유저 로그인
            user = user,
            token = token
        ).enqueue {
            // Step 4 - 새로운 그룹 (채널) 생성
            if (it.isSuccess) {
                client.createChannel(
                    channelType = "messaging",
                    channelId = "new_channel_01",
                    memberIds = listOf(user.id),
                    extraData = mapOf("name" to "My New Channel")
                ).enqueue()
            }
        }

        // Step 5 - ChannelListViewModel 생성 및 ChannelListView과 연동
        val viewModelFactory = ChannelListViewModelFactory()
        val viewModel: ChannelListViewModel by viewModels { viewModelFactory }
        viewModel.bindView(binding.channelListView, this)
        binding.channelListView.setChannelItemClickListener { channel ->
            startActivity(MessageListActivity.newIntent(this, channel))
        }
    }
}

Note: 상용 앱에서는 Step 1~3까지의 ChatClient 초기화를 Application class에서 하시는 것을 권장 드립니다. 해당 포스트에서는 편의상 MainActivity에서 한 번에 초기화하였습니다.

이제 채널 목록 화면 개발이 모두 끝났습니다! 👏

Step 5에서 MessageListActivity를 찾지 못했다는 빨간색 밑줄이 나온다면 정상입니다. 다음으로 채팅방의 메세지 목록을 보여주는 MessageListActivity를 구현해보도록 하겠습니다.

채팅방 메세지 목록 화면 구현하기

먼저, 위와 같은 채팅방 화면을 구현하기 위해 안드로이드 스튜디오에서 다음과 같이 새로운 액티비티를 추가합니다.

activity

다음으로, 새롭게 추가된 activity_message_list.xml 파일에 다음과 같이 3가지 뷰를 추가합니다.

<?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.message.list.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.message.list.MessageListView
        android:id="@+id/messageListView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/messageInputView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/messageListHeaderView" />

    <io.getstream.chat.android.ui.message.input.MessageInputView
        android:id="@+id/messageInputView"
        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>

activity_message_list.xml 은 다음과 같이 주요한 세 가지 컴포넌트로 구성하였습니다.

  • MessageListHeaderView: 채팅방 상단의 헤더를 구성합니다. 뒤로가기, 현재 타이핑 혹은 온라인 중인 유저 정보 및 사용자 프로필 이미지 등으로 구성됩니다.
  • MessageListView: 채팅 메세지 목록을 구현합니다.
  • MessageInputView: 채팅 내용을 입력할 수 있는 EditText 입니다. 이미지 등 첨부파일 전송 및 각종 명령어를 입력할 수 있습니다.

마지막으로, 특정 채널의 채팅 메세지들을 보여주는 화면을 개발하면 채팅 앱이 완성됩니다. 새로 생성한 MessageListActivity에 아래의 코드를 복사 붙여넣기 합니다.

import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.addCallback
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.getstream.sdk.chat.viewmodel.MessageInputViewModel
import com.getstream.sdk.chat.viewmodel.messages.MessageListViewModel
import io.getstream.chat.android.client.models.Channel
import io.getstream.chat.android.ui.message.input.viewmodel.bindView
import io.getstream.chat.android.ui.message.list.header.viewmodel.MessageListHeaderViewModel
import io.getstream.chat.android.ui.message.list.header.viewmodel.bindView
import io.getstream.chat.android.ui.message.list.viewmodel.bindView
import io.getstream.chat.android.ui.message.list.viewmodel.factory.MessageListViewModelFactory
import io.getstream.chatapplication.databinding.ActivityMessageListBinding

class MessageListActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMessageListBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Step 0 - ViewBinding 초기화
        binding = ActivityMessageListBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val cid = checkNotNull(intent.getStringExtra(CID_KEY)) {
            "MessageListActivity를 시작하기 위해서는 채널 아이디 (cid) 정보가 필요합니다."
        }

        // Step 1 - 채팅방 컴포넌트에 연결할 뷰모델 초기화
        val factory = MessageListViewModelFactory(cid)
        val messageListHeaderViewModel: MessageListHeaderViewModel by viewModels { factory }
        val messageListViewModel: MessageListViewModel by viewModels { factory }
        val messageInputViewModel: MessageInputViewModel by viewModels { factory }

        // Step 2 - 채팅방 컴포넌트에 뷰모델 연결
        messageListHeaderViewModel.bindView(binding.messageListHeaderView, this)
        messageListViewModel.bindView(binding.messageListView, this)
        messageInputViewModel.bindView(binding.messageInputView, this)

        // Step 3 - MessageListHeaderView와 MessageInputView 연결 및 채팅 쓰레드 모드 업데이트
        messageListViewModel.mode.observe(this) { mode ->
            when (mode) {
                is MessageListViewModel.Mode.Thread -> {
                    messageListHeaderViewModel.setActiveThread(mode.parentMessage)
                    messageInputViewModel.setActiveThread(mode.parentMessage)
                }
                MessageListViewModel.Mode.Normal -> {
                    messageListHeaderViewModel.resetThread()
                    messageInputViewModel.resetThread()
                }
            }
        }

        // Step 4 - 채팅방 동작에 대한 이벤트 구독 및 NavigateUp 이벤트 발생 시 채팅방 종료
        messageListViewModel.state.observe(this) { state ->
            if (state is MessageListViewModel.State.NavigateUp) {
                finish()
            }
        }

        // Step 5 - 채팅방 헤더의 뒤로가기 버튼 및 디바이스 백버튼 터치 시 NavigateUp 이벤트 송출
        val backHandler = {
            messageListViewModel.onEvent(MessageListViewModel.Event.BackButtonPressed)
        }
        binding.messageListHeaderView.setBackButtonClickListener(backHandler)
        onBackPressedDispatcher.addCallback(this) {
            backHandler()
        }
    }

    companion object {
        // MessageListActivity의 인텐트 생성 및 채팅방의 cid 정보 전달
        private const val CID_KEY = "key:cid"
        fun newIntent(context: Context, channel: Channel): Intent =
            Intent(context, MessageListActivity::class.java).putExtra(CID_KEY, channel.cid)
    }
}

드디어 채팅방 화면까지 개발이 모두 끝났습니다! 🥳 이제 앱을 빌드하고 실행하면 채팅 앱이 정상적으로 실행되는 것을 확인하실 수 있습니다.

Stream Chat SDK의 UI 컴포넌트는 100% 커스터마이징이 가능하기 때문에 아래와 같이 다양한 형태로 재미난 채팅 앱을 구현해 보실 수도 있습니다.

whatsapp

avengerschat

마무리

이번 포스트에서는 Stream Chat SDK for Android를 이용하여 채팅 앱을 빠르고 쉽게 구현하는 방법에 대하여 살펴보았습니다. 해당 포스트에서 다룬 코드는 GitHub에서 확인 하실 수 있습니다.

Stream Chat SDK는 수천 개의 크고 작은 국내외의 글로벌 프러덕트에서 사용되고 있으며, 총 수십억 명의 엔드 유저의 디바이스에서 동작하고 있습니다. 또한 안드로이드 뿐만 아니라, React, iOS (Swift, SwiftUI), Flutter, Jetpack Compose, Angular, Unreal, Unity 등 과 같은 다양한 플랫폼을 위한 솔루션을 제공하고 있습니다.

아래 링크에서 Stream Chat SDK에 대해 더 많은 정보를 확인하실 수 있습니다.

즐거운 코딩 되시길 바랍니다!

작성자 엄재웅 (skydoves)

profile
http://github.com/skydoves

22개의 댓글

comment-user-thumbnail
2022년 7월 27일

안녕하세요 skydoves님! 항상 잘 보고 있습니다! 제가 이번에 토이 프로젝트에 채팅 기능을 넣을 때 해당 SDK를 써보려 하는데 영리를 취하는 기업이 아니라 토이 프로젝트에서도 해당 채팅 기능을 한달 이상 꾸준히 이용하려면 요금을 지불해야하는건지 궁금합니다!

2개의 답글
comment-user-thumbnail
2022년 8월 25일

안녕하세요 skydoves님 블로그를 보며 많은 도움 받고 있습니다! Stream SDk를 이용해서 프로젝트를 진행하려는데 질문이 있어서 댓글남깁니다. 플레이스토어에 배포까지 해보려고 하는데 프로덕트에선 Authentication을 사용해야한다는 문서를 보았습니다. Authentication는 JWT를 이용하는거 같은데 저희쪽 서버에서 토큰을 만들어야하는 건가요 아니면 Stream SDK에서 토큰을 만드는 방법이 있는 건가요?

2개의 답글
comment-user-thumbnail
2022년 11월 29일

안녕하세요! skydoves의 글 덕분에 토이프로젝트에서 Stream 잘 적용해서 사용중입니다.
그런데 곧 30일 무료기간이 끝나서 위의 댓글에서 말씀해주신 Maker Account 를 사용하고 싶은데요
깃허브를 이용해서 가입하면 특별한 가입절차 없이 진행된다고 말씀해주셨는데
그냥 로그인시에 continue with github 를 이용해서 github 계정으로 사용하면 바로 Maker Account 를 사용할 수 있다는 말씀 맞을까요?

1개의 답글
comment-user-thumbnail
2023년 9월 16일

안녕하세요. 좋은 소개글 감사드립니다.

https://getstream.io/blog/chat-edge-infrastructure/
혹시 위 블로그의 이미지를 보면 서울 리전은 없어 보이는데 맞나요?

1개의 답글