[Kotlin] Session is not initialized. Call KakaoSDK#init first. 에러문제

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. 문제가 생겨 어떻게 문제를 해쳐나야할 지 막막합니다.

처음 계획한 방향성

  1. 안드로이드 앱에 부여되는 해시 키를 가지고 카카오 서버로 건낸 다음 [callback] 과정을 통해 카카오 사용자 인증 토큰을 가져온다.
  2. [callback] : 안드로이드 앱 액티비티 카카오 로그인 버튼 → [1. 카카오 앱 혹은 웹 활성화 → 2. 카카오 내에서 로그인 확인 → 3. 회원확인] → 안드로이드 앱 : OnActivityResult
  3. onActivityResult 를 통해 가져온 값(accessToken)을 다시 안드로이드 앱에 셋팅한 kakaoSDK 에 전달하여 최종 로그인 여부를 확인한다.
  4. 최종 카카오 로그인 성공시 사용자 정보를 파이어베이스에 저장하기 위해 회원가입 화면으로 넘어가기

코드는 다음과 같습니다.

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)

SDK는 v2만 사용 부탁드립니다.

https://developers.kakao.com/docs/latest/ko/getting-started/sdk-android#select-module - Android 모듈설정

implementation group: ‘com.kakao.sdk’, name: ‘usermgmt’, version: ‘2.12.1’ 로 바꿔봤는데 액티비티별로 Session, usermgmt, KakaoSDK 등등 다 빨간색으로 바뀌고 인폴트가 안되는데 이렇게 바꾸는 게 맞나요?

v1 SDK와 v2 SDK를 같이 사용하시다 보니 문제가 발생하는것으로 보입니다.

참조 오류는 v1의 참조가 끊기어 발생하는 오류 같습니다.
v1 SDK는 23년 11월 지원 종료되므로 v2만 사용부탁드립니다.

기존에 v1을 사용하고 계셨다면 아래 마이그레이션 가이드도 참고하시면 좋을것 같습니다.

Kakao Developers

1개의 좋아요

답변 감사합니다. 혹시 GlobalApplication name 관련해서 궁금해서 그러는데 다음과 같이 이름을 작성하면 오류가 날까요? 폴더 안에 넣어서 관리를 시도하고 있거든요.

<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>

image

안녕하세요

공식 문서에서 가이드하는 것과 다른 형태가 되더라도 별도로 생성한 Application 클래스를 올바르게 지정해주기만 하면 android:name을 어떻게 지정하던지 문제 없습니다

2개의 좋아요

친절한 설명 감사합니다.