이번에 대학교에서 진행하는 공학경진대회에서 앱을 개발하던 도중 간편하게 로그인을 할 수 있는 방법과 현재 많은 웹에서 사용하는 방법인 API를 이용한 로그인을 구현하게 되어 후에 또 사용하게 될 경우를 대비하여 이렇게 작성하게 되었다.
Android SDK를 적용할 프로젝트의 settings.gradle(Project) 파일에 다음과 같이 Android SDK 레파지토리를 설정한다.
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/' }
}
}
build.gradle(Module) 파일에 필요한 모듈을 설정한다. 전체 모듈을 설치하거나 필요한 모듈만 선택해 설치하면 된다.
dependencies {
implementation "com.kakao.sdk:v2-all:2.15.0" // 전체 모듈 설치, 2.11.0 버전부터 지원
implementation "com.kakao.sdk:v2-user:2.15.0" // 카카오 로그인
implementation "com.kakao.sdk:v2-talk:2.15.0" // 친구, 메시지(카카오톡)
implementation "com.kakao.sdk:v2-story:2.15.0" // 카카오스토리
implementation "com.kakao.sdk:v2-share:2.15.0" // 메시지(카카오톡 공유)
implementation "com.kakao.sdk:v2-navi:2.15.0" // 카카오내비
implementation "com.kakao.sdk:v2-friend:2.15.0" // 카카오톡 소셜 피커, 리소스 번들 파일 포함
}
카카오 API를 통해 카카오 서버와 통신하기 위해 앱에 인터넷 사용 권한을 설정해야 한다. 다음과 같이 인터넷 사용 권한 설정을 추가해주면 된다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.sample">
<!-- 인터넷 사용 권한 설정-->
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
...
Java 8 사용을 위한 설정이 필요하다. 다음 예제를 참고하여 build.gradle(Module) 파일에 다음과 같이 자바 버전을 설정한다.
// Java 8 사용을 위한 build.gradle 설정
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
Android SDK를 사용하기 위해서는 이티브 앱 키로 초기화 해야 한다. 그렇기 때문에 Application()을 상속한 클래스를 하나 생성해준다.
나는 MyApplication이라는 이름으로 생성하였다.
import android.app.Application
import android.util.Log
import com.kakao.sdk.common.KakaoSdk
import com.kakao.sdk.common.util.Utility
import com.kakao.sdk.user.Constants.TAG
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 다른 초기화 코드들
// Kakao SDK 초기화
KakaoSdk.init(this, "{NATIVE_APP_KEY}")
}
}
NATIVE_APP_KEY는 이후 설명할 앱을 추가하는 과정인 키 해시 등록
파트를 보면 네이티브 앱 키
를 확인 할 수 있다.
이후 Manifest.xml에서의 application에도 Kakao SDK 초기화를 수행한 클래스의 이름을 설정해야 한다.
<application
<!-- android:name 설정 -->
android:name=".MyApplication"
...
>
키 해시 등록 이전에 중요한 작업이 있다.
나중에 네이티브 앱 키를 사용할 것이다.
이후 플랫폼에 들어가 Android 플랫폼을 등록한다.
패키지명은 반드시 Manifest.xml에 있는 package명과 동일하게 작성한다.
나는 Manifest.xml 파일을 확인했을 때 package명이 적혀있지 않아 직접 프로젝트를 생성했을 때의 package명과 동일하게 작성해주었다.
ex) com.example.sample
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.sample">
이제 제일 중요한 키 해시 등록이다.
공식 문서를 보면 확인하는 방법은 여러가지이지만 나는 제대로 동작하지 않아 코드를 통해 키 해시를 얻었다.
나는 초기화를 수행하였던 MyApplication에 키 해시를 얻는 코드를 추가하였다.
import android.app.Application
import android.util.Log
import com.kakao.sdk.common.KakaoSdk
import com.kakao.sdk.common.util.Utility
import com.kakao.sdk.user.Constants.TAG
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 다른 초기화 코드들
// Kakao SDK 초기화
KakaoSdk.init(this, "{NATIVE_APP_KEY}")
// 키 해시를 얻는 코드
var keyHash = Utility.getKeyHash(this)
Log.e(TAG, "해시 키 값 : ${keyHash}")
}
}
애뮬레이터를 실행 후 logcat에서 해시를 검색한 결과 다음과 같이 나온다.
이 키 해시를 등록해준다.
이 과정이 끝나면 설정은 끝났다.
본격적으로 로그인 기능을 구현해보자.
카카오 로그인 기능을 구현하기 위해서는 리다이렉션(Redirection)을 통해 인가 코드를 받아야 한다. 이를 위해 AndroidManifest.xml에 액티비티(Activity) 설정이 필요하다.
기존에 존재하는 .MainActivity의 <activity>
코드 밑에 추가하면 된다.
<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" />
<!-- Redirect URI: "kakao${NATIVE_APP_KEY}://oauth" -->
<data android:host="oauth"
android:scheme="kakao${NATIVE_APP_KEY}" />
</intent-filter>
</activity>
중요한 부분은 android:scheme="kakao${NATIVE_APP_KEY}"
NATIVE_APP_KEY 부분에 우리가 생성한 앱의 네이티브 앱 키를 입력해주면 된다.
기억해야할 점은 네이티브 앱 키 앞에 kakao를 무조건 붙어야한다.
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1"
implementation 'androidx.activity:activity-ktx:1.7.2'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
혹시 코드 일부가 import 되지 않는다면 관련된 모듈을 찾아 작성하거나 댓글을 남긴다면 확인해보고 알려드리겠습니다.
로그인과 로그아웃, 로그인 상태를 확인 할 코드 예제이다.
사용자의 이름, 이메일을 가져오는 코드도 추가하였다.
<LinearLayout 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"
android:orientation="vertical"
android:gravity="center_horizontal"
android:padding="16dp"
tools:context=".MainActivity">
<Space
android:layout_width="match_parent"
android:layout_height="10dp" />
<Button
android:id="@+id/btn_kakao_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="카카오 로그인하기" />
<Button
android:id="@+id/btn_kakao_logout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="카카오 로그아웃하기" />
<TextView
android:id="@+id/tv_login_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp" />
<Button
android:id="@+id/buttonInfo"
android:layout_width="205dp"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
import android.annotation.SuppressLint
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.TextView
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.asLiveData
import com.example.kakaotest2.KakaoOauthViewModel.Companion.TAG
import com.kakao.sdk.user.UserApiClient
class MainActivity : AppCompatActivity() {
private lateinit var kakaoOauthViewModel: KakaoOauthViewModel
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ViewModelProvider를 통해 ViewModel 인스턴스 생성
kakaoOauthViewModel = ViewModelProvider(this, KakaoOauthViewModelFactory(application)).get(KakaoOauthViewModel::class.java)
val btnKakaoLogin = findViewById<Button>(R.id.btn_kakao_login)
val btnKakaoLogout = findViewById<Button>(R.id.btn_kakao_logout)
val tvLoginStatus = findViewById<TextView>(R.id.tv_login_status)
btnKakaoLogin.setOnClickListener {
kakaoOauthViewModel.kakaoLogin()
}
btnKakaoLogout.setOnClickListener {
kakaoOauthViewModel.kakaoLogout()
}
kakaoOauthViewModel.isLoggedIn.asLiveData().observe(this) { isLoggedIn ->
val loginStatusInfoTitle = if (isLoggedIn) "로그인 상태" else "로그아웃 상태"
tvLoginStatus.text = loginStatusInfoTitle
}
// 사용자 정보 가져오기
val test = findViewById<Button>(R.id.buttonInfo)
test.setOnClickListener {
getInfo()
}
}
fun getInfo(){
// 사용자 정보 요청 (기본)
UserApiClient.instance.me { user, error ->
if (error != null) {
Log.e(TAG, "사용자 정보 요청 실패", error)
}
else if (user != null) {
Log.i(TAG, "사용자 정보 요청 성공" +
"\n회원번호: ${user.id}" +
"\n이메일: ${user.kakaoAccount?.email}" +
"\n닉네임: ${user.kakaoAccount?.profile?.nickname}" )
}
}
}
import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.kakao.sdk.auth.model.OAuthToken
import com.kakao.sdk.common.model.ClientError
import com.kakao.sdk.common.model.ClientErrorCause
import com.kakao.sdk.user.UserApiClient
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class KakaoOauthViewModel(application : Application) : ViewModel() {
// 비어있는 생성자 추가
constructor() : this(Application())
companion object {
const val TAG = "KakaoOauthViewModel"
}
private val context = application.applicationContext
// 로그인 여부 초기값 false, 바인딩
val isLoggedIn = MutableStateFlow<Boolean>(false)
// 코루틴
fun kakaoLogin(){
viewModelScope.launch {
// handleKakaoLogin은 true false 로그인 성공 여부 리턴
// emit으로 로그인 여부를 이벤트로 보냄
isLoggedIn.emit(handleKakaoLogin())
}
}
// 코루틴
fun kakaoLogout(){
viewModelScope.launch {
// handleKakaoLogin은 true false 로그인 성공 여부 리턴
// emit으로 로그인 여부를 이벤트로 보냄
if(handleKakaoLogout()){
isLoggedIn.emit(false)
}
}
}
private suspend fun handleKakaoLogout() : Boolean =
suspendCoroutine { continuation ->
// 로그아웃
UserApiClient.instance.logout { error ->
if (error != null) {
Log.e(TAG, "로그아웃 실패. SDK에서 토큰 삭제됨", error)
continuation.resume(false)
}
else {
Log.i(TAG, "로그아웃 성공. SDK에서 토큰 삭제됨")
continuation.resume(true)
}
}
}
private suspend fun handleKakaoLogin() : Boolean =
suspendCoroutine<Boolean> { continuation ->
// 로그인 조합 예제
// 카카오계정으로 로그인 공통 callback 구성
// 카카오톡으로 로그인 할 수 없어 카카오계정으로 로그인할 경우 사용됨
val callback: (OAuthToken?, Throwable?) -> Unit = { token, error ->
if (error != null) {
Log.e(TAG, "카카오계정으로 로그인 실패", error)
continuation.resume(false)
} else if (token != null) {
Log.i(TAG, "카카오계정으로 로그인 성공 ${token.accessToken}")
continuation.resume(true)
}
}
// 카카오톡이 설치되어 있으면 카카오톡으로 로그인, 아니면 카카오계정으로 로그인
if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) {
UserApiClient.instance.loginWithKakaoTalk(context) { token, error ->
if (error != null) {
Log.e(TAG, "카카오톡으로 로그인 실패", error)
// 사용자가 카카오톡 설치 후 디바이스 권한 요청 화면에서 로그인을 취소한 경우,
// 의도적인 로그인 취소로 보고 카카오계정으로 로그인 시도 없이 로그인 취소로 처리 (예: 뒤로 가기)
if (error is ClientError && error.reason == ClientErrorCause.Cancelled) {
return@loginWithKakaoTalk
}
// 카카오톡에 연결된 카카오계정이 없는 경우, 카카오계정으로 로그인 시도
UserApiClient.instance.loginWithKakaoAccount(context, callback = callback)
} else if (token != null) {
Log.i(TAG, "카카오톡으로 로그인 성공 ${token.accessToken}")
}
}
} else {
UserApiClient.instance.loginWithKakaoAccount(context, callback = callback)
}
}
}
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import android.app.Application
class KakaoOauthViewModelFactory(private val application: Application) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(KakaoOauthViewModel::class.java)) {
return KakaoOauthViewModel(application) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
로그인 하기 클릭하였을 때
로그인 후
밑의 버튼을 누르면 사용자 정보를 가져오고 logcat에서 확인 가능하다.
이 밖에도 앱과 spring boot와 통신하는 기능도 추가하였는데 추후에 작성할 예정이다.
츨처 : Kakao Developers
즐겁게 읽었습니다. 유용한 정보 감사합니다.