๐—”๐—ก๐——๐—ฅ๐—ข๐—œ๐——/๐Ÿ‘พ Jetpack Compose ์‹ค์ „ํŽธ

Jetpack Compose ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ๊ตฌํ˜„

z_ero 2025. 2. 23. 17:01

Jetpack Compose ํ™˜๊ฒฝ์—์„œ์˜ ๋ฌธ์ œ์ 

Jetpack Compose๋กœ ๊ตฌ๊ธ€ ์†Œ์…œ ๋กœ๊ทธ์ธ์„ ๊ตฌํ˜„ํ•˜๋ ค๊ณ  ํ–ˆ์ง€๋งŒ, ๊ด€๋ จ ์ •๋ณด๊ฐ€ ์ถฉ๋ถ„ํžˆ ์ œ๊ณต๋˜์ง€ ์•Š์•˜๋‹ค. (๋Œ€๋ถ€๋ถ„์˜ ์ž๋ฃŒ๋Š” Firebase ์ธ์ฆ ๊ธฐ๋ฐ˜์˜ ๋กœ๊ทธ์ธ ๊ตฌํ˜„ ๋ฐฉ์‹์— ์ง‘์ค‘๋˜์–ด ์žˆ์—ˆ๋‹ค.)

๊ตฌ๊ธ€ ์†Œ์…œ ๋กœ๊ทธ์ธ์€ ๋กœ๊ทธ์ธ์˜ ๊ธฐ๋ณธ์ด๋‚˜ ๋‹ค๋ฆ„์—†๋Š”๋ฐ.. ์™œ ๊ทธ๋Ÿฐ๊ฑธ๊นŒ? ์ด์œ ๋ฅผ ์•Œ์•„๋ณด๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ ์ž๋ฃŒ๋ฅผ ์ฐพ์•„๋ณธ ๊ฒฐ๊ณผ, Google OAuth๋ฅผ Firebase ์—†์ด ๊ตฌํ˜„ํ•˜๋ ค๋ฉด ๊ธฐ์กด์˜ XML ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค๊ณ„๋œ Intent ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.

์ด ๋ฐฉ์‹์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์ง•๊ณผ ํ•œ๊ณ„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค:

  1. XML ๊ธฐ๋ฐ˜ ๊ตฌ์กฐ
    Google OAuth๋Š” Jetpack Compose๋ฅผ ๊ณ ๋ คํ•˜์ง€ ์•Š์€ XML ์ค‘์‹ฌ์˜ ์„ค๊ณ„ ๋ฐฉ์‹์„ ๋”ฐ๋ฅธ๋‹ค. ๋”ฐ๋ผ์„œ Compose ํ™˜๊ฒฝ์—์„œ ๊ทธ๋Œ€๋กœ ํ™œ์šฉํ•˜๊ธฐ ์–ด๋ ต๋‹ค.
  2. ๋กœ๊ทธ์ธ ํ๋ฆ„์˜ ๋น„ํšจ์œจ์„ฑ
    Intent๋ฅผ ์ด์šฉํ•ด Activity๋ฅผ ์ด๋™ํ•˜๋Š” ์ „ํ†ต์ ์ธ ๋ฐฉ์‹์€ ์ตœ์‹  Compose์˜ State-driven UI ๊ตฌ์กฐ์™€ ์–ด์šธ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง€๊ณ  ๊ด€๋ฆฌ๊ฐ€ ์–ด๋ ค์›Œ์ง„๋‹ค.
  3. Compose์™€์˜ ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ
    Jetpack Compose๋Š” ์„ ์–ธํ˜• UI๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜์ง€๋งŒ, Google OAuth๋Š” ๋ช…๋ นํ˜• UI์— ์ ํ•ฉํ•˜๊ฒŒ ์„ค๊ณ„๋˜์—ˆ๋‹ค. ์ด๋กœ ์ธํ•ด Compose ํ™˜๊ฒฝ์—์„œ ์ด์งˆ๊ฐ์ด ๋ฐœ์ƒํ•˜๋ฉฐ, ์ž์—ฐ์Šค๋Ÿฝ์ง€ ์•Š์€ ์‚ฌ์šฉ์ž ํ๋ฆ„์ด ๋งŒ๋“ค์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค.

๊ฒฐ๋ก ์ ์œผ๋กœ, Firebase๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” Google OAuth ๊ตฌํ˜„์€ Jetpack Compose์™€์˜ ์™„๋ฒฝํ•œ ํ˜ธํ™˜์ด ์–ด๋ ต๋‹ค๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค. ์ตœ์‹  UI ๊ธฐ์ˆ ๊ณผ ์—ฐ๋™ํ•˜๋ ค๋ฉด ์ƒ๋‹นํ•œ ์ˆ˜์ค€์˜ ์ถ”๊ฐ€ ์ž‘์—…๊ณผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์ด ํ•„์š”ํ•˜๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์ฝ”๋”ฉ๊ณ„์˜ ๋น›... Jetpack Compose ๊ณ ์ˆ˜๋‹˜์ด ์˜ฌ๋ ค์ฃผ์‹  ์œ ํŠœ๋ธŒ๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. (๋‚ด๊ฐ€ ์ฐพ์•„๋ณธ ๋ธ”๋กœ๊ทธ์—์„œ๋„ ์ด๋ถ„ ์œ ํŠœ๋ธŒ๋ฅผ ์ฐธ๊ณ ํ–ˆ์—ˆ๋‹ค! ์—ญ์‹œ...)


๊ตฌ๊ธ€ ์†Œ์…œ ๋กœ๊ทธ์ธ ๊ตฌํ˜„ ๊ฐ€์ด๋“œ (Firebase ๋ฏธ์‚ฌ์šฉ)

1. ๊ตฌ๊ธ€ ํด๋ผ์šฐ๋“œ ์„ธํŒ…

1) Google Cloud Console์— ์ ‘์†ํ•œ๋‹ค.

 

2) API ๋ฐ ์„œ๋น„์Šค > ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด ๋ฉ”๋‰ด๋กœ ์ด๋™ํ•ด OAuth ํด๋ผ์ด์–ธํŠธ ID๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์œ ํ˜•์—์„œ Android๋ฅผ ์„ ํƒํ•œ๋‹ค.
  • ํ”„๋กœ์ ํŠธ ํŒจํ‚ค์ง€๋ช…: Android Studio ์ƒ๋‹จ์— ํ‘œ์‹œ๋œ ํŒจํ‚ค์ง€๋ช…์„ ์ž…๋ ฅํ•œ๋‹ค.
  • SHA-1 ๋””์ง€ํ„ธ ์ง€๋ฌธ: Android Studio ํ„ฐ๋ฏธ๋„์—์„œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ด ์ถ”์ถœํ•  ์ˆ˜ ์žˆ๋‹ค.
    keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
  • ๋””์ง€ํ„ธ ์ง€๋ฌธ ์ถ”์ถœ ๋ฐฉ๋ฒ•์€ Google Cloud Console์—์„œ๋„ ์นœ์ ˆํ•˜๊ฒŒ ๋ช…๋ น์–ด๋ฅผ ์ œ๊ณตํ•ด์ฃผ๊ณ  ์žˆ๋‹ค.

3) ์„ค์ •์ด ์™„๋ฃŒ๋˜๋ฉด, OAuth ํด๋ผ์ด์–ธํŠธ ID๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.


2. OAuth ๋™์˜ ํ™”๋ฉด ์„ค์ •

1) OAuth ๋™์˜ ํ™”๋ฉด ๋ฉ”๋‰ด๋กœ ์ด๋™ํ•ด ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์ฑ„์šด๋‹ค.

  • ์•ฑ ์ด๋ฆ„, ์ง€์› ์ด๋ฉ”์ผ ๋“ฑ ๊ธฐ๋ณธ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•œ๋‹ค.
  • ์•ฑ์ด ์•„์ง ๋ฐฐํฌ๋˜์ง€ ์•Š์€ ๋””๋ฒ„๊ทธ ์ƒํƒœ๋ผ๋ฉด, ํ…Œ์ŠคํŠธ ์‚ฌ์šฉ์ž๋กœ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ์ด๋ฉ”์ผ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.

2) ํ…Œ์ŠคํŠธ ์‚ฌ์šฉ์ž๋Š” ์ตœ๋Œ€ 100๋ช…๊นŒ์ง€ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.


3. Jetpack Compose์—์„œ Google Sign-In ๊ตฌํ˜„

Jetpack Compose ํ™˜๊ฒฝ์—์„œ Google Sign-In์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ActivityResultContract๋ฅผ ํ™œ์šฉํ•˜์—ฌ Intent๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ตฌ์กฐ๋ฅผ ์„ค๊ณ„ํ•œ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด Compose์™€ Google OAuth๋ฅผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

1) ActivityResultContract ๊ตฌํ˜„

class GoogleApiContract : ActivityResultContract<Int, Task<GoogleSignInAccount>?>() {
    override fun createIntent(context: Context, input: Int): Intent {
        val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(BuildConfig.GOOGLE_OAUTH_CLIENT_ID)
            .requestId()
            .build()

        val intent = GoogleSignIn.getClient(context, gso)
        return intent.signInIntent
    }

    override fun parseResult(resultCode: Int, intent: Intent?): Task<GoogleSignInAccount>? {
        return when (resultCode) {
            Activity.RESULT_OK -> GoogleSignIn.getSignedInAccountFromIntent(intent)
            else -> null
        }
    }
}

 

2) LoginScreen์—์„œ ์‚ฌ์šฉ

@Composable
fun LoginScreen(viewModel: LoginViewModel) {
    val authResultLauncher = rememberLauncherForActivityResult(
        contract = GoogleApiContract()
    ) { task ->
        viewModel.handleGoogleSignInResult(task)
    }

    Button(onClick = { authResultLauncher.launch(1) }) {
        Text("Google๋กœ ๋กœ๊ทธ์ธ")
    }

    when (viewModel.loginApiState) {
        is ApiState.Loading -> Text("๋กœ๊ทธ์ธ ์ค‘...")
        is ApiState.Success -> Text("๋กœ๊ทธ์ธ ์„ฑ๊ณต!")
        is ApiState.Error -> Text("๋กœ๊ทธ์ธ ์‹คํŒจ: ${(viewModel.loginApiState as ApiState.Error).message}")
        else -> {}
    }
}

 

3) ViewModel์—์„œ Google Sign-In ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ

class LoginViewModel : ViewModel() {
    private val _loginApiState = MutableStateFlow<ApiState>(ApiState.Idle)
    val loginApiState: StateFlow<ApiState> = _loginApiState

    fun handleGoogleSignInResult(task: Task<GoogleSignInAccount>?) {
        _loginApiState.value = ApiState.Loading

        if (task == null) {
            _loginApiState.value = ApiState.Error("Google ๋กœ๊ทธ์ธ ์‹คํŒจ")
            return
        }

        try {
            val account = task.getResult(ApiException::class.java)
            account?.let {
                Log.d("LoginViewModel", "Google sign in success: ${account.id}")
                _loginApiState.value = ApiState.Success("Google ๋กœ๊ทธ์ธ ์„ฑ๊ณต")
            } ?: run {
                _loginApiState.value = ApiState.Error("Google ๋กœ๊ทธ์ธ ์‹คํŒจ")
            }
        } catch (e: ApiException) {
            Log.e("LoginViewModel", "Google sign in failed: ${e.statusCode}")
            _loginApiState.value = ApiState.Error("Google ๋กœ๊ทธ์ธ ์‹คํŒจ")
        }
    }
}

 

4) ApiState ์ •์˜

sealed class ApiState {
    object Idle : ApiState()
    object Loading : ApiState()
    data class Success(val message: String) : ApiState()
    data class Error(val message: String) : ApiState()
}

 


์ฐธ๊ณ  ์ž๋ฃŒ

https://velog.io/@jjwm10625/Jetpack-Compose-%EA%B5%AC%EA%B8%80-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84

 

๋ฐ˜์‘ํ˜•