TL;DR ?

  • 안드로이드 앱에 카카오 로그인을 연결할 때는 네이티브로 개발하는 것을 권장한다. 앱에서의 연결을 통해 사용자 정보를 받아오고 이를 API와 연결하든... 네이티브에서 다 하든.. 그 뒤론 알아서!
  • 카카오 로그인 관련해선 엄청나게 많은 블로그가 있지만 최신 버전(?) 그리고 코틀린(kotlin) 코드가 없어서 고생 좀 했다.
  • 카카오 개발 가이드는 알고 보면 쉽고, 모르고 보면 어렵다. 그래도 공식이니 읽어봐야한다. 언제 또 바뀔 지 모른다...
  • 사진의 출처는 카카오 개발 가이드입니다.

시작

안드로이드 앱에 카카오 로그인 기능을 추가해야 했다. API 서버(node.js)가 따로 있어서 그 쪽에서 처리를 해야 하나 고민했는데, 카카오 개발팀에서는 안드로이드 네이티브에서 구현하는 것을 권장하는 글을 읽었다(참고).

그래서 네이티브에서 구현해 프로필까지 받아오고, 성공 또는 실패 시 콜백 처리까지 하는 방법을 테스트해보았다. 앞서 적은 것처럼 코틀린(kotlin) 코드가 아직 없고 공식 가이드에도 deprecated 된 코드가 있는 등 어려움이 있었다.

준비하기

  1. gradle(project 단위)에 kakao repository를 추가한다.

    allprojects {  
     ...
    }
    
    subprojects {
       repositories {
           mavenCentral()
           maven { url 'http://devrepo.kakao.com:8088/nexus/content/groups/public/' 
           }
       }
    }
  2. gradle(app 단위)에 dependencies를 추가한다. (로그인 sdk만 포함하였다.)

     dependencies {
           // 카카오 로그인 sdk를 사용하기 위해 필요.
           implementation group: 'com.kakao.sdk', name: 'usermgmt', version: project.KAKAO_SDK_VERSION
     }    
  3. gradle(project 단위)에 KAKAO_SDK_VERSION을 추가한다. (최신버전 확인하기 - Android SDK / Full SDK Source 기준)

     ext.kotlin_version='xxx'
     ext.KAKAO_SDK_VERESION='1.23.0'
  4. Kakao developer에 가입 또는 로그인한다.

  5. 앱을 생성한다. (아이콘, 앱 이름, 회사명은 수정할 수 있다.)
    image.png

  6. 앱 키 중에서 네이티브 앱 키를 복사해둔다.
    image.png

  7. 안드로이드 res > values 폴더 안에 kakao_strings.xml 파일을 만들고 아래와 같이 앱 키를 저장한다.

     <resources>
         <string name="kakao_app_key">AAAAAAAAAAAAAAAAAAAAAA</string>
     </resources>
  8. AndroidManifest.xml 코드에 인터넷 사용 permission을 위한 코드와, 카카오 앱키 등록을 위한 코드를 작성한다.

     <!-- 인터넷 사용 permission -->
     <uses-permission android:name="android.permission.INTERNET" />
    
     <!-- 카카오 앱 키 사용 -->
     <meta-data
         android:name="com.kakao.sdk.AppKey"
         android:value="@string/kakao_app_key" />
    
     <!-- 카카오 로그인 시 웹 뷰를 띄우기 위한 코드 -->
     <activity
         android:name="com.kakao.auth.authorization.authcode.KakaoWebViewActivity"
         android:launchMode="singleTop"
         android:windowSoftInputMode="adjustResize">
    
         <intent-filter>
             <action android:name="android.intent.action.MAIN"/>
             <category android:name="android.intent.category.DEFAULT"/>
         </intent-filter>
     </activity>
  9. 키 해시 구하기

    • 키 해시를 구하는 방법은 1) cmd, 2) 코드 활용 이다. * 릴리즈 때는 또 바꿔주어야 한다.
    • 로그인 용 activity 안에 키 해시 구해서 출력하도록 하였다.
    • 카카오 개발가이드를 보면 PackageManager.GET_SIGNATURESpackageInfo!!.signatures 코드가 있는데 막상 적어보니 deprecated 되었다고 나왔다. 그래서 stackoverflow의 도움을 받아... GET_SIGNING_CERTIFICATES(API 28이상)를 이용하였다.
    fun getHashKey(context: Context): String? {
        try {
            if (Build.VERSION.SDK_INT >= 28) {
                val packageInfo = getPackageInfo(context, PackageManager.GET_SIGNING_CERTIFICATES)
                val signatures = packageInfo.signingInfo.apkContentsSigners
                val md = MessageDigest.getInstance("SHA")
                for (signature in signatures) {
                    md.update(signature.toByteArray())
                    return String(Base64.encode(md.digest(), NO_WRAP))
                }
            } else {
                val packageInfo =
                    getPackageInfo(context, PackageManager.GET_SIGNATURES) ?: return null

                for (signature in packageInfo!!.signatures) {
                    try {
                        val md = MessageDigest.getInstance("SHA")
                        md.update(signature.toByteArray())
                        return Base64.encodeToString(md.digest(), Base64.NO_WRAP)
                    } catch (e: NoSuchAlgorithmException) {
                        Dlog.w(
                            "Unable to get MessageDigest. signature=$signature"
                        )
                    }
                }
            }
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
        } 

        return null
    }

Global Application, Kakao SDK Adapter

  1. 안드로이드 앱 전역에서 공유할 수 있는 Global Application을 만든다.
    • 기존 Application 상속 클래스가 있다면 거기에 추가해야 한다.
    class GlobalApplication : Application() {
        override fun onCreate() {
            super.onCreate()

            instance = this
            KakaoSDK.init(KakaoSDKAdapter())
        }

        override fun onTerminate() {
            super.onTerminate()
            instance = null
        }

        fun getGlobalApplicationContext(): GlobalApplication {
            checkNotNull(instance) { "this application does not inherit com.kakao.GlobalApplication" }
            return instance!!
        }

        companion object {
            var instance: GlobalApplication? = null
        }
    }
  1. 카카오 로그인 sdk에서 제공하는 KakaoAdapter를 상속하는 KakaoSDKAdapter를 만든다.
    • 블로그로 찾아보면 isSecureMode가 빠져있어 그대로 복붙했을 때 에러가 난다. 공식 개발 가이드를 찾아보면 override 해야 하는 함수와 설명이 잘 적혀있다.
  class KakaoSDKAdapter : KakaoAdapter() {
      override fun getSessionConfig(): ISessionConfig {
          return object : ISessionConfig {
              override fun getAuthTypes(): Array<AuthType> {
                  return arrayOf(AuthType.KAKAO_ACCOUNT)
              }

              override fun isUsingWebviewTimer(): Boolean {
                  return false
              }

              override fun getApprovalType(): ApprovalType? {
                  return ApprovalType.INDIVIDUAL
              }

              override fun isSaveFormData(): Boolean {
                  return true
              }

              override fun isSecureMode(): Boolean {
                  return true
              }
          }
      }

      override fun getApplicationConfig(): IApplicationConfig {
          return IApplicationConfig {
              GlobalApplication.instance?.getGlobalApplicationContext()
          }
      }
  }
  1. Global Application을 실행하도록 manifest.xml을 수정한다.
    <application
           android:name=".GlobalApplication"
       ...
           >
    ...

로그인 버튼에 카카오 로그인 연동

  1. 버튼 추가
    버튼은 카카오에서 제공하는 버튼(카카오 로고와 카카오 계정으로 로그인 문구)을 이용하거나 직접 만들 수 있다.
  • 카카오 제공 버튼 추가

    • xml 코드와 콜백만 추가하면 된다. 자동으로 카카오 로그인 페이지로 연결되도록 이미 설정되어 있다.
      <com.kakao.usermgmt.LoginButton
      android:id="@+id/btn_kakao_login"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
       />    
  • 커스텀 버튼 추가

    • 커스텀 버튼에 클릭 이벤트로 카카오 로그인 세션을 수동으로 열어야 한다.
      button.onClick { // anko 사용
        session.addCallback(callback);
        session.open(AuthType.KAKAO_LOGIN_ALL, this);
      }
  1. 콜백 처리

    • 콜백에서 유저 프로필을 받아올 수 있다.

      class LoginActivity: AppCompatActivity() {
      private var callback: SessionCallback = SessionCallback()
      
      override fun onCreate(savedInstanceState: Bundle?){
          ...
      
          // Get hash key and print
          ...
      
          // 카카오 제공 버튼일 경우
          Session.getCurrentSession.addCallback(callback);
      }
      
      override fun onDestroy() {
          ...
          Session.getCurrentSession().removeCallback(callback);
      }
      
      override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
          if (Session.getCurrentSession().handleActivityResult(requestCode, resultCode, data)) {
              Dlog.d("session get current session")
              return
          }
      
          super.onActivityResult(requestCode, resultCode, data)
      }
      
        private class SessionCallback : ISessionCallback {
          override fun onSessionOpenFailed(exception: KakaoException?) {
              Dlog.e("Session Call back :: onSessionOpenFailed: ${exception?.message}")
          }
      
          override fun onSessionOpened() {
              UserManagement.getInstance().me(object : MeV2ResponseCallback() {
      
                  override fun onFailure(errorResult: ErrorResult?) {
                      Dlog.d("Session Call back :: on failed ${errorResult?.errorMessage}")
                  }
      
                  override fun onSessionClosed(errorResult: ErrorResult?) {
                      Dlog.e("Session Call back :: onSessionClosed ${errorResult?.errorMessage}")
      
                  }
      
                  override fun onSuccess(result: MeV2Response?) {
                      checkNotNull(result) { "session response null" }
                      // register or login
                  }
      
              })
          }
      
      }