본문 바로가기

TIL

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

Topic =  FIrebase Storage 통해 이미지  넣고 불러오기 + Metadata 간단한 개념

 

 


 

Why Firebase DB에 이미지를 저장하기 위해서 Storage를 사용하는 이유

 - Firebase Realtime DB, Cloud Firestore에도 uri 값을 저장하는 방식과 Base64로 변환하는 등의 방식으로 이미지를 저장하고 불러올 수 있지만, DB에 이미지 파일을 포함할 경우 DB 용량 크기의 문제 등으로 DB 상호작용의 성능 저하를 일으키는 원인이 된다.

 

Storage를 사용할 경우 얻는 이점은 다음과 같다.

  • 용량이 큰 이미지 파일을 DB와 분리하여 관리함으로 DB의 성능 저하를 방지할 수 있다.
  • FIrebase Storage는 CDN ( Content Delivery Network )을 통해 이미지 파일을 제공하는데 이는 Firebase DB에서 이미지를 불러오는 것보다 빠른 속도를 제공한다고 한다... 그래도 되게 늦게 온다
  • Firebase Console에서도 원하는 이미지로 수정이 가능하는 등 유연한 업데이트가 가능하다

 

 

Firebase Storage 
  • FirebaseStorage는 클라우드 저장소 서비스로 이미지, 동영상, 문서 등을 저장하고 관리할 수 있도록 한다.
  • Storage도 Firebase에서 제공하는 서비스이기 때문에 Firebase Console에서 사용등록을 해주어야 한다.

 

주요 개념

  • Bucket
    • Firebase Storage에 저장되는 파일은 *버킷에 저장된다.
      • *버킷
      • 버킷이란 파일을 정장하는 논리적인 단위를 한다, 이는 실제로 존재하는 하드웨어 컨테이너가 존재하는 것이 아니라, 파일을 그룹화하고 구조화하기 위한 개념이다.
  • Path
    • 경로에 저장하는 방식으로는 일반적으로 버킷이름 / 파일 경로 / ( gs://project-bucket/images/user1.jpg ) 의 형식으로 위치를 통한 접근이 가능하다.
  • 보안및 편의성
    • Realtime DB, Firestore와 마찬가지로 특정 그룹, 사용자 만이 이미지에 액세스 할 수 있도록 설정할 수 있다.
    • Firebase Storage에 업로드된 파일에 대한 고유한 URL을 제공한다.

 

 

 

Storage [ 1 ] Firebase Console 설정

 

  • Storage 또한 Firebase의 서비스 중 하나이기 때문에 Console에서 사용설정을 해주어야 한다.

 

  • 앱에서 사용자가 Firebase Storage에 파일을 저장 및 읽을 수 있게 보안규칙의 read, write 를 true로 설정해준다.

 

 

 

Storage [ 2 ] Android Studio Project 설정 Manifest, build.gradle

 

Manifest - INTERNET permission 허용

<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
       ...

 

build.gradle - Glide ( 이미지 로더 Library 중 선호하는 Library 사용 )와 Storage,auth 추가.

dependencies {

    implementation(platform("com.google.firebase:firebase-bom:32.3.1"))
    ...
    implementation("com.google.firebase:firebase-auth-ktx")
    implementation("com.google.firebase:firebase-storage-ktx")
    implementation ("com.github.bumptech.glide:glide:4.14.2")

 

 

 

Storage [ 3 ] 단일 이미지 데이터 저장
class mypage : Fragment() {
    // 이미지를 불러올 때에도 사용할 것이므로 전역변수로 사용
    private lateinit var photoUri: Uri
    private var _binding : FragmentBinding? = null
    private val binding get() = _binding!!
    
    //  UID에 해당 하는 이미지로 Mypage의 이미지를 변경할 것이므로 예제에서 필요하다.
    private lateinit var auth : FirebaseAuth
    
    ...
    
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.btnProfileImgEdit.setOnClickListener {
            pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
        }

        binding.completeButton.setOnClickListener {
            val address = binding.profileAddress.text.toString()
            val nickname = binding.profileNickname.text.toString()
            val intro = binding.profileText.text.toString()
            imageUpload()
            profileUpload(nickname,intro,address)
        }
    }
    
    private fun imageUpload() {
        
        val uploadTask = storage.reference.child("images").child("${auth.currentUser!!.uid}")
            .putFile(photoUri!!)
        uploadTask.addOnSuccessListener {
            // 파일 저장 성공 시 이벤트
            Log.d("xxxx", " img upload successful ")
        }.addOnFailureListener {
            // 파일 저장 실패 시 이벤트
            Log.d("xxxx", " img upload failure : $it ")
        }
    }
    
    // Intent.ACTION_PICK 등 편한 방법으로 이미지 가져오는 함수 지정해주기.
    private val pickMedia =
        registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
            if (uri != null) {
                Log.d("xxxx", "selected URI : $uri")
                photoUri = uri

                Glide.with(this)
                    .load(photoUri)
                    .into(binding.profileImage)

            } else {
                Log.d("xxxx", "No media selected ")
            }
        }    
    
    
}
  • Firebase DB에 데이터를 넣는 방식과 유사한 방식으로 Storage에 파일을 저장해준다.
  • addOnSuccessListener, FailureListener를 통해 처리여부를 확인할 수 있으며 Console 에서도 정상적으로 저장되었는지 확인해주면 된다

파일이 정상적으로 저장된 것을 확인할 수 있다.

 

 

 

Storage  [ 4 ] 데이터 불러오기

 

먼저 [ 3 ] 에서 작성했던 코드와 같은 클래스 내부에서 ... 에 해당하는 onCreateView() 코드이다.

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentMypageProfileBinding.inflate(inflater, container, false)

        val downloadTask = storage.reference.child("images").child("${auth.currentUser!!.uid}")
            .downloadUrl
        downloadTask.addOnSuccessListener{
            photoUri = it
            Log.d("xxxx", "downloadTask Successful uri : $it")
            if (it != null)
                Glide.with(this)
                    .load(photoUri)
                    .into(binding.profileImage)
        }.addOnFailureListener{
            Log.d("xxxx", "downloadTask Failure Exception : $it")
        }

        return binding.root
    }
  • 저장된 파일의 경로에 접근해 해당 파일을 downloadUrl 메서드를 통해 불러온 뒤 photoUri 값으로 지정해주고, ImageLoader Glide를 통해 ImageView에서 확인할 수 있도록해준다.

 

 

 

 

Storage  [ 5 ] 복수 이미지 데이터 저장
    private val pickMultipleMedia =
        registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia(10)) { uriList ->
            if (uriList.isNotEmpty()) {
                uriList.forEach {uri ->
                    Log.d("xxxx", "Selected URI: $uri")
                }
                uris = uriList.toMutableList()
                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: ")
            }
        }

    private fun imageUpload(){

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

                storage.reference.child("post").child(fileName).putFile(uri).addOnSuccessListener {
                    Log.d("xxxx", "Edit Frag imageUpload Successful : ${it}")
                }.addOnFailureListener{
                    Log.d("xxxx", " Edit Frag imageUpload Failure : $it ")
                }
            }
        }
    }

➕ 10.23 추가

  •  registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia()) 를 통해 이미지를 가져오는 경우 이미지를 선택한 순서대로 리스트에 담기지 않는다. Intent.ACTION_PICK 으로 해도 마찬가지로 클릭된 순서대로 List에 들어가지 않기 때문에 추후에 추가 예정

 

 

 

 

 

Storage Metadata 

Metadata 개념

  • 이전에 Annotation 학습하면서 Metadata의 개념에 대해 간단하게 알아보았었는데 일부 내용 옮기면서 복습해보자.
  • 메타데이터 ?
    • 메타 데이터는 특정 데이터에 대한 추가적인 세부 사항을 제공하여 해당 데이터를 효율적으로 인식할 수 있도록 해주는 것으로, 어떤 데이터의 구조화 된 정보를 분석, 분류하고 부가적 정보를 추가하기 위해 그 데이터 뒤에 함께 따라가는 정보를 한다. 사진을 촬영하는 경우 해당 사진 데이터의 촬영 시간, 어떤 카메라로 찍었는지, 촬영 위치를 별도로 저장되는 경우가 있는데, 이러한 데이터들을 메타 데이터라고 할 수 있다.
    • 자세한 개념은 위키백과 참조 
 

메타데이터 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 2010년대에 메타데이터는 일반적으로 디지털 형태를 가리킨다. 그러나 1960~1970년대의 전통적인 카드 카탈로그들 또한 메타데이터의 한 예로 들 수 있으며, 이 카

ko.wikipedia.org

 

 

Storage Metadata 적용하기 

   Storage에서는 Metadata를 통해 파일에 대한 추가정보를 파일과 함께 저장할 수 있으며 Metadata를 활용하여 파일을 관리하고 필요한 정보를 추출할 수 있다.

 

저장하기

val metadata = StorageMetadata.Builder()
            .setCustomMetadata("key1", "value1")
            .setCustomMetadata("key2", "value2")
            .build()

        val uploadTask = storage.reference.child("images").child("${auth.currentUser!!.uid}")
            .putFile(photoUri!!, metadata)
  • 저장하는 방법은, putfile 시 해당 file과 metadata를 함께 저장해주면 된다.

 

불러오기

 val uploadTask = storage.reference.child("images").child("${auth.currentUser!!.uid}")
            .metadata.addOnsuccessListener { metadata ->
               ...
            }

 

 


 

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

A. Metadata 개념 이해도 ( 87 / 100 )

 

B. 여러장의 사진을 선택하고 저장하는 기능 추후에 학습 후 추가 예정 ✔️10.21 추가

 

B. metadata를 통한 효율적인 storage 관리

 


 

[오류,에러 등등]

1. 특별한 에러는 없었다.

 

 


 

[느낀 점]

1. 코드 순서 , 생명주기 같은 개념이 어렵다.

 

2. 내가 쉬우면 남들도 쉽고 내가 어려우면 남들은 쉽다;;;

 

3. 몸 컨디션 관리를 해야할 것 같다.

 

 


[Reference]

 

// 이미지 권한 관련 - 다음에 권한관리에서 정리할 예정

https://show-me-the-money.tistory.com/entry/%EC%B5%9C%EC%8B%A0-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Kotlin-%EA%B0%A4%EB%9F%AC%EB%A6%AC%EC%97%90%EC%84%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0

// storage

https://rnfirebase.io/storage/usage

// metadata

https://ko.wikipedia.org/wiki/%EB%A9%94%ED%83%80%EB%8D%B0%EC%9D%B4%ED%84%B0