Jetpack Compose ํ๊ฒฝ์์์ ๋ฌธ์ ์
Jetpack Compose๋ก ๊ตฌ๊ธ ์์ ๋ก๊ทธ์ธ์ ๊ตฌํํ๋ ค๊ณ ํ์ง๋ง, ๊ด๋ จ ์ ๋ณด๊ฐ ์ถฉ๋ถํ ์ ๊ณต๋์ง ์์๋ค. (๋๋ถ๋ถ์ ์๋ฃ๋ Firebase ์ธ์ฆ ๊ธฐ๋ฐ์ ๋ก๊ทธ์ธ ๊ตฌํ ๋ฐฉ์์ ์ง์ค๋์ด ์์๋ค.)
๊ตฌ๊ธ ์์
๋ก๊ทธ์ธ์ ๋ก๊ทธ์ธ์ ๊ธฐ๋ณธ์ด๋ ๋ค๋ฆ์๋๋ฐ.. ์ ๊ทธ๋ฐ๊ฑธ๊น? ์ด์ ๋ฅผ ์์๋ณด๊ธฐ ์ํด ์ฌ๋ฌ ์๋ฃ๋ฅผ ์ฐพ์๋ณธ ๊ฒฐ๊ณผ, Google OAuth๋ฅผ Firebase ์์ด ๊ตฌํํ๋ ค๋ฉด ๊ธฐ์กด์ XML ๊ธฐ๋ฐ์ผ๋ก ์ค๊ณ๋ Intent ๋ฐฉ์์ ์ฌ์ฉํด์ผ ํ๋ค๋ ์ ์ ์๊ฒ ๋์๋ค.
์ด ๋ฐฉ์์ ๋ค์๊ณผ ๊ฐ์ ํน์ง๊ณผ ํ๊ณ๋ฅผ ๊ฐ์ง๊ณ ์๋ค:
- XML ๊ธฐ๋ฐ ๊ตฌ์กฐ
Google OAuth๋ Jetpack Compose๋ฅผ ๊ณ ๋ คํ์ง ์์ XML ์ค์ฌ์ ์ค๊ณ ๋ฐฉ์์ ๋ฐ๋ฅธ๋ค. ๋ฐ๋ผ์ Compose ํ๊ฒฝ์์ ๊ทธ๋๋ก ํ์ฉํ๊ธฐ ์ด๋ ต๋ค. - ๋ก๊ทธ์ธ ํ๋ฆ์ ๋นํจ์จ์ฑ
Intent๋ฅผ ์ด์ฉํด Activity๋ฅผ ์ด๋ํ๋ ์ ํต์ ์ธ ๋ฐฉ์์ ์ต์ Compose์ State-driven UI ๊ตฌ์กฐ์ ์ด์ธ๋ฆฌ์ง ์๋๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก ์ฝ๋๊ฐ ๋ณต์กํด์ง๊ณ ๊ด๋ฆฌ๊ฐ ์ด๋ ค์์ง๋ค. - 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()
}
์ฐธ๊ณ ์๋ฃ
- Velog ๋ธ๋ก๊ทธ: Jetpack Compose์์ ๊ตฌ๊ธ/์นด์นด์ค ๋ก๊ทธ์ธ์ ๊ตฌํํด๋ณด์
- YouTube: ๊ตฌ๊ธ ๋ก๊ทธ์ธ ๊ตฌํ ๊ฐ์ด๋