FirebaseUI로 Android 로그인

이 Codelab은 Kotlin 기반 Android 고급 교육 과정의 일부입니다. Codelab을 순서대로 진행하는 경우 학습 효과를 극대화할 수 있지만 순서를 바꿔 진행해도 괜찮습니다. 모든 교육 과정 Codelab은 Kotlin 기반 고급 Android Codelab 방문 페이지에 나열되어 있습니다.

소개

Android 앱을 빌드할 때 사용자의 로그인을 지원하면 다양한 이점이 있습니다. 사용자가 앱 내에서 ID를 만들 수 있도록 허용하면 사용자가 앱과 상호작용할 수 있는 방법을 더 많이 제공할 수 있습니다.

개인화된 계정을 사용하면 사용자가 인앱 환경을 맞춤설정하고, 다른 사용자와 소통하고, 다른 기기 (예: 웹 또는 새 휴대전화)에서 앱을 사용하는 경우 데이터를 유지하고 전송할 수 있습니다.

이 Codelab에서는 FirebaseUI 라이브러리를 사용하여 앱의 로그인을 지원하는 방법을 기본적으로 알아봅니다. FirebaseUI 라이브러리는 로그인 흐름을 빌드하려는 개발자에게 간단한 방법을 제공하며 사용자 계정 관리 작업을 처리합니다.

기본 요건

  • Android 앱 빌드 방법의 기본사항
  • LiveData 및 ViewModel

학습할 내용

  • 프로젝트에 Firebase를 추가하는 방법
  • Android 앱의 로그인 지원 방법
  • 앱의 현재 인증 상태를 확인하는 방법
  • 사용자를 로그아웃하는 방법

실습할 내용

  • Firebase Console을 사용하여 앱에 Firebase를 통합합니다.
  • 로그인 기능을 구현합니다.
  • 로그인한 사용자를 위해 앱에 맞춤설정을 추가합니다.
  • 사용자 로그아웃을 구현합니다.

LiveData 및 ViewModel 자세히 알아보기

이 Codelab의 앱에서는 LiveData와 ViewModel에 관한 기본적인 이해가 필요합니다. 이러한 개념에 관한 간략한 개요를 보려면 LiveDataViewModel 개요를 읽어보세요.

Kotlin으로 Android 앱 개발 과정을 통해 이 Codelab의 일부로 접하게 될 기본적인 Android 주제를 알아볼 수도 있습니다. 이 과정은 Udacity 과정Codelabs 과정으로 제공됩니다.

이 Codelab에서는 재미있는 Android 사실을 표시하는 앱을 빌드합니다. 무엇보다 앱에 로그인/로그아웃 버튼이 표시됩니다. 사용자가 앱에 로그인하면 표시되는 Android 사실에 개인화된 인사말이 포함됩니다.

샘플 앱을 다운로드하려면 다음 중 하나를 실행하세요.

ZIP 파일 다운로드

또는 다음 명령어를 사용하여 명령줄에서 GitHub 저장소를 클론하고 저장소의 start 브랜치로 전환합니다.

$  git clone https://github.com/googlecodelabs/android-kotlin-login

중요: Firebase를 사용하도록 앱을 통합하므로 시작 앱을 빌드하고 실행하려면 몇 가지 설정을 해야 합니다. Codelab의 다음 단계에서 이를 작업해 보겠습니다.

1단계: Firebase 프로젝트 만들기

Android 앱에 Firebase를 추가하려면 먼저 Android 앱에 연결할 Firebase 프로젝트를 만들어야 합니다.

  1. Firebase Console에서 프로젝트 추가를 클릭합니다.
  2. 프로젝트 이름을 선택하거나 입력합니다. 프로젝트 이름을 원하는 대로 지정할 수 있지만 빌드 중인 앱과 관련된 이름을 선택하는 것이 좋습니다.
  3. 계속을 클릭합니다.
  4. Google 애널리틱스 설정을 건너뛰고 나중에 옵션을 선택할 수 있습니다.
  5. 프로젝트 만들기를 클릭하여 Firebase 프로젝트 설정을 완료합니다.

2단계: Firebase에 앱 등록

이제 Firebase 프로젝트가 준비되었으므로 Android 앱을 추가할 수 있습니다.

  1. Firebase Console의 프로젝트 개요 페이지 중앙에 있는 Android 아이콘을 클릭하여 설정 워크플로를 시작합니다.
  2. Android 패키지 이름 필드에 앱의 애플리케이션 ID를 입력합니다. Firebase 프로젝트에 앱을 등록한 후에는 이 값을 추가하거나 수정할 수 없으므로 앱에서 사용하는 ID를 입력해야 합니다.
  1. 애플리케이션 ID패키지 이름이라고도 합니다.
  2. 모듈(앱 수준) Gradle 파일(일반적으로 app/build.gradle)에서 이 애플리케이션 ID(예시 ID: com.yourcompany.yourproject)를 찾습니다.
  3. 디버그 서명 인증서 SHA-1을 입력합니다. 명령줄 터미널에 다음 명령어를 입력하여 이 키를 생성할 수 있습니다.
keytool -alias androiddebugkey -keystore ~/.android/debug.keystore -list -v -storepass android
  1. 앱 등록을 클릭합니다.

3단계: 프로젝트에 Firebase 구성 파일 추가

앱에 Firebase Android 구성 파일을 추가합니다.

  1. google-services.json 다운로드를 클릭하여 Firebase Android 구성 파일 (google-services.json)을 가져옵니다.
  • 언제든지 다시 Firebase Android 구성 파일을 다운로드할 수 있습니다.
  • 구성 파일에 문자가 추가되지 않았으며 이름이 google-services.json로 지정되어야 합니다.
  1. 구성 파일을 앱의 모듈 (앱 수준) 디렉터리로 이동합니다.

4단계: Firebase 제품을 사용 설정하도록 Android 프로젝트 구성

  1. 앱에서 Firebase 제품을 사용할 수 있도록 google-services 플러그인을 Gradle 파일에 추가합니다.
  1. 루트 수준 (프로젝트 수준) Gradle 파일 (build.gradle)에서 Google 서비스 플러그인을 포함하는 규칙을 추가합니다. Google의 Maven 저장소도 있는지 확인합니다.

build.gradle

buildscript {

  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository
  }

  dependencies {
    // ...

    // Add the following line:
    classpath 'com.google.gms:google-services:4.3.0'  // Google Services plugin
  }
}

allprojects {
  // ...

  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository
    // ...
  }
}
  1. 모듈 (앱 수준) Gradle 파일 (일반적으로 app/build.gradle)에서 다음 줄을 파일 하단에 추가합니다.

app/build.gradle

apply plugin: 'com.android.application'

android {
  // ...
}

// Add the following line to the bottom of the file:
apply plugin: 'com.google.gms.google-services'  // Google Play services Gradle plugin

4단계: Firebase 종속 항목 추가

이 Codelab에서 Firebase를 통합하는 주된 이유는 사용자를 만들고 관리하는 방법을 제공하기 위해서입니다. 이를 위해서는 로그인을 구현할 수 있는 Firebase 라이브러리를 추가해야 합니다.

  1. 앱에서 SDK를 사용할 수 있도록 build.gradle (Module:app) 파일에 다음 종속 항목을 추가합니다. firebase-auth SDK를 사용하면 애플리케이션의 인증된 사용자를 관리할 수 있습니다.

app/build.gradle:

implementation 'com.firebaseui:firebase-ui-auth:5.0.0'
  1. 프로젝트를 Gradle 파일과 동기화하여 앱에서 모든 종속 항목을 사용할 수 있는지 확인합니다. 메시지가 표시되지 않으면 Android 스튜디오 또는 툴바에서 File > Sync Project with Gradle Files를 선택합니다.

5단계: 앱 실행 및 코드 검사

  1. 에뮬레이터 또는 실제 기기에서 앱을 실행하여 개발을 시작할 수 있도록 환경이 올바르게 설정되었는지 확인합니다.

성공하면 홈 화면에 재미있는 Android 사실과 왼쪽 상단에 로그인 버튼이 표시됩니다. 로그인 버튼을 탭해도 아직 아무 작업도 발생하지 않습니다.

높은 수준에서 보면 여러 프래그먼트가 있는 단일 활동 앱입니다. MainFragment에는 아래 화면에 표시되는 모든 UI가 포함되어 있습니다. (후속 Codelab에서 LoginFragmentSettingsFragment를 사용합니다.)

  1. 코드를 숙지합니다. 특히 다음 사항에 유의하세요.
  • FirebaseUserLiveData는 앱과 연결된 현재 Firebase 사용자를 관찰하기 위해 구현할 클래스입니다. 나중에 FirebaseAuth 인스턴스를 진입점으로 사용하여 이 사용자 정보를 가져옵니다.
  • MainFragmentLoginViewModel에 연결됩니다. LoginViewModelFirebaseUserLiveData를 사용하여 authenticationState 변수를 만들기 위해 구현할 클래스입니다. 이 authenticationState 변수를 사용하면 MainFragment가 값을 관찰하여 그에 따라 UI를 업데이트할 수 있습니다.

이 단계에서는 Firebase Console을 사용하여 앱에서 지원할 인증 방법을 설정합니다. 이 Codelab에서는 사용자가 제공한 이메일 주소 또는 Google 계정으로 로그인할 수 있도록 하는 데 중점을 둡니다.

  1. Firebase Console로 이동합니다. (참고: 아직 Firebase 추가 워크플로에 있는 경우 왼쪽 상단에 있는 X를 클릭하여 Console로 돌아갑니다.
  2. 아직 프로젝트에 있지 않은 경우 프로젝트를 선택합니다.
  3. 왼쪽 탐색 메뉴를 열고 개발 > 인증 을 선택합니다.

  1. 상단 탐색 메뉴에서 로그인 방법 탭을 선택합니다.

  1. 이메일/비밀번호 행을 클릭합니다.
  2. 팝업에서 사용 설정됨 스위치를 전환하고 저장을 클릭합니다.
  3. 마찬가지로 Google 행을 클릭합니다.
  4. 사용 설정됨 스위치를 전환하고 프로젝트 지원 이메일을 입력한 다음 저장을 클릭합니다.

이 작업에서는 사용자를 위한 로그인 기능을 구현합니다.

  1. MainFragment.kt를 엽니다.
  2. MainFragment 레이아웃에서 auth_button를 확인합니다. 현재 사용자 입력을 처리하도록 설정되어 있지 않습니다.
  3. onViewCreated(),에서 launchSignInFlow()을 호출하기 위해 auth_buttononClickListener를 추가합니다.

MainFragment.kt

binding.authButton.setOnClickListener { launchSignInFlow() }
  1. MainFragment.kt에서 launchSignInFlow() 메서드를 찾습니다. 현재 TODO이(가) 포함되어 있습니다.
  2. 아래와 같이 launchSignInFlow() 함수를 완성합니다.

MainFragment.kt

private fun launchSignInFlow() {
   // Give users the option to sign in / register with their email or Google account.
   // If users choose to register with their email,
   // they will need to create a password as well.
   val providers = arrayListOf(
       AuthUI.IdpConfig.EmailBuilder().build(), AuthUI.IdpConfig.GoogleBuilder().build()

       // This is where you can provide more ways for users to register and 
       // sign in.
   )

   // Create and launch sign-in intent.
   // We listen to the response of this activity with the
   // SIGN_IN_REQUEST_CODE 
   startActivityForResult(
       AuthUI.getInstance()
           .createSignInIntentBuilder()
           .setAvailableProviders(providers)
           .build(),
       MainFragment.SIGN_IN_REQUEST_CODE
   )
}

이를 통해 사용자는 이메일 주소 또는 Google 계정으로 등록하고 로그인할 수 있습니다. 사용자가 이메일 주소로 등록하는 경우 사용자가 만든 이메일 및 비밀번호 조합은 앱에 고유합니다. 즉, 이메일 및 비밀번호 조합으로 앱에 로그인할 수 있지만 동일한 사용자 인증 정보로 다른 Firebase 지원 앱에 로그인할 수는 없습니다.

  1. MainFragment.kt에서는 아래와 같이 onActivityResult() 메서드를 구현하여 로그인 프로세스의 결과를 수신 대기할 수 있습니다. SIGN_IN_REQUEST_CODE로 로그인 절차를 시작했으므로 SIGN_IN_REQUEST_CODEonActivityResult()에 다시 전달되는 시점을 필터링하여 로그인 절차의 결과를 수신 대기할 수도 있습니다. 사용자가 로그인에 성공했는지 알 수 있도록 몇 가지 로그 문을 작성합니다.

MainFragment.kt

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
   super.onActivityResult(requestCode, resultCode, data)
   if (requestCode == SIGN_IN_REQUEST_CODE) {
       val response = IdpResponse.fromResultIntent(data)
       if (resultCode == Activity.RESULT_OK) {
           // User successfully signed in
           Log.i(TAG, "Successfully signed in user ${FirebaseAuth.getInstance().currentUser?.displayName}!")
       } else {
           // Sign in failed. If response is null the user canceled the
           // sign-in flow using the back button. Otherwise check
           // response.getError().getErrorCode() and handle the error.
           Log.i(TAG, "Sign in unsuccessful ${response?.error?.errorCode}")
       }
   }
}

이제 앱에서 사용자를 등록하고 로그인할 수 있습니다.

  1. 앱을 실행하고 로그인 버튼을 탭하면 로그인 화면이 표시되는지 확인합니다.
  2. 이제 이메일 주소와 비밀번호 또는 Google 계정으로 로그인할 수 있습니다.
  3. 로그인 후 UI는 변경되지 않습니다 (다음 단계에서 UI 업데이트를 구현함). 하지만 모든 것이 올바르게 작동한다면 등록 흐름을 거친 후 Successfully signed in user ${your name}! 로그 메시지가 표시됩니다.
  4. Firebase Console로 이동하여 개발 > 인증 > 사용자로 이동하여 앱에 등록된 사용자가 1명인지 확인할 수도 있습니다.
  5. 사용자가 앱의 계정을 만들 때 이 계정은 로그인 기능에 Firebase를 사용하는 앱이 아닌 앱에만 연결됩니다.

이 작업에서는 인증 상태에 따라 UI를 업데이트하는 작업을 구현합니다. 사용자가 로그인한 경우 이름을 표시하여 홈 화면을 맞춤설정할 수 있습니다. 또한 사용자가 로그인하면 로그인 버튼이 로그아웃 버튼으로 업데이트됩니다.

  1. 이미 생성된 FirebaseUserLiveData.kt 클래스를 엽니다. 가장 먼저 해야 할 일은 앱의 다른 클래스가 사용자가 로그인했는지 로그아웃했는지 알 수 있는 방법을 제공하는 것입니다. 하지만 LiveData 값이 업데이트되지 않으므로 클래스는 아직 아무것도 실행하지 않습니다.
  2. FirebaseAuth 라이브러리를 사용하고 있으므로 FirebaseUI 라이브러리의 일부로 구현된 FirebaseUser.AuthStateListener 콜백을 사용하여 로그인한 사용자의 변경사항을 수신 대기할 수 있습니다. 이 콜백은 사용자가 앱에 로그인하거나 로그아웃할 때마다 트리거됩니다.
  3. FirebaseUserLiveData.ktauthStateListener 변수를 정의합니다. 이 변수를 사용하여 LiveData의 값을 저장합니다. authStateListener 변수는 애플리케이션의 상태에 따라 인증 상태의 변경사항을 올바르게 수신 대기하고 중지할 수 있도록 생성되었습니다. 예를 들어 사용자가 앱을 백그라운드로 전환하면 잠재적인 메모리 누수를 방지하기 위해 앱이 인증 상태 변경을 수신 대기하는 것을 중지해야 합니다.
  4. FirebaseUserLiveData 값이 현재 Firebase 사용자에 대응하도록 authStateListener를 업데이트합니다.

FirebaseUserLiveData.kt

private val authStateListener = FirebaseAuth.AuthStateListener { firebaseAuth ->
   value = firebaseAuth.currentUser
}
  1. LoginViewModel.kt를 엽니다.
  2. LoginViewModel.kt에서 방금 구현한 FirebaseUserLiveData 객체를 기반으로 authenticationState 변수를 만듭니다. 이 authenticationState 변수를 만들어 이제 다른 클래스가 LoginViewModel을 통해 사용자가 로그인했는지 여부를 쿼리할 수 있습니다.

LoginViewModel.kt

val authenticationState = FirebaseUserLiveData().map { user ->
   if (user != null) {
       AuthenticationState.AUTHENTICATED
   } else {
       AuthenticationState.UNAUTHENTICATED
   }
}
  1. MainFragment.kt. 열기
  2. MainFragment.ktobserveAuthenticationState()에서 LoginViewModel에 추가한 authenticationState 변수를 사용하여 UI를 적절하게 변경할 수 있습니다. 로그인한 사용자가 있는 경우 authButton로그아웃이 표시되어야 합니다.

MainFragment.kt

private fun observeAuthenticationState() {
   val factToDisplay = viewModel.getFactToDisplay(requireContext())

   viewModel.authenticationState.observe(viewLifecycleOwner, Observer { authenticationState ->
       when (authenticationState) {
           LoginViewModel.AuthenticationState.AUTHENTICATED -> {
               binding.authButton.text = getString(R.string.logout_button_text)
               binding.authButton.setOnClickListener {
                   // TODO implement logging out user in next step
               }

                // TODO 2. If the user is logged in, 
                 // you can customize the welcome message they see by
                 // utilizing the getFactWithPersonalization() function provided

           }
           else -> {
               // TODO 3. Lastly, if there is no logged-in user, 
                // auth_button should display Login and
                //  launch the sign in screen when clicked.
           }
       }
   })
}
  1. 사용자가 로그인한 경우 MainFragment.
    에 제공된 getFactWithPersonalization() 함수를 활용하여 표시되는 환영 메시지를 맞춤설정할 수도 있습니다.

MainFragment.kt

binding.welcomeText.text = getFactWithPersonalization(factToDisplay)
  1. 마지막으로 로그인한 사용자가 없는 경우 (authenticationStateLoginViewModel.AuthenticationState.AUTHENTICATED이 아닌 경우) auth_button로그인을 표시해야 하며 클릭하면 로그인 화면이 실행되어야 합니다. 표시되는 메시지도 맞춤설정되지 않아야 합니다.

MainFragment.kt

binding.authButton.text = getString(R.string.login_button_text)
binding.authButton.setOnClickListener { launchSignInFlow() }
binding.welcomeText.text = factToDisplay

모든 단계를 완료하면 최종 observeAuthenticationState() 메서드가 아래 코드와 같이 표시됩니다.

MainFragment.kt

private fun observeAuthenticationState() {
   val factToDisplay = viewModel.getFactToDisplay(requireContext())

   viewModel.authenticationState.observe(viewLifecycleOwner, Observer { authenticationState ->
        // TODO 1. Use the authenticationState variable you just added 
         // in LoginViewModel and change the UI accordingly.
       when (authenticationState) {
            // TODO 2.  If the user is logged in, 
             // you can customize the welcome message they see by
             // utilizing the getFactWithPersonalization() function provided
           LoginViewModel.AuthenticationState.AUTHENTICATED -> {
               binding.welcomeText.text = getFactWithPersonalization(factToDisplay)
               binding.authButton.text = getString(R.string.logout_button_text)
               binding.authButton.setOnClickListener {
                   // TODO implement logging out user in next step
               }
           }
           else -> {
                // TODO 3. Lastly, if there is no logged-in user, 
                 // auth_button should display Login and
                 // launch the sign in screen when clicked.
               binding.welcomeText.text = factToDisplay

               binding.authButton.text = getString(R.string.login_button_text)
               binding.authButton.setOnClickListener {
                   launchSignInFlow()
               }
           }
       }
   })
}
  1. 앱을 실행합니다. 사용자가 로그인했는지 여부에 따라 UI가 업데이트됩니다. 모든 것이 제대로 작동하고 로그인되어 있다면 이제 홈 화면에 Android 사실이 표시되는 것 외에도 이름으로 인사말이 표시됩니다. 이제 로그인 버튼에도 로그아웃이 표시됩니다.

이 작업에서는 로그아웃 기능을 구현합니다.

앱에서 사용자가 로그인할 수 있으므로 로그아웃할 수 있는 방법도 제공해야 합니다. 다음은 한 줄의 코드로 사용자를 로그아웃하는 방법의 예입니다.

AuthUI.getInstance().signOut(requireContext())
  1. MainFragment.kt를 엽니다.
  2. MainFragment.ktobserveAuthenticationState()에서 로그인한 사용자가 있을 때 auth_button가 올바르게 작동하도록 로그아웃 로직을 추가합니다. 메서드의 최종 결과는 아래 코드와 같습니다.

MainFragment.kt

private fun observeAuthenticationState() {
   val factToDisplay = viewModel.getFactToDisplay(requireContext())

   viewModel.authenticationState.observe(viewLifecycleOwner, Observer { authenticationState ->
       when (authenticationState) {
           LoginViewModel.AuthenticationState.AUTHENTICATED -> {
               binding.welcomeText.text = getFactWithPersonalization(factToDisplay)

               binding.authButton.text = getString(R.string.logout_button_text)
               binding.authButton.setOnClickListener {
                   AuthUI.getInstance().signOut(requireContext())
               }
           }
           else -> {
               binding.welcomeText.text = factToDisplay

               binding.authButton.text = getString(R.string.login_button_text)
               binding.authButton.setOnClickListener {
                   launchSignInFlow()
               }
           }
       }
   })
}
  1. 앱을 실행합니다.
  2. 로그아웃 버튼을 탭하고 사용자가 로그아웃되었으며 버튼의 상태가 로그인으로 변경되었는지 확인합니다.

완성된 앱의 최종 버전은 https://github.com/googlecodelabs/android-kotlin-login에서 확인할 수 있습니다.

이 Codelab에서 배운 내용은 다음과 같습니다.

  • gradle 파일에 필요한 종속 항목을 추가하고 Firebase 콘솔에서 프로젝트를 설정하여 프로젝트에 Firebase를 추가하는 방법
  • FirebaseUI 라이브러리를 사용하여 앱의 로그인을 구현하고 사용자의 로그인 허용 방식을 지정하는 방법 사용자가 앱에서 만드는 계정은 앱에만 적용되며 로그인 기능을 위해 Firebase를 사용하는 모든 앱과 공유되지 않습니다.
  • LiveData를 사용하여 앱의 현재 인증 상태를 관찰하는 방법
  • 사용자를 로그아웃하는 방법

이 Codelab에서는 Android 앱의 로그인을 지원하는 방법을 기본적으로 다루었습니다.

이 Codelab에서는 사용자가 이메일 주소로 등록하고 로그인할 수 있도록 했습니다. 하지만 FirebaseUI 라이브러리를 사용하면 전화번호로 로그인하는 등의 다른 인증 방법도 지원할 수 있습니다. FirebaseUI 라이브러리의 기능과 제공되는 다른 기능을 활용하는 방법을 자세히 알아보려면 다음 리소스를 확인하세요.

로그인 관련 권장사항에 대한 자세한 내용은 다음 리소스를 참고하세요.

Codelab:

Android 개발자 문서:

동영상:

이 과정의 다른 Codelab 링크는 Kotlin 기반 Android 고급 Codelab 방문 페이지를 참고하세요.