부트캠프 최종 팀 프로젝트 기록 9
서론
지난 포스팅과 연결된다.
https://rkdrkd-history.tistory.com/244
지난 포스팅에는 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
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
변환이 완료된 코드를 key 해시에 넣어 준다.
만약 앱 출시를 기획하고 있으면 release 전용 sha 1 key도 같이 추가해 줘야 한다.
다음으로 kakao login을 활성화해 준다.
https://developers.kakao.com/console/app/1132069/product/login
활성화 진행 후 맨 아래에 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 코드를 짜기 위해 어떻게 해야 할지 고민했던 시간이었다.
'⛏️ | 개발 기록 > 🪐 | Cosmic Detox' 카테고리의 다른 글
[Android] SplashScreen Api를 이용해 로그인 여부 확인 (0) | 2024.09.17 |
---|---|
[Android] Firebase Google One-Tap login으로 마이그레이션 (0) | 2024.09.13 |
[Node.js] Intellij, Vscode에서 Firebase Functions 개발을 위한 Node.js 프로젝트 세팅 (1) | 2024.09.11 |
[Android] 앱 서비스 출시를 위한 개인정보처리방침 및 이용약관 작성하기 (0) | 2024.09.09 |
[Android] runnable, handler를 이용해 현재 열려 있는 앱 감지 (0) | 2024.09.05 |