[TIL] Kotlin Firebase Firestore, Storage [ 1 ] 이미지가 포함된 게시글 쓰기
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 비교