implementation "com.kakao.sdk:v2-user:2.12.0"
implementation group: 'com.kakao.sdk', name: 'usermgmt', version: '1.27.0'
앱 ID : 839720
안녕하세요. 저는 코틀린을 독학하면서 어플을 만들고 있습니다.
현재 카카오 간편 로그인 버튼을 누르면 firebase로 토큰을 넘겨받아 이중으로 인증하는 절차를 만들어보고 있습니다. 왜냐하면 파이어베이스와 카카오 사용자 정보를 통합해서 관리하고 싶었거든요. 아직 사용자 정보를 가져오는 방법 조차 이해가 부족하여 파이어 베이스를 통해 유저 정보를 가져오고 싶습니다.
참고링크 : [Kotlin][Firebase] Kakao Login 구현 #3
문제는 Session is not initialized. Call KakaoSDK#init first. 문제가 생겨 어떻게 문제를 해쳐나야할 지 막막합니다.
처음 계획한 방향성
- 안드로이드 앱에 부여되는 해시 키를 가지고 카카오 서버로 건낸 다음 [callback] 과정을 통해 카카오 사용자 인증 토큰을 가져온다.
- [callback] : 안드로이드 앱 액티비티 카카오 로그인 버튼 → [1. 카카오 앱 혹은 웹 활성화 → 2. 카카오 내에서 로그인 확인 → 3. 회원확인] → 안드로이드 앱 : OnActivityResult
- onActivityResult 를 통해 가져온 값(accessToken)을 다시 안드로이드 앱에 셋팅한 kakaoSDK 에 전달하여 최종 로그인 여부를 확인한다.
- 최종 카카오 로그인 성공시 사용자 정보를 파이어베이스에 저장하기 위해 회원가입 화면으로 넘어가기
코드는 다음과 같습니다.
AndroidManifest 일부(GlobalApplication.kr 파일은 kakaologinprocessing 파일을 만들어 보관중)
<application
android:name=".kakaologinprocessing.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:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SNSGapp"
tools:targetApi="31">
<meta-data
android:name="com.kakao.sdk.AppKey"
android:value="@string/kakao_app_key" />
<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>
GlobalApplication.kr(SDK쪽에서 계속 앱이 튕김)
class GlobalApplication : Application() {
companion object { //singleton 사용
var instance: GlobalApplication? = null
}
override fun onCreate() {
super.onCreate()
// 카카오 SDK 초기화
instance = this
KakaoSdk.init(this, getString(R.string.kakao_app_key))
// ver 1 -> ver2: KakaoSdk.init으로 바꿔야함
if(KakaoSDK.getAdapter() == null) { KakaoSDK.init(KakaoSDKAdapter(getAppContext())) }
}
override fun onTerminate() {
super.onTerminate()
instance = null
}
fun getAppContext(): GlobalApplication {
checkNotNull(instance) {
"This Application does not inherit com.example.App"
}
return instance!!
} }//class
kakaoSDKAdapter.kr(그 와중에 첫째 줄Context 비활성화됨)
class KakaoSDKAdapter(Context: GlobalApplication) : KakaoAdapter(){
override fun getApplicationConfig(): IApplicationConfig {
return IApplicationConfig {
GlobalApplication.instance?.getAppContext()
}}
override fun getSessionConfig() : ISessionConfig {
return object : ISessionConfig{
override fun getAuthTypes(): Array<AuthType> {
return arrayOf(AuthType.KAKAO_LOGIN_ALL) // 모든 로그인 방식 제공
// Auth Type
// KAKAO_TALK : 카카오톡 로그인 타입
// KAKAO_STORY : 카카오스토리 로그인 타입
// KAKAO_ACCOUNT : 웹뷰 다이얼로그를 통한 계정연결 타입
// KAKAO_TALK_EXCLUDE_NATIVE_LOGIN : 카카오톡 로그인 타입과 함께 계정생성을 위한 버튼을 함께 제공
// KAKAO_LOGIN_ALL : 모든 로그인 방식을 제공
}
override fun isUsingWebviewTimer(): Boolean {
return false
}
override fun isSecureMode(): Boolean {
return true
}
override fun getApprovalType(): ApprovalType {
return ApprovalType.INDIVIDUAL
}
override fun isSaveFormData(): Boolean {
return true
}
}
}
}
SessionCallback.kt
class SessionCallback(val context : LoginCheckActivity): ISessionCallback {
private val TAG : String = “로그/SessionCallback”
override fun onSessionOpened() {
Toast.makeText(GlobalApplication.instance, "Successfully logged in to Kakao. Now creating or updating a Firebase User.", Toast.LENGTH_LONG).show()
UserManagement.getInstance().me(object : MeV2ResponseCallback(){
override fun onSuccess(result: MeV2Response?) {
if(result != null){
Log.i("Log", "ID : ${result!!.id}")
Log.i("Log","E-Mail : ${result.kakaoAccount.email}")
Log.i("Log", "Gender : ${result.kakaoAccount.gender}")
Log.d(TAG, "세션 오픈")
val accessToken = Session.getCurrentSession().tokenInfo.accessToken
context.getFirebaseJwt(accessToken).continueWithTask { task ->
val firebaseToken = task.result
val auth = FirebaseAuth.getInstance()
auth.signInWithCustomToken(firebaseToken!!)
}.addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d(TAG, "Successfully created a Firebase user")
context.startMainActivity()
}
else {
Toast.makeText(GlobalApplication.instance,"Failed to create a Firebase user.", Toast.LENGTH_LONG).show()
if (task.exception != null) {
Log.e(TAG, task.exception.toString())
}
context.updateUI() // todo 다시 회원가입 여부를 묻기 위해 updateUI 확인하기
}
}
}
}
override fun onSessionClosed(errorResult: ErrorResult?) {
Log.e(TAG, "세션 종료")
}
override fun onFailure(errorResult: ErrorResult?) {
val errorCode = errorResult?.errorCode
val clientErrorCode = -777
if(errorCode == clientErrorCode){
Log.e(TAG, "카카오톡 서버의 네트워크가 불안정합니다. 잠시 후 다시 시도해주세요.")
}else{
Log.e(TAG, "알 수 없는 오류로 카카오로그인 실패 \n${errorResult?.errorMessage}")
}
}
})
}
override fun onSessionOpenFailed(exception: KakaoException?) {
Log.e(TAG, "onSessionOpenFailed ${exception?.message}")
context.onStart() // session 연결 실패 시 LoginActivity 로 이동
}
}
LoginCheckActivity.kr(onActivityResult 부분은 어떻게 고쳐야 될 지도 모름, getFirebaseJwt의 'Requset’도 비활성화됨)
open class LoginCheckActivity : AppCompatActivity() {
private var _binding: ActivityLoginCheckBinding? = null
private val binding get() = _binding!!
// private lateinit var binding : ActivityLoginCheckBinding
private val TAG: String = "로그"
// 로그인 공통 callback (login 결과를 SessionCallback.kt 으로 전송)
private lateinit var callback: SessionCallback
private lateinit var fbAuth: FirebaseAuth // Firebase Auth
override fun onCreate(savedInstanceState: Bundle?) {
// val binding by lazy { ActivityLoginCheckBinding.inflate(layoutInflater) } // viewBinding을 통해 엑티비티 화면과 연결할 수 있는 명령어를 활성화 시킨다.
_binding = ActivityLoginCheckBinding.inflate(layoutInflater)
super.onCreate(savedInstanceState)
setContentView(binding.root)
// ---------- 배경화면-------------
// 배경화면에 gif 영상를 가져오기 위한 명령어
Glide.with(this).load(R.drawable.intro_activity_small).into(binding.loginBackGround)
// 색상칠하기, https://swalloow.tistory.com/89
binding.loginBackGround.setColorFilter(Color.parseColor("Gray"), PorterDuff.Mode.MULTIPLY)
// ---------- 배경화면-------------
// -------------버튼 기능 활성화-------------
val intent = Intent(this, AnotherLoginActivity::class.java)
binding.anotherLoginMethodButton.setOnClickListener {
startActivity(intent)
}
// -------------버튼 기능 활성화-------------
// todo ------------- 카카오 버튼 연결하기------------
Log.d(TAG, "LoginActivity - onCreate() called")
fbAuth = Firebase.auth // Initialize Firebase Auth
callback = SessionCallback(this) // Initialize Session
binding.kakaoLoginButton.setOnClickListener {
kakaoLoginStart()
}
binding.kakaoStartButton.setOnClickListener {
startMainActivity()
}
// todo ------------- 카카오 버튼 연결하기------------
}// onCreate
// todo ------------- onStart() : 버튼 기능설정 -----------
public override fun onStart() {
super.onStart()
Log.d(TAG, "LoginActivity - onStart() called")
updateUI()
} // onStart
// todo ------------- onStart() : 버튼 기능설정 -----------
// todo ----------------updateUI()---------------------------
fun updateUI() {
Log.d(TAG, "LoginActivity - updateUI() called")
val user = fbAuth.currentUser
if (user != null) { // UI ver.1
binding.kakaoStartButton.visibility = View.VISIBLE
binding.kakaoLoginButton.visibility = View.GONE
} else { // UI ver.2
binding.kakaoStartButton.visibility = View.GONE
binding.kakaoLoginButton.visibility = View.VISIBLE
}
} // updateUI
// todo ----------------updateUI()---------------------------
// todo ----------------kakaoLoginStart()--------------------
/* KAKAO LOGIN */
private fun kakaoLoginStart() {
Log.d(TAG, "LoginActivity - kakaoLoginStart() called")
val keyHash = Utility.getKeyHash(this) // keyHash 발급
Log.d(TAG, "KEY_HASH : $keyHash")
Session.getCurrentSession().addCallback(callback)
Session.getCurrentSession().open(AuthType.KAKAO_LOGIN_ALL, this)
} // kakaoLoginStart
// todo ----------------kakaoLoginStart()--------------------
/*
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
Log.d(TAG, “LoginActivity - onActivityResult() called”)
if (Session.getCurrentSession().handleActivityResult(requestCode, resultCode, data)) {
Log.i(TAG, "Session get current session")
return
}
super.onActivityResult(requestCode, resultCode, data)
}
*/
// todo -- getFirebaseJwt() : SessionCallback 속 명령어, 카카오 토큰을 가지고 파이어 베이스 토큰까지 인증한 상황------------
open fun getFirebaseJwt(kakaoAccessToken: String): Task<String> {
Log.d(TAG, "LoginActivity - getFirebaseJwt() called")
val source = TaskCompletionSource<String>()
val queue = Volley.newRequestQueue(this)
val url = "https://kapi.kakao.com/v2/user/me?secure_resource=true" // "http://localhost:8000/verifyToken" // validation server
val validationObject: HashMap<String?, String?> = HashMap()
validationObject["token"] = kakaoAccessToken
val request: JsonObjectRequest = object : JsonObjectRequest(
Request.Method.POST, url,
JSONObject(validationObject as Map<*, *>),
Response.Listener { response ->
try {
val firebaseToken = response.getString("firebase_token")
source.setResult(firebaseToken)
} catch (e: Exception) {
source.setException(e)
}
},
Response.ErrorListener { error ->
Log.e(TAG, error.toString())
source.setException(error)
}) {
override fun getParams(): Map<String, String> {
val params: MutableMap<String, String> = HashMap()
params["Authorization"] = String.format("Basic %s", Base64.encodeToString(
String.format("%s:%s", "token", kakaoAccessToken)
.toByteArray(), Base64.DEFAULT)
)
return params
}
}
queue.add(request)
return source.task // call validation server and retrieve firebase token
}
// todo ---------- getFirebaseJwt() : 카카오 토큰을 가지고 파이어 베이스 토큰까지 인증한 상황------------
// todo ---------- startMainActivity() : 파이어 베이스로 최종 로그인에 성공한 자만 메인으로 가기 --------
fun startMainActivity() {
Log.d(TAG, "LoginActivity - startMainActivity() called")
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
} // startMainActivity
// todo ---------- startMainActivity() : 파이어 베이스로 최종 로그인에 성공한 자만 메인으로 가기 --------
override fun onDestroy() {
super.onDestroy()
Session.getCurrentSession().removeCallback(callback)
} // onDestroy
// ----------- 뒤로가기 누르면 앱 종료시키기 ---------------
// 참고 링크 : https://jo-coder.tistory.com/17
private var backPressedTime: Long = 0
override fun onBackPressed() {
Log.d("TAG", "뒤로가기")
// 2초내 다시 클릭하면 앱 종료
if (System.currentTimeMillis() - backPressedTime >= 1500) {
backPressedTime = System.currentTimeMillis()
Toast.makeText(this, "'뒤로' 버튼을 한번 더 누르시면 앱이 종료됩니다.", Toast.LENGTH_SHORT).show()
} else {
ActivityCompat.finishAffinity(this)
System.runFinalization()
exitProcess(0)
}
} // onBackPressed()
// ----------- 뒤로가기 누르면 앱 종료시키기 ---------------
} // class
결과
E/AndroidRuntime: FATAL EXCEPTION: main
Process: kr.co.tourapy.snsgapp, PID: 28344
java.lang.IllegalStateException: Session is not initialized. Call KakaoSDK#init first.
at com.kakao.auth.Session.getCurrentSession(Session.java:112)
at kr.co.tourapy.snsgapp.kakaologinprocessing.LoginCheckActivity.kakaoLoginStart(LoginCheckActivity.kt:142)
at kr.co.tourapy.snsgapp.kakaologinprocessing.LoginCheckActivity.onCreate$lambda-1(LoginCheckActivity.kt:101)
at kr.co.tourapy.snsgapp.kakaologinprocessing.LoginCheckActivity.$r8$lambda$RD2d4Q1jOX4IOkKYN6_XCSQ_FFI(Unknown Source:0)
at kr.co.tourapy.snsgapp.kakaologinprocessing.LoginCheckActivity$$ExternalSyntheticLambda1.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:7441)
at android.view.View.performClickInternal(View.java:7418)
at android.view.View.access$3700(View.java:835)
at android.view.View$PerformClick.run(View.java:28676)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7839)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)