본문 바로가기

안드로이드 Android

안드로이드 개발 (12) SendBird Chat SDK

안녕하세요 오늘 공부한 것을 정리를 이어서 샌드버드에 대해 알아보겠습니다. 

확정은 아니지만 어쩌면 조만간 외주 프로젝트에서 투입되서 SendBird 를 사용할지도 몰라서

미리 공부를 해봤습니다.

(알아두면 나쁠건 없으니까)

 

SendBird는 국내 기업 입니다.

현재 10억 5천만 달러의 가치를 인정 받으면서

국내 B2B 소프트웨어 스타트업 최초로 유니콘 기업이라는 칭호를 얻게 되었습니다. 

 

관련기사 

https://platum.kr/archives/160778

 

센드버드, 1억 달러 투자유치…엔터프라이즈 SW ‘유니콘’ 탄생

 

platum.kr

 

국내 첫 B2B 스타트업으로 유니콘 기업이라는 칭호를 얻은 SendBird는 채팅 API 플랫폼을 선보이며, 많은 기업에서 이 SendBird의 채팅 플랫폼을 사용하고 있다고 합니다. 어째서 SendBird가 글로벌 채팅 API가 되었는지 궁금하기도 하고, 외주 일로 SendBird의 Android Chat SDK를 다룰 수 있어서,

주력 상품인 채팅 플랫폼을 맛보기로 사용해보도록 하겠습니다.

 

https://sendbird.com/docs/chat/v3/android/getting-started/about-chat-sdk

 

About Chat SDK | Chat Android SDK | Sendbird Docs

See how Sendbird Chat SDK works and try our sample app for Android.

sendbird.com

 

위 링크를 통해 개념을 배우고 사용해봤습니다.

 

0. 시작

https://dashboard.sendbird.com/auth/signin?_ga=2.136478896.1207239033.1623639446-672426265.1623058267 

 

Dashboard | Sendbird

 

dashboard.sendbird.com

샌드버드는 앞서서 사용하기전에 홈페이지 가입이 필요합니다.

가입을 하면 채팅을 총괄하는 관리자 ID를 만들 수 있습니다.

이 과정을 가장 먼저 진행을 해야합니다. 

위 링크를 통해서 가입을 하고 나면 계정 정보 관련으로 Applications에서 채팅을 적용할 앱을 create + 버튼을 눌러서 등록해주시면 됩니다.

웹 서버 기반으로 채팅이 만들어집니다.

 

안드로이드 클라이언트가 서버를 이용해 채팅을 구현하는 방식은 다음과 같습니다.

 

1) 클라이언트는 서버에서 DB를 받아 데이터를 UI로 표시를 해줍니다.

2) 서버는 클라이언트로 부터 언제나 DB를 받습니다. (가입, 로그인, 채팅방 입성, 채팅 초대, 채팅 보내기 등등..)

3) 클라이언트는 DB를 받아서 가공해서 다른 서버의 DB와 같이 연동해서 사용할 수 있습니다. 

4) 클라이언트에서 서버에서 실시간으로 데이터를 받을 수 있는 Callback 함수들을 사용해서 상대방 채팅을 실시간으로 받거나 이벤트를 받을 수 있습니다.

 

별도의 설명이 필요없을 정도의 매우 심플한 구조입니다.

안드로이드가 요청으로 서버 데이터를 받을 수 있고 혹은 요청없이 서버데이터를 받을 수 있습니다. 

 

기본적으로 소켓 서버로 운영되고 있다고 보면 됩니다.

 

1. 개념

위에 설명한것과 같이 SendBird는 매우 심플한 구조를 가지고 있으며, SendBird에서 자체 서버를 운영하고 있습니다. 

SendBird가 기업에서 많이 사용하는 채팅 플랫폼이 된 이유는 문서를 보고 누구든지 SendBird를 사용하기 쉽다는 점이 있습니다.

 

SendBird Chat SDK는 여러가지 CallBack 함수를 지원해주면서

서버에서 db를 가져오는 각종 쿼리 함수들을 지원해줍니다.

 

1) 채널 유형

SendBird는 크게 OpenChannel GroupChannel로 나뉩니다. 

OpenChannel의 경우 누구나 참여할 수 있고 그렇기 때문에 여러명이 접속이 가능합니다. 열려있는 채팅 공간이고 심플한 기능들을 지원합니다.

응용프로그램을 사용하는 모든 사용자가 접속할 수 있고 최대 1000명까지 지원합니다.

 

GroupChannel의 경우 참여할 유저를 따로 초대를 해야 채팅에 참여가 가능합니다. 기본적으로 비공개 채팅 공간이고 대표적으로 카카오톡을 생각하면 좋을 것 같습니다. 공개 설정을 모든 사용자로 바꿀수도 있습니다.

최대 100명까지 사람들이 접속할 수 있습니다.

 

GroupChannel은 1:1 , 1:N, 비공개 단톡방 등등 유저가 다양한 카테고리로 사용할 수 있게끔 SendBird 에서

지원을 해주고, 답장기능 및 채팅을 읽지 않는 갯수 등등 openChannel에서 사용할 수 없었던 다양한 기능들을 사용할 수 있습니다.

(기본적으로 카카오톡을 연상하면 됩니다. )

 

**SuperGroupChannel**

 

SuperGroupChannel은 GroupChannel보다 더 많은 인원을 수용할 수 있습니다.

- 2000명까지 수용가능

 

하지만 더 많은 인원을 수용하기 때문에 GroupChannel 보다 제한된 몇몇 기능들이 있습니다.

 

- 읽지않는 채팅을 100개 까지 제한 

- 푸쉬 알림 어느정도 제한 사항 존재

- 미리보기 10명까지 지원 

....

등등이 존재합니다. 

 

 

채팅 개념은 크게 OpenChannel 과 GroupChannel로 나뉘고

그외 사용자 관련된것들은 SendBird라는 클래스가 존재합니다.

 

Android Chat SDK에 3가지 클래스가 있습니다.

OpenChannel, GroupChannel, SendBird

 

위 3개의 클래스를 응용하면 원하는 채팅을 만들 수 있을것입니다. 귀찮은 일은 이미 SendBird가 다 처리해놨습니다. 많은 기업이 SendBird의 ChatAPI를 사용하는 이유가 바로 여기있다고 생각합니다. 탄탄한 서버에 클라이언트 개발자가 매우 사용하기 간편하기 때문에 서버에서 DB를 받아 클라이언트 개발자가 원하는데로 커스텀하기 쉽습니다.

 

하지만.. 단점으로 비용인데 자체 서버를 운영하기 때문에 채팅 서버 비용이 만만치 않게 빠져나갑니다. 자본이 부족한 기업이라면 다른 채팅 API나 저렴한 FCM+ 로컬 DB, 파이어베이스 등등 다른것을 고려해보길 권장합니다.

 

2) 유저 유형

 유저 유형은 크게 4가지로 나뉩니다.  

 

User

-어플리케이션을 사용중인 일반 User를 뜻합니다.

Participant

-오픈채널에 참여중이고 online 상태중인 User를 뜻합니다.

Member

-특정 그룹채널에 참여중인 유저를 뜻합니다. 

Operator

채널을 관리하는 운영자 특정 Member 및 Participant를 차단하거나 음소거를 시킬 수 있음 

 

공식 문서를 읽을 때 유저관련 명칭 정리 정도로 생각하면 좋을것 같습니다.

 

 

2. Android Chat SDK 

1) 셋팅

처음에 가입을 완료하고 Application까지 등록을 해야 아래설정이 가능합니다.

안드로이드 개발자가 sdk를 사용할 때 늘 그랫듯이 gradle 설정을 해야합니다.

allprojects {
    repositories {
        ...
        maven { url "https://repo.sendbird.com/public/maven" }
    }
}

위와 같이 url를 설정해주고

dependencies {
    ...
    implementation 'com.sendbird.sdk:sendbird-android-sdk:3.0.166'
    ...
}

dependencies에 위와같이 추가해줍시다.

그다음 SendBird.init를 사용해줘야합니다. 

class App: Application() {
    override fun onCreate() {
        super.onCreate()
        //샌드버드 키 등록 
        SendBird.init(getString(R.string.sendBird_key),applicationContext)
    }
}

파라미터로 sendBird에 등록했던 Application의 key를 넣어줍시다

 Applications의 이미 만든 Application 이름을 선택해주면 

Application ID가 바로 SendBird Key 입니다. 

 

 

2) 특징

Android Chat SDK로 지원해주는 메서드는 SendBird.init() 메서드를 빼고 모두 비동기 입니다.

그렇다면, 클라이언트 입장에서 코딩을 할때 주의해야할점이 콜백지옥으로 빠지는 경우가 있습니다.

그래서 코딩을 할 때 이점을 유의해서 최대한 콜백지옥으로 빠지지 않게끔 주의하면 좋을것 같습니다.

 

예제.

// The CHANNEL_URL below can be retrieved using the openChannel.getUrl().
OpenChannel.getChannel(CHANNEL_URL, new OpenChannel.OpenChannelGetHandler() {
    @Override
    public void onResult(OpenChannel openChannel, SendBirdException e) {
        if (e != null) {
            // Handle error.
        }

        // Call the instance method of the result object in the "openChannel" parameter of the onResult() callback method.
        openChannel.enter(new OpenChannel.OpenChannelEnterHandler() {
            @Override
            public void onResult(SendBirdException e) {
                if (e != null) {
                    // Handle error.
                }
                
                // The current user successfully enters the open channel,
                // and can chat with other users in the channel by using APIs.
                ... 
            }
        });
    }
});

 

- Android Chat SDK는 위 처럼 메서드안에 메서드가 사용되는 형식으로 주로 쓰게 됩니다.

 

3) SDK 설명

OpenChannel, GroupChannel , SendBird 

크게 3가지 클래스를 기준으로 많이 사용합니다.

 

OpenChannel은 오픈 채널에 관련된 것들을 모두 지원합니다. 

- 오픈채널 관련 채널 생성, 채팅 내역, 채팅 db 검색 등등.. 

- 오픈채널 이벤트 관련 인터페이스 

 

GroupChannel은 그룹 채널에 관련된 것들을 모두 지원합니다. 
- 그룹채널 관련 채널 생성, 채팅 내역, 채팅 db 검색 등등.. 

- 그룹채널 이벤트 관련 인터페이스 

- 메시지 스레딩 기능, 영수증 등등 

 

 

SendBird는 유저 로그인 및 가입, 유저정보, 서버로 전체 DB 가져오기, 유저차단, 차단 풀기 등등에 사용합니다

- 서버로부터 이벤트 수신 등록 및 대기 

- SendBird ChatSdk 셋팅 

- 유저 가입 및 유저 정보 얻기 

- SendBird와 연결관리 

- 서버의 전반적인 DB 가져오기 

- 유저 차단 , 차단 풀기

...등등 

 

 

그외 

Parms 관련 클래스들이 지원됩니다.

채널을 만들때나 유저를 생성할때 Parms를 생성해서 여러 추가정보를 입력할 수있습니다. 

 val params = GroupChannelParams()
            .setPublic(false)
            .setEphemeral(false)
            .setDistinct(true)
            .setSuper(false)
            .addUserIds(userList)
            .setName(getString(R.string.currentUser))
        GroupChannel.createChannel(params) { groupChannel, _ ->
            currentUrl = groupChannel.url
        }

이 외에 유저관련 parms , 오픈채널 관련 Parms 등등이 있습니다.

이러한 개념만 알면 필요할때 공식문서 사이트에 들어가 필요한 parms 클래스명이 무엇인지 확인해서 사용하면 됩니다. 

 

 

이제 구체적인 감각을 익히기 위해 예제를 살펴보겠습니다.

아래는 그룹채팅으로 1:1를 구현한 예제입니다.

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions'
}

테스트를 위해서 id 'kotlin-android-extensions' 를 추가했습니다.

** 주의 **

위 extensions는 deprecated가 되었고 9월부터 지원하지 않습니다. 이 예제는 빠른 테스트를 위해 넣었습니다.

 

그래서 실제 프로젝트에 사용할땐 

findViewById, Compose, ViewBinding, DataBinding중 하나를 선택해서 사용하시기를 권장합니다.

 

xml 코드입니다. 

<?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">

    <TextView
        android:id="@+id/chat_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        app:layout_constraintBottom_toTopOf="@+id/chatMessage_et"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="chat" />

    <EditText
        android:id="@+id/chatMessage_et"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="textPersonName"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/button"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <Button
        android:id="@+id/btn_invite"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="유저2 초대"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_exit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="나가기"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

위와 같은 xml이 그려집니다.

 

그 다음 

res-> strings 에 아래와 같이 적읍시다. 

<!--    프로젝트 Run할때 기기마다 아래 유저를 다르게 해서 테스트 해야합니다. -->
    <!-- A기종은 TestUser1 B기종은 TestUser2-->
    <string name="currentUser">TestUser1</string>
    <string name="opponentUser">TestUser2</string>

그리고 MainActivity로 가서 코드를 작성해줍니다.

class MainActivity : BaseActivity() {
    lateinit var currentUrl: String
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.oneonone_activity)

        SendBird.connect(getString(R.string.currentUser)) { user, e ->
            if(e ==null)
            GroupChannel.createMyGroupChannelListQuery().next { list, _ ->
                if (list.isNullOrEmpty()) {
                    toastShort("채팅방이 생성 되었습니다.")
                    createChannel()
                } else {
                    toastShort("이미 채팅방이 존재합니다.")
                    currentUrl = list.first().url
                    getGroupUrl(currentUrl) { groupChannel ->
                        prevMsg()                
                    }
                }
            }
        }


        button.setOnClickListener {
//            1.방찾기
            GroupChannel.getChannel(currentUrl) { groupChannel, e ->
//                2.입장 (메시지 보내기 위해서는 필수)
                groupChannel.join { }
//                3.메시지 보내기
                groupChannel.sendUserMessage(chatMessage_et.text.toString()) { msg, e ->
//                4. 메시지를 UI로 표시
                    chat_tv.append("${msg.message}\n")
                }
            }
        }
        btn_invite.setOnClickListener {
            val userIds = listOf(getString(R.string.opponentUser))
            getGroupUrl(currentUrl) { groupChannel ->
                groupChannel.inviteWithUserIds(userIds) {}
            }
        }

        btn_exit.setOnClickListener {
            getGroupUrl(currentUrl) { groupChannel ->
                groupChannel.leave { toastShort("탈퇴") }
            }
        }
    }

    private fun createChannel() {
        val userList = listOf<String>(getString(R.string.currentUser))

        val params = GroupChannelParams()
            .setPublic(false)
            .setEphemeral(false)
            .setDistinct(true)
            .setSuper(false)
            .addUserIds(userList)
            .setName(getString(R.string.currentUser))
        GroupChannel.createChannel(params) { groupChannel, _ ->
            currentUrl = groupChannel.url
        }
    }

    private fun prevMsg() {
        GroupChannel.createMyGroupChannelListQuery().next { list, e ->
            list.forEach {
                chat_tv.append("${it.lastMessage.message}\n")
            }
        }
    }

    private fun getGroupUrl(url: String, afterLogin: (GroupChannel) -> Unit) {
        GroupChannel.getChannel(url) { groupChannel, e ->
            afterLogin(groupChannel)
        }
    }


    override fun onResume() {
        super.onResume()
        SendBird.addChannelHandler(GROUP_HANDLER_ID, object : SendBird.ChannelHandler() {
            override fun onMessageReceived(p0: BaseChannel?, message: BaseMessage?) {
                chat_tv.append("${message!!.message}\n")
            }
        })
    }

    companion object {
        const val GROUP_HANDLER_ID = "GROUP_HANDLER_ID"
    }
}

 

예제 흐름 순서 

그룹 채팅으로 1:1방 구현 * 

 

(1) 화면이 켜지는순간 유저 명이 이미 존재한다면 로그인 아니라면 가입 ->

 

(2) 로그인한 User의 GroupChannel이 하나라도 없을시 채팅방 생성 있을시 생성하지 않음

   (이미 존재하는 방일시 이전 채팅내역 다가져오기) ->  

 

(3) 유저 초대버튼을 누르면 strings의 저장해놓은 상대방 유저를 채팅으로 초대함 -> 

 

(4) 상대방 채팅을 언제든지 읽을 수 있도록 onResume()에 SendBird.addChannelHandler() 등록 

 

 

4) 테스트

2개의 기기를 사용해서 서로 채팅을 해볼 생각입니다.

 

2개의 가상기기나 혹은 실기기를 준비해봅시다. 

 

테스트를 위해서 웹사이트도 같이 봐줍시다.

아까 Application ID를 보던 페이지에서 Group channels 를 누르면

위 사진과 같은 화면이 나옵니다.

 

우측 상단에 CreateUser + 를 눌러서 TestUser1, TestUser2 를 만들어 줍시다.

(물론 클라이언트에서 코드로 유저를 추가할 수 있지만 빠른 테스트를 위해 웹사이트로 만들어봤습니다.)

 

그런 다음 좌측 사이드에 Group channels 를 눌러서 해당 페이지를 켜놓고 안드로이드 스튜디오로 이동해봅시다.

 

A기종을 먼저 테스트 해봅시다. 

Run을 해서 A기종에 앱을 만들어봅시다.

앱 빌드가 완료되면 화면부터 뜰탠데, 

기존 채팅방이 없기 때문에 코드 조건문에 따라서 채팅방이 새로 생성이 됩니다.

 

그리고 웹사이트에는 새로운 채팅이 하나가 추가된게 UI로 표시가 되지만

상대 유저 입장에서

실제 서버로 부터 받는 DB의 쿼리로 list를 가져올 때 채팅이 1개라도 없으면 리스트에 포함되지 않아서

상대 유저로 로그인 했을 때에 새로운 방을 만들어버립니다. 

그러니 이 예제에서는 아래와 같은 순서로 해주시길 바랍니다.

 

(1) 우측 상단 버튼을 누름

(2) 버튼을 누르고  채팅 아무거나 적을것 

 

(물론 실프로젝트는 위와 같이 안되게 잘 커스텀해주시면 됩니다. 언제나 예제일뿐.)

 

 

 

이어서 B기종으로 테스트 해봅시다.

빌드를 하기전에 

B기종으로 빌드를 할때 res-> strings.xml을 아래처럼 바꿉시다.

(다시 말씀드리지만 테스트를 위해 임시로 짠 방식입니다.)

<!--    프로젝트 Run할때 기기마다 아래 유저를 다르게 해서 테스트 해야합니다. -->
    <!-- A기종은 TestUser1 B기종은 TestUser2-->
    <string name="currentUser">TestUser2</string>
    <string name="opponentUser">TestUser1</string>

B기종은 사용자가 TestUser2 상대방이 TestUser1 입니다. 

(A기종은 사용자가 TestUser1 상대방이 TestUser2)

 

B기종으로 실행한 화면 입니다. 

이미 방이 존재하기 때문에 채팅방이 존재한다고 나오고 이전 채팅 내역을 가져올 수 있습니다.

 

아까 켜놨던 웹사이트도 보면 채팅이 생성되고 유저가 2명인걸 알 수가 있습니다.

채팅방을 클릭하면 관리자 권한으로 채팅 내역을 볼 수 있습니다.

 

 

이렇게 설정하면 실시간으로 서로 채팅이 오갈 수 있습니다.

어디까지나 예제로 적은거라 좀 비효율적인 로직이 있긴한데

 

코드좀 파악하고 큰 흐름에 대해 감이 온다면 SendBird Chat SDK가 워낙 쉽기 때문에 누구든지 원하는 형태로 

커스텀 해서 사용할 수 있습니다.

 

 

아래는 실제 사용 영상입니다.

 

 

채팅을 사용하기 쉽게 만든 SendBird 팀에게 감탄을 하게 되네요 ㅎㅎ

나중에 자본력이 생기면 그때 SendBird를 적극 사용해볼 생각입니다. 

필요한 기능을 가진 함수들이나 추가 설명은 (답장 기능이라던지..)

등등의 SendBird 공식 문서에 잘 나와 있습니다.

위 개념들만 잘 다져놓는다면 추가적인 기능들은 금방 찾아 사용하게 될거 같네요 

 

이상 안드로이드 SendBird Chat SDK에 알아봤습니다.