registerForActivityResult() 사용하기

KEH·2021년 7월 16일
0
post-thumbnail
post-custom-banner

안드로이드에서 액티비티간 데이터를 주고받을 때 startActivityForResultonActivityResult를 사용했다. 1학기 모바일 프로젝트 할 때까지만 해도 잘 써왔던 저 놈들이 이번에 인프런 강의를 듣다보니 deprecated가 돼 있었다... 나는 어쩔 수 없이 이 둘을 대체할 다른 방법을 찾기 시작했다.

대체할 방법을 찾던 중 registerForActivityResult() 라는 메서드를 발견했고, 이를 공부한 후 계속 사용하게 될 부분이라 정리하고자 한다.

나는 로그인 액티비티에서 구글 아이디로 로그인 버튼을 눌렀을 때 구글 로그인 화면으로 이동하며 로그인 시 필요한 clientId를 구글 로그인 화면에 전달하고, 로그인 후 메인 액티비티로 이동하는 과정에서 사용하였다.

registerForActivityResult()는 총 세 가지 단계로 이루어진다.
Contract 정의 -> 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를 등록해보자. 이 때 등록한다는 것은 호출 후 전달 받은 데이터를 활용해 다음 동작을 처리하는 것을 말한다. 즉, 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 실행

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)

profile
개발을 즐기고 잘하고 싶은 안드로이드 개발자입니다 :P
post-custom-banner

0개의 댓글