본문 바로가기
⛏️ | 개발 기록/🪐 | Cosmic Detox

[Android] Kakao Login을 Firebase Authentication과 연결

by immgga 2024. 9. 12.

출처: unsplash.com

 

부트캠프 최종 팀 프로젝트 기록 9

 

 

서론

지난 포스팅과 연결된다.

https://rkdrkd-history.tistory.com/244

 

[Node.js] Intellij, Vscode에서 Firebase Functions 개발을 위한 Node.js 프로젝트 세팅

부트캠프 최종 프로젝트 기록 7  서론현재 kakao 로그인 기능을 구현하려 하고 있다.kakao login을 그냥 구현하는 게 아닌 firebase authentication과 연동해서 사용하려 한다.그러기 위해서는 firebase functio

rkdrkd-history.tistory.com

 

지난 포스팅에는 firebase functions 개발을 위한 node js 환경 세팅까지 진행했다.

오늘은 android에서 구현한 firebase functions와 kakao login sdk를 이용해 firebase authentication에 추가해 보는 작업을 진행해 보겠다.

 

아래의 블로그를 참고해서 개발해 주었다.

https://super-strength-crystal.blogspot.com/2024/02/android-kakao-firebase-authentication.html

 

[Android] Kakao 계정으로 Firebase Authentication 연동

Kakao Login API, Firebase Authentication 연동

super-strength-crystal.blogspot.com

 

firebase functions 기능도 위 블로그와 동일하게 진행했고, android 코드만 다르기 때문에 firebase functions 코드는 위 포스팅을 참고해 보시길.

 

 

Firebase Authentication에 Kakao 계정 추가하기

본격적으로 시작해 보겠다.

우선 아래 사항들이 선행으로 진행돼 있어야 한다.

  • firebase project 생성 및 blaze 요금제로 업그레이드.
  • android studio에 kakao sdk 및 firebase authentication, functions 라이브러리 추가.
  • internet 퍼미션 추가.
  • kakao developers에서 application 생성

이 정도가 있겠다.

위 사항들을 모두 완료했으면 kakao application의 세팅 먼저 진행해 주겠다.

 

Kakao Application 세팅

자신이 생성한 application에서 android 플랫폼을 연결해 준다.

패키지명은 자신이 생성한 android project의 package id를 사용한다. 그러면 마켓 URL도 자동으로 생성된다.

key 해시는 자신의 sha 1 key를 base64로 변환해서 넣어줘야 한다.

 

변환은 아래의 사이트에서 진행한다.

https://base64.guru/converter/encode/hex

 

Hex to Base64 | Base64 Encode | Base64 Converter | Base64

Hex to Base64 The “Hex to Base64” converter is a smart tool which is able to convert online Hex values to Base64 strings. I call him “smart” because it accepts several written representations of hexadecimal values. The conversion process is quite s

base64.guru

 

변환이 완료된 코드를 key 해시에 넣어 준다.

만약 앱 출시를 기획하고 있으면 release 전용 sha 1 key도 같이 추가해 줘야 한다.

 

다음으로 kakao login을 활성화해 준다.

https://developers.kakao.com/console/app/1132069/product/login

 

카카오계정

 

accounts.kakao.com

 

활성화 진행 후 맨 아래에 Redirect URL은 kakao login을 rest api로 구현할 경우에만 작성해 준다.

나의 경우에는 작성하지 않았다.

 

로그인 동의항목에는 간단하게 2가지 목록만 불러오도록 해주었다.

 

 

Android에서 구현

이제 android에서 kakao login을 구현해 보겠다.

우선 manifest부터 먼저 작업해 주겠다.

 

manifest의 application 안에 아래의 코드를 넣어 준다.

<activity android:name="com.kakao.sdk.auth.AuthCodeHandlerActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>

        <data android:host="oauth"
            android:scheme="@string/KAKAO_OAUTH_HOST"/>
    </intent-filter>
</activity>

사용자가 카카오 로그인을 시도할 때, 앱이 순간적으로 외부 브라우저로 포커싱이 되게 된다.

외부 브라우저에서 다시 우리 앱으로 돌아오게 해주는 것이 위 코드의 역할이다.

로그인 완료 후 우리 앱으로 돌아오게 하려면 위의 코드를 적용해 주어야 한다.

data의 scheme를 보면 kakao_oauth_host가 있다.

이 코드는 gradle에서 local properties에서 불러온다.

 

local properties에 kakao(네이티브 앱 키)의 형식으로 값을 추가해 준다.

만약 네이티브 앱 키가 1000이면 kakao1000 이런 형식이다.

그리고 sync 후 clean, rebuild project를 진행하면 @string/KAKAO_OAUTH_HOST를 쓸 수 있게 된다.

 

Repository 구현

repository에 로그인 로직을 진행해 준다.

interface KakaoSignInRepository {
    fun kakaoLogin(onSuccess: () -> Unit, onFailure: (Throwable) -> Unit)
}
class KakaoSignInRepositoryImpl @Inject constructor(
    @ApplicationContext private val context: Context,
    private val userApiClient: UserApiClient,
    private val auth: FirebaseAuth,
    private val functions: FirebaseFunctions
): KakaoSignInRepository {
    override fun kakaoLogin(onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) {
        if (userApiClient.isKakaoTalkLoginAvailable(context)) {
            loginWithKakaoTalk(onSuccess, onFailure)
        } else {
            loginWithKaKaoAccount(onSuccess, onFailure)
        }
    }
    
    . . .
}

repository에서는 hilt로 inject 받은 context, userApiClient, firebaseAuth, FirebaseFunctions를 파라미터로 구성해 주었다.

 

userApiClient는 kakao sdk 라이브러리 전용 클래스이다.

카카오톡 로그인이 가능한지 여부를 파악한 다음, 카카오톡 로그인 또는 카카오계정 로그인으로 진행된다.

// 카카오톡 로그인
private fun loginWithKakaoTalk(onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) {
    userApiClient.loginWithKakaoTalk(context) { token, error ->
        if (error != null) {
            if (error is ClientError && error.reason == ClientErrorCause.Cancelled) onFailure(error)
            else loginWithKaKaoAccount(onSuccess, onFailure)
        } else if (token != null) {
            getCustomToken(token.accessToken, onSuccess, onFailure)
        }
    }
}

// 카카오 계정으로 로그인
private fun loginWithKaKaoAccount(onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) {
    userApiClient.loginWithKakaoAccount(context) { token: OAuthToken?, error: Throwable? ->
        if (error != null) {
            onFailure(error)
        } else if (token != null) {
            getCustomToken(token.accessToken, onSuccess, onFailure)
        }
    }
}

카카오톡, 카카오계정 로그인을 진행해 준 다음, 성공했으면 firebase auth token 생성을 위한 getCustomToken 함수를 실행한다.

 

private fun getCustomToken(accessToken: String, onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) {
    val data = hashMapOf("token" to accessToken)

    functions.getHttpsCallable("kakaoCustomAuth")
        .call(data)
        .addOnCompleteListener { task ->
            try {
                val result = task.result?.data as HashMap<*, *>
                var mKey: String? = null
                for (key in result.keys) {
                    mKey = key.toString()
                }
                val customToken = result[mKey!!].toString()

                firebaseAuthWithKakao(customToken, onSuccess, onFailure)
            } catch (e: Exception) {
                // 호출 실패
                onFailure(e)
            }
        }
}

이 함수에서 deploy 한 firebase functions를 실행한다.

이전에 flutter에서 firebase functions를 호출했을 때는 rest api 호출 방식으로 url을 정의해서 호출했는데, android에는 그런 거 필요 없이 전용 호출 함수가 있어서 편리했다.

getHttpsCallable에 자신이 deploy 한 함수의 name을 설정해 주고 call 해주면 된다.

data에는 request body로 들어가는 데이터들을 넣는 것 같다.

토큰 생성까지 끝내면 최종적으로 firebase authentication에 사용자 데이터를 생성해 주어야 한다.

 

private fun firebaseAuthWithKakao(customToken: String, onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) {
    auth.signInWithCustomToken(customToken).addOnCompleteListener { result ->
        if (result.isSuccessful) {
            onSuccess()
        } else {
            onFailure(result.exception!!)
        }
    }
}

커스텀 token을 이용한 sign in이기 때문에 signInWithCustomToken을 이용해 주었고, 여기까지 성공했을 때 onSuccess()를 호출해 주었다.

 

UseCase & Hilt Module에 추가

usecase에는 정의한 repository를 hilt로 주입받아서 viewModel에서 쓸 수 있도록 해준다.

class KakaoLoginUseCase @Inject constructor(
    private val kakaoSignInRepository: KakaoSignInRepository
) {
    operator fun invoke(onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) {
        kakaoSignInRepository.kakaoLogin(onSuccess, onFailure)
    }
}

 

생성한 repository를 hilt module에 추가하는 작업은 필수적으로 진행해야 한다. usecase는 안 해도 상관없다.

@Binds
abstract fun bindKakaoSignInRepository(kakaoSignInRepositoryImpl: KakaoSignInRepositoryImpl): KakaoSignInRepository

 

repository에서 firebaseAuth, firebaseFunctions, userApiClient를 주입받아 사용했는데, 역시 그냥 쓰면 오류가 날 것이다.

module에 정의해 주면 오류를 해결할 수 있다.

@Provides
fun provideFirebaseAuth() = Firebase.auth

@Provides
fun provideFirebaseFunctions() = Firebase.functions

@Provides
@Singleton
fun provideUserApiClient(): UserApiClient = UserApiClient.instance

 

ViewModel 구현

마지막 단계로는 ViewModel이 남았다.

viewModel 구현은 쉽다.

먼저 kakao login 상태를 표시할 state를 만들어 준다.

private val _kakaoLogin = MutableStateFlow<LoginUiState>(LoginUiState.Init)
val kakaoLogin: StateFlow<LoginUiState> = _kakaoLogin.asStateFlow()
fun kakaoLogin() {
    viewModelScope.launch {
        _kakaoLogin.value = LoginUiState.Loading

        kakaoLoginUseCase(
            onSuccess = { _kakaoLogin.value = LoginUiState.Success },
            onFailure = { exception ->
                _kakaoLogin.value = LoginUiState.Failure(exception)
            }
        )
    }
}

Activity & Fragment에서는 위 viewModel 코드를 자신의 상황에 맞게 사용하면 된다.

uiState에서 success에 원래 결괏값이 들어가지만, sign in에서는 결괏값이 딱히 없기 때문에 object로 정의해 주었고, failure에는 error를 담을 data class로 정의해 주었다.

 

sealed interface LoginUiState {
    data object Init: LoginUiState
    data object Loading: LoginUiState
    data object Success: LoginUiState
    data class Failure(val e: Throwable): LoginUiState
}

 

 

구현 중 발생한 문제 상황

카카오 로그인을 구현하다가 카카오 로그인 버튼이 클릭되지 않는 문제가 있었다.

이 문제는 컴퓨터를 재부팅했더니 해결돼서 넘어가겠다.

 

 

정리

kakao 로그인을 clean architecture를 이용해 적용하기 위해 구조를 어떻게 짜야할지 고민을 많이 했던 것 같다.

현재 repository에 함수를 5개로 구성되어 있는 코드에서 kakaoLogin 함수만 repository에 정의해 놓은 상태이다. 다른 함수들도 repository에 넣어야 하는지 고민했지만, 다른 함수들도 넣게 되면 오히려 viewModel이 복잡해질 것 같아서 기각했다.

 

더 좋은 repository 코드를 짜기 위해 어떻게 해야 할지 고민했던 시간이었다.

728x90