안드로이드 + Firebase DB + 인증

이영준·2023년 4월 19일
0

📌 Firebase란

  • 웹과 모바일 개발에 필요한 기능을 제공하는 BaaS(Back end as a Service)
  • 백엔드 개발을 통해 서버를 따로 설계, 구현하지 않고 프론트엔드 개발에 집중할 수 있도록 도와주는 서비스
  • FCM(Firebase Cloud Messaging)을 통하여 푸쉬 기능을 구현한다.

📌 Firebase를 통해 실행할 수 있는 기능들

  • 실시간 DB
  • 인증
  • Cloud Firestore
  • Cloud Function
  • Cloud Storage
  • 호스팅
  • ML Kit(머신러닝)

🔑 실시간 데이터베이스

클라우드 호스팅 데이터베이스
JSON으로 저장되어 클라이언트에 실시간 동기화

  • 실시간 : 일반적인 HTTP 요청이 아닌 동기화를 사용하므로 데이터가 변경될 때마다 연결된 모든 기기가 수 밀리초 내에 업데이트를 수신한다.
  • 오프라인 : 데이터를 디스크에 저장하므로 앱이 오프라인일 때도 이후에 네트워크에 연결되면 클라이언트 기기가 놓쳤던 변경이 모두 수신되어 동기화된다.

🔑 인증

앱에서 사용자 인증 시 필요한 백엔드 서비스와 사용하기 쉬운 SDK, 기성 UI 라이브러리 제공
UI 인증 : 전체 로그인 시스템을 추가할 때 권장하는 방법 - 인기 ID 제공업체를 이용한 로그인의 UI 흐름을 처리하는 인증 솔루션 제공
SDK 인증 : 이메일 및 비밀번호 기반 인증, ID 공급업체 통합, 전화번호 인증, 커스텀 인증 시스템 통합

🔑 Cloud Firestore

서버개발에 사용되는 유연하고 확장 가능한 NoSQL DB
실시간 DB와 마찬가지로 실시간 리스너를 통해 클라이언트 애플리케이션 간에 데이터의 동기화를 유지하고 모바일 및 웹에 대한 오프라인 지원을 제공한다.

🔑 FCM

무료로 메시지를 안정적으로 전송할 수 있는 교차 플랫폼 메시징 솔루션

  • 새 이메일이나 기타 데이터를 동기화할 수 있음을 클라이언트 앱에 알릴 수 있음
  • 알림 메시지로 앱의 재참여 유도가 가능하다

📌 Firebase 연동

https://firebase.google.com/docs/android/setup?hl=ko
https://firebase.google.com/docs/database/android/read-and-write?hl=ko&authuser=0

연동은 해당레퍼런스를 참고한다.

📌 Firebase 실시간 데이터베이스 사용

class ChatActivity : AppCompatActivity() {

    private val messageList = mutableListOf<ChattingItem>()
    private val database = Firebase.database


    //firebase에 message라는 테이블이 없으면 생성, 있으면 리턴
    private lateinit var myRef: DatabaseReference
    private lateinit var itemAdapter : ChatItemRecyclerViewAdapter
    private val binding: ActivityMainBinding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        val name = intent.getStringExtra("name") ?: "no_name"
        itemAdapter = ChatItemRecyclerViewAdapter(messageList, name)

        initFirebase()

        binding.chatRecycler.apply {
            adapter = itemAdapter
        }

        binding.sendBtn.setOnClickListener {
            val msg = binding.messageEt.text.toString()
            binding.messageEt.setText("")
            //값이 있는 경우에만 보냄
            if (TextUtils.isEmpty(msg) == false)
//            messageList.add(chatItem)
                myRef.push().setValue(ChattingItem("", name, msg, System.currentTimeMillis()))
        }
    }

    private fun initFirebase() {
        myRef = database.getReference("message")
        val childEventListener = object : ChildEventListener {
            override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
                Log.d(TAG, "onChildAdded: ${snapshot}")
                messageList.add(getChatItem(snapshot))
                //diffUtil 로 바꾸는 것이 효율적이긴 할 듯
                itemAdapter.notifyItemInserted(messageList.size)
                binding.chatRecycler.smoothScrollToPosition(messageList.size)
            }

            override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
                Log.d(TAG, "onChildChanged: ")
                //바뀐 chatting item 가져옴
                val chattingItem = getChatItem(snapshot)

                var oldItem : ChattingItem = ChattingItem()
                messageList.forEach{
                    if(it.firebaseKey == chattingItem.firebaseKey){
                        // list에서 바꿀 기존의 chatting item
                        oldItem = it
                    }
                }
                val index = messageList.indexOf(oldItem)
                messageList[index] = chattingItem
                itemAdapter.notifyItemChanged(index)
                binding.chatRecycler.smoothScrollToPosition(messageList.size)
            }

            override fun onChildRemoved(snapshot: DataSnapshot) {
                Log.d(TAG, "onChildRemoved: ")
                val chatItem = getChatItem(snapshot)
                val index = messageList.indexOf(chatItem)
                messageList.removeAt(index)
                itemAdapter.notifyItemRemoved(index)
                binding.chatRecycler.smoothScrollToPosition(messageList.size)
            }

            override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
                Log.d(TAG, "onChildMoved: ")
            }

            override fun onCancelled(error: DatabaseError) {
                Log.d(TAG, "onCancelled: ")
            }

        }
        
        myRef.addChildEventListener(childEventListener)
    }

    private fun getChatItem(snapshot: DataSnapshot) : ChattingItem{
//        val chatItem = snapshot.value as ChattingItem
        val chatItem = snapshot.getValue(ChattingItem::class.java) ?: ChattingItem()
        chatItem.firebaseKey = snapshot.key ?: ""
        return chatItem
    }
}

📌 Firebase Auth

실시간 DB의 규칙을 수정한다.

Firebase의 authentication에 들어간다.


https://console.cloud.google.com/apis/credentials?hl=ko&project=myfirebasechat-4fae1
위 링크에서 사용자 인증 정보를 확인할 수 있다.

https://firebase.google.com/docs/auth/android/google-signin?hl=ko

해당 페이지에 링크로 넘어갈 수 있는 레퍼런스
https://github.com/firebase/snippets-android/blob/cb15737fe61389d2b58c65ae171cf83c26119cb3/auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/GoogleSignInActivity.kt#L44-L45

https://developers.google.com/identity/one-tap/android/get-saved-credentials?hl=ko

Auth 사용 예제



private const val TAG = "ChatAuthLoginAc_싸피"


class ChatAuthLoginActivity : AppCompatActivity() {
    private lateinit var binding: ActivityChatLoginBinding
    private lateinit var auth: FirebaseAuth
    private lateinit var googleSignInClient: GoogleSignInClient

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityChatLoginBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.enterBtn.isEnabled = false

        binding.enterBtn.setOnClickListener {
            val intent = Intent(this, ChatActivity::class.java).apply {
                putExtra("name", binding.nameEt.text.toString())
            }
            startActivity(intent)
            finish()
        }
        // 구글 로그인 절차 수행.
        initAuth()
    }


    // 인증 초기화
    private fun initAuth() {

        val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(getString(R.string.default_web_client_id))
            .requestEmail()
            .build()

        googleSignInClient = GoogleSignIn.getClient(this, gso)
        auth = Firebase.auth
        signIn()
    }


    override fun onStart() {
        super.onStart()
        // Check if user is signed in (non-null) and update UI accordingly.
        val currentUser = auth.currentUser
        updateUI(currentUser)
    }
    // [END on_start_check_user]

    // [START onactivityresult]
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...);
        if (requestCode == RC_SIGN_IN) {
            val task = GoogleSignIn.getSignedInAccountFromIntent(data)
            try {
                // Google Sign In was successful, authenticate with Firebase
                val account = task.getResult(ApiException::class.java)!!
                Log.d(TAG, "firebaseAuthWithGoogle:" + account.id)
                firebaseAuthWithGoogle(account.idToken!!)
            } catch (e: ApiException) {
                // Google Sign In failed, update UI appropriately
                Log.w(TAG, "Google sign in failed", e)
            }
        }
    }
    // [END onactivityresult]

    // [START auth_with_google]
    private fun firebaseAuthWithGoogle(idToken: String) {
        val credential = GoogleAuthProvider.getCredential(idToken, null)
        auth.signInWithCredential(credential)
            .addOnCompleteListener(this) { task ->
                if (task.isSuccessful) {
                    // Sign in success, update UI with the signed-in user's information
                    Log.d(TAG, "signInWithCredential:success")
                    val user = auth.currentUser
                    updateUI(user)
                } else {
                    // If sign in fails, display a message to the user.
                    Log.w(TAG, "signInWithCredential:failure", task.exception)
                    updateUI(null)
                }
            }
    }
    // [END auth_with_google]

    // [START signin]
    private fun signIn() {
        val signInIntent = googleSignInClient.signInIntent
        startActivityForResult(signInIntent, RC_SIGN_IN)
    }
    // [END signin]

    private fun updateUI(user: FirebaseUser?) {
        if (user != null) {
            binding.nameEt.setText(user.displayName.toString())
            binding.enterBtn.isEnabled = true
        } else {
            binding.nameEt.setText("인증 실패")
        }
    }


    companion object {
        private const val RC_SIGN_IN = 9001
    }
}
profile
컴퓨터와 교육 그사이 어딘가

0개의 댓글