
안드로이드에서 액티비티간 데이터를 주고받을 때 startActivityForResult와 onActivityResult를 사용했다. 1학기 모바일 프로젝트 할 때까지만 해도 잘 써왔던 저 놈들이 이번에 인프런 강의를 듣다보니 deprecated가 돼 있었다... 나는 어쩔 수 없이 이 둘을 대체할 다른 방법을 찾기 시작했다.
대체할 방법을 찾던 중 registerForActivityResult() 라는 메서드를 발견했고, 이를 공부한 후 계속 사용하게 될 부분이라 정리하고자 한다.
나는 로그인 액티비티에서 구글 아이디로 로그인 버튼을 눌렀을 때 구글 로그인 화면으로 이동하며 로그인 시 필요한 clientId를 구글 로그인 화면에 전달하고, 로그인 후 메인 액티비티로 이동하는 과정에서 사용하였다.
registerForActivityResult()는 총 세 가지 단계로 이루어진다.
Contract 정의 -> Contract 등록 -> Contract 실행
Contract가 실행됐을 때 어떤 형식의 데이터가 전달되며 어떤 액티비티가 실행되고, 다시 돌아온 후 어떤 데이터를 전달 받는지를 정의하는 부분이다.
Contract는 ActivityResultContract라는 추상클래스를 상속하는 서브 클래스이다. 해당 추상 클래스에는 createIntent()와 parseResult()라는 메소드가 존재한다.
createIntent()는 어떤 액티비티를 호출할 지 정의하고 호출을 위한 인텐트를 실행하는 메소드이다. 해당 메소드는 액티비티의 context와 호출할 액티비티에게 전달할 데이터를 매개변수를 갖는다.
parseResult()는 호출된 액티비티에서 다시 원래 액티비티로 돌아올 때 원래 액티비티에게 전달할 데이터를 정의하는 메소드이다. 이 메소드는 원래 액티비티에 전달할 데이터를 리턴한다.
class SignInIntentContract : ActivityResultContract<String, String>() {
var googleSignInClient: GoogleSignInClient? = null
//LoginActivity에서 구글 아이디로 로그인 버튼을 누르면 구글 로그인 화면으로 이동하는 intent를 만드는 함수.
override fun createIntent(context: Context, clientId: String): Intent {
//구글로그인 초기설정
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(clientId)
.requestEmail()
.build()
googleSignInClient = GoogleSignIn.getClient(context, gso)
//구글로그인 화면 intent
val signInIntent = googleSignInClient!!.signInIntent
return signInIntent
}
//구글 로그인 화면에서 다시 LoginActivity로 돌아올 때 호춯되는 함수.
override fun parseResult(resultCode: Int, intent: Intent?): String? {
return when (resultCode) {
//정상적으로 로그인이 이뤄진 경우 getTokenId 함수를 통해 얻은 tokenId를 LoginActivity에 전달.
Activity.RESULT_OK -> getTokenId(intent)
else -> null
}
}
//tokenId를 리턴해주는 함수.
fun getTokenId(data: Intent?): String? {
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
try {
// Google Sign In was successful, authenticate with Firebase
val account = task.getResult(ApiException::class.java)!!
return account.idToken
} catch (e: ApiException) {
// Google Sign In failed, update UI appropriately
println("getTokenId() tokenId를 정상적으로 얻어오지 못함=>\n${e}")
return null
}
}
}
위의 코드는 로그인 액티비티에서 구글 로그인 시 필요한 clientId와 함께 구글 로그인 화면을 호출하고, 구글 로그인 화면에서 로그인에 성공하면 로그인 액티비티에게 IdToken을 전달하는 코드이다.
첫 번째로 SignInIntentContract 클래스가 ActivityResultContract를 상속받은 것을 확인할 수 있다. 이 때 ActivityResultContract<String, String>은 intent를 호출 할 때 String 형식의 데이터(여기서는 clientId)를 전달하고, 다시 돌아올 때 String 형식의 데이터(여기서는 IdToken)을 전달 받는다는 것을 의미한다.
createIntent() 메서드에서는 firebase authentication 구글 로그인에서 제공하는 구글 로그인 intent를 전달 받은 clientId를 통해 생성하고, 이를 호출하는 것을 확인할 수 있다.
parseIntent() 메서드에서는 구글 로그인에서 다시 로그인 액티비티로 돌아올 때 로그인에 성공했을 경우 IdToken을 리턴하기 위해 IdToken을 가져오는 getToken() 메서드를 호출하는 것을 확인할 수 있다.
정의한 Contract를 등록해보자. 이 때 등록한다는 것은 호출 후 전달 받은 데이터를 활용해 다음 동작을 처리하는 것을 말한다. 즉, Contract 서브 클래스에서 parseIntent() 후 콜백 함수이다. Contract를 등록할 땐 registerForActivityResult()를 호출한다. registerForActivityResult() 함수는 등록까지 마친 Contract를 실행할 수 있는 ActivityResultLauncher를 리턴한다.
class LoginActivity : AppCompatActivity() {
var launcher: ActivityResultLauncher<String>? = null
override fun onCreate(savedInstanceState: Bundle?) {
...
//LoginActivity -> 구글로그인 화면 -> LoginActivity로 돌아온 후 콜백 함수.
//구글로그인 화면을 통해 얻어온 tokenId를 이용해 Firebase 사용자 인증 정보로 교환하고
//해당 정보를 사용해 Firebase에 인증합니다.
launcher = registerForActivityResult(SignInIntentContract()) { result: String? ->
result?.let {
firebaseAuthWithGoogle(it) //tokenId를 이용해 firebase에 인증하는 함수 호출.
}
}
//tokenId를 이용해 firebase에 인증하는 함수.
fun firebaseAuthWithGoogle(idToken: String) {
//it가 tokenId, credential은 Firebase 사용자 인증 정보.
val credential = GoogleAuthProvider.getCredential(idToken, null)
//Firebase 사용자 인증 정보(credential)를 사용해 Firebase에 인증.
auth!!.signInWithCredential(credential)
.addOnCompleteListener(this@LoginActivity) { task: Task<AuthResult> ->
if (task.isSuccessful) {
// Sign in success, update UI with the signed-in user's information
Toast.makeText(
this@LoginActivity,
getString(R.string.signin_complete),
Toast.LENGTH_SHORT
).show()
startActivity(Intent(this@LoginActivity, MainActivity::class.java))
} else {
println("firebaseAuthWithGoogle => ${task.exception}")
Toast.makeText(
this@LoginActivity, getString(R.string.signin_google_faile),
Toast.LENGTH_SHORT
).show()
}
}
}
}
구글 로그인 화면을 호출하는 intent가 실행되고, 성공적으로 로그인까지 진행되면 로그인 액티비티는 IdToken을 전달 받을 수 있다. 이 IdToken를 이용해 firebase에 인증을 하고, 인증이 성공하면 메인 액티비티로 이동해야 한다.
이 과정을 콜백 함수인 registerForActivityResult()에 작성한다. firebaseAuthWithGoogle() 함수는 IdToken으로 firebase에 인증을 진행하고 메인 액티비티로 이동하는 과정을 수행하는 함수이고 이 함수의 매개변수인 it가 바로 parseIntent()를 통해 전달 받은 IdToken이다.
Contract를 정의하고 등록까지 마쳤으니 이제 실행을 해야 한다. 실행은 ActivityResultLauncher 인스턴스의 launch() 메소드를 호출하면 된다. 우리는 Contract를 정의하며 registerForActivityResult()를 호출했고, 이 때 ActivityResultLauncher를 리턴받았으니 이를 사용해 contract를 실행한다.
contract가 실행되면 Contract 클래스의 createIntent() 메서드에 정의한 대로 인텐트가 호출되고, 호출 후 parseIntent()를 통해 다시 데이터를 전달 받아 registerForActivityResult()를 통해 콜백 함수가 실행된다.
class LoginActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
...
binding.googleLoginButton.setOnClickListener {
//Launcher를 실행해 LoginActivity -> 구글 로그인 화면으로 이동.
launcher!!.launch(getString(R.string.default_web_client_id))
}
}
}
xml에 googleLoginButton이란 id로 구글 아이디로 로그인 버튼을 정의했고, 버튼을 클릭했을 때 앞서 Contract를 등록하고 리턴 받은 ActivityResultLauncher인 launcher 인스턴스의 launch() 메서드를 호출해 구글 로그인 화면으로 이동하는 intent를 호출한다. 이 때 Contract에 정의한 대로 인텐트를 생성할 때 필요한 String 형태의 clientId를 매개변수로 전달한다.
[출처] Charlezz - 액티비티 결과 처리하기 (Good bye… startActivityForResult, onActivityResult)