[Android Studio][Kotlin] 카카오 로그인 구현

ideal dev·2023년 2월 2일
1
post-thumbnail

카카오 Developers 세팅

  1. https://developers.kakao.com 회원가입 또는 로그인
  2. 내 애플리케이션 클릭
  3. 애플리케이션 추가하기
  1. 앱 키를 발급받으려면 해시키를 넣어줘야하는데 해시키는 아래와 같이 구할 수 있다.
    해시키 구하는 방법

안드로이드 스튜디오 맨하단 Terminal 클릭 후 아래 코드를 입력하면 된다

keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -storepass android -keypass android | openssl sha1 -binary | openssl base64
  1. 그럼 카카오 디벨로퍼에서 앱키 목록들을 확인할 수 있다. 우리가 사용할 것은 네이티브 앱 키 이다.

위 웹사이트 설정 과정은 다른 분들이 자세하게 잘 설명해주셔서 문제가 없었는데,
카카오SDK 가 업데이트된 이후 안드로이드 스튜디오 셋팅글은 잘 안보여서 작성하게 되었다.

다른 글을 참고했을 때는 코드가 진짜 복잡했는데 SDK도 점점 업데이트 되면서 내가 구현해야 할 ? 복붙해야할 ..? ㅎㅎ.. 코드들도 상당히 줄어들었다. 감사합니다!


안드로이드 스튜디오 설정 방법

1. settings.gradle 클릭

maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/' } 코드 추가

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
        maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/' } // 카카오 로그인
    }
}

2. build.gradle(:app) 클릭

dependencies 부분에 추가

dependencies {
	...
    implementation "com.kakao.sdk:v2-user:2.12.1" // 카카오 로그인
    }

3. AndroidManifest.xml 에 인터넷 권한 추가

3. 카카오 SDK를 사용하려면 가장 먼저 네이티브 앱 키로 초기화를 해야 함.

Application 을 상속한 GlobalApplication 라는 클래스 하나 생성

아래와 같이 생성되는데, 여기에 SDK 초기화 코드를 넣어주어야 한다.

class GlobalApplication : Application() {
    
}

초기화 코드를 넣어준다.

class GlobalApplication : Application() {
        override fun onCreate() {
        super.onCreate()

        // Kakao Sdk 초기화
        KakaoSdk.init(this, "네이티브 앱 키 넣으시면 됩니당")
    }
}

4. 그럼 애플리케이션을 만들었으니 매니페스트에 적어줘야하므로

AndroidManifest.xml 에 들어가서
android:name = ".GlobalApplication" 코드와
meta-data 코드를 추가해준다.
name 은 그대로 적으면 되고, value 값은 네이티브앱 키 값이다.

예시) 네이티브앱 키 값 : akdfadfladflka

<meta-data
	android:name="com.kakao.sdk.AppKey"
    android:value="akdfadfladflka"/>

그럼 아래와 같이 완성된다.

5. 매니패스트에서 하나 더 해줘야할 것이 있다

카카오 로그인을 구현하기 위해서 카카오 인증 서버에서 전달해주는 인가 코드(Authorization Code)를 발급해 서비스 앱에 등록된 Redirect URI을 전달받아야 한다.

자세한 사항은 그림을 읽어보면 이해가 쉽다.
사용자가 카카오 계정 인증을 성공하고, 필수 동의 항목을 선택한 후에 로그인을 요청하면
서비스는 전달받은 인가 코드로 토큰을 요청하여 받는다.

그래서 하고 싶은 말이 무엇이냐, 인가 코드를 받기 위해서 새로운 액티비티를 생성하고, manifest 파일도 수정해주어야 한다.

아래 부분을 참고하면 되고,

4,5 를 모두 적용한 매니페스트 이다.

완성된 매니패스트

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:name=".GlobalApplication"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.RecordWOD"
        tools:targetApi="31">
        <meta-data
            android:name="com.kakao.sdk.AppKey"
            android:value="@string/kakao_app_key"/>

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </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" />

                <data android:host="oauth"
                    android:scheme="kakao네이티브앱키" />
            </intent-filter>

        </activity>
    </application>

</manifest>

✔︎ 오류 주의 : ERR_UNKNOWN_URL_SCHEME

네이티브 앱 키를 asdfasdfadf 라고 가정하겠습니다.

나같은 경우, AuthCodeHandlerActivity 매니페스트 내부를 이렇게 설정하여서 에러가 났다.

<data android:host="oauth"
android:scheme="asdfasdfadf" />

네이티브 앱 키 앞에 kakao 를 붙여주어야 오류가 안난다.

<data android:host="oauth"
android:scheme="kakaoasdfasdfadf" />

6. AuthCodeHandlerActivity 코드

그럼 매니페스트에 액티비티라고 적어줬으므로, 그에 맞는 액티비티를 생성한다.
AuthCodeHandlerActivity 액티비티 생성

코드

import android.content.ContentValues.TAG
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
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

class AuthCodeHandlerActivity : AppCompatActivity() {

    private val mCallback: (OAuthToken?, Throwable?) -> Unit = { token, error ->
        if (error != null) {
            Log.e(TAG, "로그인 실패 $error")
        } else if (token != null) {
            Log.e(TAG, "로그인 성공 ${token.accessToken}")
        }
    }

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

// 카카오톡 설치 확인
        if (UserApiClient.instance.isKakaoTalkLoginAvailable(this)) {
            // 카카오톡 로그인
            UserApiClient.instance.loginWithKakaoTalk(this) { token, error ->
                // 로그인 실패 부분
                if (error != null) {
                    Log.e(TAG, "로그인 실패 $error")
                    // 사용자가 취소
                    if (error is ClientError && error.reason == ClientErrorCause.Cancelled) {
                        return@loginWithKakaoTalk
                    }
                    // 다른 오류
                    else {
                        UserApiClient.instance.loginWithKakaoAccount(
                            this,
                            callback = mCallback
                        ) // 카카오 이메일 로그인
                    }
                }
                // 로그인 성공 부분
                else if (token != null) {
                    Log.e(TAG, "로그인 성공 ${token.accessToken}")
                }
            }
        } else {
            UserApiClient.instance.loginWithKakaoAccount(this, callback = mCallback) // 카카오 이메일 로그인
        }
    }
}

그럼 초기 셋팅 끝 !

6. 이제 로그인을 구현하고자 하는 액티비티나 프래그먼트에 들어간다.

나는 프래그먼트에 구현하였다!

먼저 callback 선언

    // 카카오 로그인
    // 카카오계정으로 로그인 공통 callback 구성
    // 카카오톡으로 로그인 할 수 없어 카카오계정으로 로그인할 경우 사용됨
    val callback: (OAuthToken?, Throwable?) -> Unit = { token, error ->
        if (error != null) {
            Log.e(TAG, "카카오계정으로 로그인 실패", error)
        } else if (token != null) {
            Log.i(TAG, "카카오계정으로 로그인 성공 ${token.accessToken}")
            GoMain()
        }
    }

GoMain() 은 로그인이 성공했을 때 어느 액티비티로 갈 지 적어준 메소드이다.
액티비티라면 Intent 코드,
프래그먼트라면 아래와 비슷한 코드를 적어주면 된당.

Navigation.findNavController(binding.root).navigate(R.id.action_loginFragment_to_mainFragment)

그리고 KakaoLoginBtn 이라는 버튼을 눌렀을 때, 실행되게 했다.
나는 BaseFragment 를 상속받아 중복되는 코드를 생략하였는데 그 과정에서 binding 코드도 생략되어서 아래에 같이 넣어두겠다.

여기서 액티비티라면 context 등의 코드들이 약간씩 수정될 것이당.

loginFragment 코드

class LoginFragment : BaseFragment<FragmentLoginBinding>() {

    companion object {
        fun newInstance() = LoginFragment()
    }
    // 카카오 로그인
    // 카카오계정으로 로그인 공통 callback 구성
    // 카카오톡으로 로그인 할 수 없어 카카오계정으로 로그인할 경우 사용됨
    val callback: (OAuthToken?, Throwable?) -> Unit = { token, error ->
        if (error != null) {
            Log.e(TAG, "카카오계정으로 로그인 실패", error)
        } else if (token != null) {
            Log.i(TAG, "카카오계정으로 로그인 성공 ${token.accessToken}")
            GoMain()
        }
    }
    override fun initView() {
        val context : Context = requireContext()
        // 버튼 클릭했을 때 로그인
        with(binding) {
            KakaoLoginBtn.setOnClickListener {

                // 카카오톡이 설치되어 있으면 카카오톡으로 로그인, 아니면 카카오계정으로 로그인
                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}")
                            GoMain()
                        }
                    }
                } else {
                    UserApiClient.instance.loginWithKakaoAccount(context, callback = callback)
                }


            }
        }
        //<--
    }


    private fun navigateToNotesScreen() {
        findNavController().navigate(R.id.action_loginFragment_to_mainFragment)
    }

    override fun getViewBinding(
        inflater: LayoutInflater,
        container: ViewGroup?
    ) = FragmentLoginBinding.inflate(inflater, container, false)

    fun GoMain(){
        // 로그인 -> 메인
        Navigation.findNavController(binding.root).navigate(R.id.action_loginFragment_to_mainFragment)
    }
}

BaseFragment 코드


// 추상화 클래스
// 클래스의 틀을 복제
abstract class BaseFragment<VB : ViewBinding> :
    Fragment() {
        private var _binding: VB by autoCleaned()
        val binding: VB get() = _binding

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = getViewBinding(inflater, container)
        return binding.root
    }

    abstract fun initView()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initView()
    }
    protected abstract fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): VB

}

실행하면 로그인이 잘 되는 화면을 볼 수 있다.
끝! 수고하셨습니다.


사실
https://developers.kakao.com/docs/latest/ko/kakaologin/common#intro-kakaologin
여기 정리가 너무 잘 되어있어서 내 포스팅이 허접해지는 순간이지만
은근 또 삽질은 했기에 어떻게 구현했는 지 정리해봤다 ㅎㅎ
나와 비슷한 삽질을 하고 계신분들께 도움이 되었으면 하는 마음을 담아 .. ~~

0개의 댓글