TIL

[TIL] Kotlin Firebase Firestore, Storage [ 1 ] 이미지가 포함된 게시글 쓰기

정상호소인 2023. 10. 21. 23:54

Topic =  Cloud Firestore 데이터 저장하기

 


 

Why Realtime DB를 어느정도 학습했으니, Firestore에 대해서도 학습해보자.

 

  • 전에 Firebase Service 각각에 대해서 간단하게 정리해둔 글에서도 Realtime DB와 Firestore가 동일하게 DB의 역할을 한다. 이 두가지 DB는 데이터 저장 방식 ( Realtime DB - 노드 트리의 형식으로 Json 으로 저장 , Firestore - Collection - Document 으로 저장 ) 의 차이와 지원하는 쿼리의 종류 등과 같은 차이점이 있어 필요한 상황에 따라 구분하여 사용할 수 있다.
  • Firestore의 경우 정렬 및 필터링 조건을 동시에 사용이 가능하지만, Realtime DB는 동시에 사용 불가능
  • 지금까지 Firebase DB 각각을 학습하면서 생각 든 건 Realtime DB의 경우 아래 그림과 같이 채팅 같이 실시간 업데이트에 중점을 두고 별도로 정렬을 하지 않아도 되는 경우 사용하기 좋은 것 같지만, DB 양이 많아졌을 때 DB 구조를 잘 짜지않으면 데이터 정렬에 어려움이 많을 것 같다.
  • Google 문서에서는 두가지 DB 중에 무엇을 사용해야 할지 모르겠다면 Firestore를 사용하는 것을 권장한다.

 

각 DB의 일부 차이점

추가 Realtime DB 의 경우 용량, 대역폭에 따른 요금이 주로 이루고, Firestore의 경우 용량, document CRUD 연산 횟수에 따른 요금이 주로 이룬다고 한다.

 

 

 

 

 

Firebase Console - CloudFirestore 시작하기

 

Firestore 사용 설정

  • 좌측 모든 제품 탭 등을 통해서 Firestore Datebase를 생성해준다

 

  • asia-northeast3 (Seoul) 선택

 

  • 편한 방식으로 생성하고 만약 프로덕션 모드로 생성하게 되었을 경우 DB의 데이터를 읽고 쓰기 위해선 DB 규칙에 들어가서 읽기, 쓰기 보안규칙을 변경해주어야 한다.

 

Manifest, build.gradle 설정

 

build.gradle

dependencies {

    implementation(platform("com.google.firebase:firebase-bom:32.3.1"))
    implementation("com.google.firebase:firebase-auth-ktx")
    implementation("com.google.firebase:firebase-firestore-ktx")
    implementation("com.google.firebase:firebase-storage-ktx")
    implementation ("com.github.bumptech.glide:glide:4.14.2")
  • 예제에서 로그인되어 있는 사용자를 확인하여 게시글을 작성하고 내가 쓴 글을 확인할 수 있는 로직을 구현할 것이므로 storage, glide, auth도 추가되어 있다. firestore만 사용할 거면 BoM 플랫폼과 firestore만 넣어주자

Manifest

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET"/>
    <application
  • INTERNET permission 추가

 

 

DB에 넣을 Data Model 만들기
data class Post(
    val uid : String,
    val title : String,
    val price : String,
    val category : String,
    val address : String,
    val deadline : String,
    val desc : String,
    val imgs : List<String>,
    val uploadDate : String,
)
  • 먼저 게시글에 필요한 속성들을 모아놓는 Post 데이터 클래스를 만들어준다. 임시로 만든 것이다. 데이터 모델을 만들 때 주의해야할 점은 Firestore DB의 데이터 형식에 맞게 정의해주어야 한다
  • Firestore 데이터 탭의 필드 데이터의 유형을 통해 FIrestore에 알맞는 유형을 확인할 수 있다. uri 같이 유형에 존재하지 않는 형식을 넣으면 정상적인 Data가 들어가지 않는다.

 

 

Firestore 객체 선언 및 초기화
class EditFragment : Fragment() {
    private var _binding: FragmentEditBinding? = null
    private val binding get() = _binding!!

    private lateinit var auth: FirebaseAuth
    
    private var db = Firebase.firestore
    private lateinit var storage: FirebaseStorage

    private var uris: MutableList<Uri>? = mutableListOf()
    private var imgs: MutableList<String> = mutableListOf()

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

        auth = Firebase.auth
        storage = Firebase.storage

    }
  • 1. binding 및 Firebase 객체 선언
  • 2. 선택된 이미지 확인 및 Storage에 담기 위한 uri 리스트와 Storage에 담긴 이미지의 파일위치 값을 imgs에 담기위해 선언 해둔다
  • 3. onCreate 에서 Firebase 객체 초기화.

 

 

업로드 기능 구현

 

 

 

[ 1 ]  갤러리 이미지 불러오기, Rcv에 선택된 이미지 보여주기

 

    private val pickMultipleMedia =
        registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia(10)) { uriList ->
            if (uriList.isNotEmpty()) {
                uris!!.addAll(uriList)
                Log.d("xxxx", "Edit Frag Number of items selected : ${uriList.size} ")
                writePostImgAdapter.submitList(uriList)
                writePostImgAdapter.notifyDataSetChanged()
            } else {
                Log.d("xxxx", "Edit Frag No media selected: ")
            }
        }
    • 선택된 이미지 RecyclerView에 띄우기에 대해서는 별도로 정리 X

 

onViewCreated 내부에서 클릭 이벤트 정의 

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // 이미지 선택
        binding.writeBtnAddImg.setOnClickListener {
            pickMultipleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
        }

        // 게시글 작성 완료
        binding.writeBtnComplete.setOnClickListener {
            imageUpload()
        }
    }
  • 테스트 계속 해보면서 작동 여부 확인을 해야하니 업로드 기능 또한 버튼 ClickListener를 먼저 달아주고 시작하자.

 

[ 2 ] 이미지 업로드

    
    private fun getTime(): String {
        val currentDateTime = Calendar.getInstance().time
        val dateFormat =
            SimpleDateFormat("yyyy.MM.dd HH:mm:ss.SSS", Locale.KOREA).format(currentDateTime)

        return dateFormat
    }    
    
    private fun imageUpload() {

        val time = getTime()
        uris?.let { uris ->
            for (i in uris.indices) {
                val uri = uris[i]
                val fileName = "${time}_$i"

                imgs.add(fileName)

                storage.reference.child("post").child(fileName).putFile(uri).addOnSuccessListener {
                    postUpload(time)
                }.addOnFailureListener {
                    Log.d("xxxx", " Edit Frag imageUpload Failure : $it ")
                }
            }
        }
        Log.d("xxxx", " Edit Frag Post Info Img : $imgs ")
        Log.d("xxxx", "Edit Frag Post Info uris : $uris")
    }
  • 게시글 업로드 시 시간을 확인해 해당 시간을 파일 경로 이름으로 지정해 해당 이미지를 식별할 수 있도록 해두었다.
  • Storage에 이미지가 정상적으로 업로드 되면 게시글도 Firestore에 업로드 되도록 onSuccessListener 내에 postUpload()를 미리 정의 해두자.
  • ✔️ 우연히 동시에 게시글을 작성할 경우의 오류 상황이 발생할 수 있어 추후에 예외 처리 또는 더 올바른 데이터 저장 방식을 찾으면 수정할 예정.

 

  • Storage에 이미지 파일 넣기에 대한 자세한 내용은 아래 게시글에서 별도로 정리
 

[TIL] Firebase Storage 단일, 복수 이미지 넣고 불러오기

Topic = FIrebase Storage 통해 이미지 넣고 불러오기 + Metadata 간단한 개념 Why Firebase DB에 이미지를 저장하기 위해서 Storage를 사용하는 이유 - Firebase Realtime DB, Cloud Firestore에도 uri 값을 저장하는 방식과

junes-daily.tistory.com

 

 

[ 3 ] Data 업로드

 

 

    private fun postUpload(time: String) {
        val postUid = auth.currentUser!!.uid
        val postTitle = binding.writeTitle.text.toString()
        val postPrice = binding.writePrice.text.toString()
        val postCategory = binding.writeCategory.text.toString()
        val postAddress = binding.writeAddress.text.toString()
        val postDeadline = binding.writeDeadline.text.toString()
        val postDesc = binding.writeDesc.text.toString()
        val postImg: List<String> = imgs.toList()
        val post: Post = Post(
            postUid,
            postTitle,
            postPrice,
            postCategory,
            postAddress,
            postDeadline,
            postDesc,
            postImg,
            time
        )

        db.collection("Posts")
            .add(post)
            .addOnSuccessListener {
                Log.d("xxxx", "postUpload: added with : ${it}")
                requireActivity().supportFragmentManager.beginTransaction().replace(R.id.main_frame_layout,HomeFragment()).commit()
            }
    }
  • 먼저 Data 모델의 형식에 따라 데이터를 만들어준다. 코드가 좀 지저분해 보여서 추후에 리팩토링 해보자.
  • db.collection("Posts")
    • Firestore DB의 저장될 Collection 위치를 지정해준다.
  • .add(post) 를 통해 만들어둔 post를 업로드 할 것임을 정의 해준다.
  • addOnSuccessListener로 Data가 성공적으로 처리되었을 경우 Fragment 전환

 

DB 데이터 저장 확인

 

 

 

불러오기도 하나의 게시물에 적으려고 했는데, 따로 정리하는 것이 나중에도 보기 좋을 것 같아서 분리하여 작성.

 

➕ 10. 22 링크 추가 

 

[TIL] Firestore, Storage [ 2 ] 이미지와 함께 게시글 데이터 가져오기

Topic = currenUser UID가 포함된 Firestore Data를 가져와보자 이전 게시글에서 데이터 저장하기에 이어서 진행 [TIL] Firebase Firestore, Storage [ 1 ] 이미지가 포함된 게시글 쓰기 Topic = Cloud Firestore 데이터 저장

junes-daily.tistory.com

 

 


 

[ A. 오늘 복습한 내용 / B. 다음에 학습할 내용 ]

A. Firebase 전반적으로 복습 - Firebase DB 이해도 +1  [ 3 / 100 ]

 

B. Firestore 쿼리문 학습

 

B. Storage에 이미지 저장을 게시글의 작성 시간을 기준으로 했는데, 우연히 동시에 게시글을 작성할 경우의 오류 상황이 발생할 수 있어 예외 처리 또는 더 올바른 데이터 저장 방식을 찾아봐야겠다.

 


 

[오류,에러 등등]

1. uri는 Firestore 데이터에 들어가질 않는다.

 

 


 

[느낀 점]

1. 어렵다

 

2. 너무 어렵다

 

3. 🤐

 

 


[Reference]

 

// Firebase DB 비교

https://iamthejiheee.tistory.com/246