본문 바로가기
깡샘 코틀린

21-2 파이어베이스 스토리지

by 농농씨 2023. 7. 15.

파이어베이스 스토리지앱의 파일을 저장하는 기능을 제공한다. 스토리지를 이용하면 사용자가 앱에서 사진을 선택하고 서버에 올린 후 다시 특정 시점에 내려받을 수 있도록 할 수 있다. 파이어베이스 스토리지를 이용하는 방법을 살펴보자

 

 

스토리지 사용 설정

스토리지를 이용하려면 파이어베이스 콘솔에서 스토리지를 시작해야 한다. 스토리지를 시작하는 방법은 21-3절에서 자세하게 살펴보고 여기서는 안드로이드 앱에서 스토리지를 사용하는 방법을 알아보자.

 

파이어베이스 콘솔에서 스토리지를 시작했으면 다음처럼 앱의 빌드 그래들 파일에 라이브러리를 등록한다.

// 스토리지 라이브러리 등록
dependencies {
    implementation 'com.google.firebase:firebase-storage-ktx'
}

 

 

파일 올리기

스토리지를 이용할 때는 먼저 FirebaseStorage 객체를 얻어야 한다.

// 스토리지 객체 얻기
val storage: FiregbaseStorage=Firebase.storage

 

그리고 파일을 올리거나 내려받으려면 파일을 가리키는 StorageReference를 만들어야 한다. StorageReference 객체는 storage.reference로 만들고 다시 child() 함수로 파일의 경로가 담긴 StorageReference 객체를 만든다. 예를 들어 파일의 경로를 "images/a.jpg"라고 하면 스토리지에서 images 폴더의 a.jpg 파일을 가리킨다. 이 객체로 파일을 올리거나 내려받으면 해당 경로에 파일을 저장하거나 내려받는다.

// 스토리지 참조 만들기
val storageRef: StorageReference = storgae.reference // StorageReference 객체 만듦
val imgRef: StorageReference = storageRef.child("images/a.jpg")
// 다시 child() 함수로 파일의 경로가 담긴 StorageReference 객체 만듦. 이 경로에 파일 올리거나 내려받음

스토리지에 파일을 올릴 때는 StorageReference의 putBytes(), putFile(), putStream() 함수를 이용한다. 이 함수를 차례로 살펴보자.

 

putBytes() 함수로 바이트값 저장하기

putBytes() 함수는 바이트 배열을 스토리지에 저장할 때 사용한다. 대표적인 예가 뷰의 화면을 바이트로 읽어서 저장하는 경우이다. 그러려면 1️⃣뷰의 출력 크기와 같은 비트맵 객체를 만들고 2️⃣이 객체에 뷰의 화면을 그려야 한다.

다음에 정의한 함수는 Bitmap.createBitmap() 함수로 뷰의 크기와 같은 빈 Bitmap 객체를 만들고 여기에 Canvas 객체로 뷰의 내용을 그린 후 반환한다.

// 화면을 비트맵 객체에 그리기
fun getBitmapFromView(view: View): Bitmap? {
    var bitmap = Bitmap.createBitmap(view.width, view.height, bitmap.Config.ARGB_8888)
    // Bitmap.createBitmap() 함수로 뷰의 크기와 같은 빈 Bitmap 객체 만듦
    var canvas = Canvas(bitmap)
    // 그리고 거기에 Canvas 객체로
    view.draw(canvas) // 뷰의 내용 그린 후
    return bitmap // 반환함
}

이 함수로 이미지 뷰가 출력하는 이미지를 바이트값으로 읽어야 한다. 다음 코드에서 data에는 addPicImageView라는 이미지 뷰 객체가 출력하는 이미지 내용이 바이트값으로 저장된다.

// 이미지를 바이트값으로 읽기
val bitmap = getBitmapFromView(binding.addPicImageView) 
// addPicImageView라는 이미지 뷰 객체가 출력하는 이미지 내용이
val baos = ByteArrayOutputStream()
bitmap?.compress(Bitmap.CompressFormat.JPEG, 200, baos) // 바이트값으로 저장됨
val data = baos.toByteArray()

이 값을 putBytes() 함수로 스토리지에 저장하는 코드는 다음처럼 작성할 수 있다.

// 바이트값을 스토리지에 저장하기
var uploadTask = imgRef.putBytes(data) 
// 바이트값으로 읽어서 저장한 이미지 내용을 
// imgRef라는, 이미지경로가 "images/a.jpg"로 지정된 StorageReference 객체,의 
// putBytes() 함수로 스토리지에 저장함
uploadTask.addOnFailureListener { // 반환값인 UploadTask 객체의 실패 콜백 함수
    Log.d("kkang", "upload fail......")
}.addOnCompleteListener { taskSnapshot -> // 성고 콜백 함수
    Log.d("kkang", "upload success ...")
}

imgRef는 이미지 경로가 "images/a.jpg"로 지정된 StorageReference 객체이다. 이 객체의 putBytes() 함수로 바이트 데이터를 스토리지에 저장했다. 그리고 반환값인 UploadTask 객체의 addOnCompleteListener나 addOnFailureListener 함수로 성공/실패 콜백을 작성한다.

스토리지에 파일이 저장되면 파이어베이스 콘솔에서 확인할 수 있다.

 

성공/실패를 파악하는 것 이외에 UploadTask 객체로 파일의 URL을 가져올 수도 있다. 이 URL은 이후에 파일을 내려받을 때 사용된다.

// 파일의 URL 얻기
val urlTask = uploadTask.continueWithTask { task -> // UploadTask 객체
// 
    if (!task.isSuccessful) {
        task.exception?.let {
            throw it
        }
    }
    imgRef.downloadUrl
}.addOnCompleteListener { task ->
    if (task.isSuccessful) {
        val downloadUri = task.result // 스토리지에 저장된 파일의 URL
        Log.d("kkang", "upload url ...${downloadUri}")
    } else {
        (... 생략 ...)
    }
}

 

putStream() 함수로 저장하기

putStream() 함수는 파일을 읽는 Stream 객체를 지정하여 이 Stream 객체에서 전달되는 데이터를 업로드한다. 다음은 파일에서 데이터를 읽는 FileInputStream 객체를 putStream() 함수의 매개변수로 지정하여 업로드하는 예이다.

// 파일 스트림으로 업로드
val stream = FileInputStream(File(filePath)) // 파일에서 데이터를 읽는 FileInputStream 객체를
val uploadTask = imgRef.putStream(stream) // putStream() 함수의 매개변수로 지정하여 업로드함

 

putFile() 함수로 저장하기

putFile() 함수는 매개변수로 파일의 경로를 지정하여 업로드한다.

// 파일 경로로 업로드
val file = Uri.fromFile(File(filePath)) // 매개변수로 파일의 경로를 지정
val uploadTask = imgRef.putFile(file) // 하여 업로드

 

업로드 파일 삭제

업로드된 파일을 삭제하려면 StorageReference의 delete() 함수를 호출한다.

// 파일 삭제
val imgRef: StorageReference = storageRef.child("images/a.jpg")
imgRef.delete() // 업로드된 파일 삭제
    .addOnFailureListener {
        Log.d("kkang"," failure...........")
    }
    .addOnCompleteListener {
        Log.d("kkang"," success...........")
    }

 

 

파일 내려받기

스토리지에 저장된 파일을 내려받을 때는 getBytes()나 getFile() 함수를 이용한다. 두 함수를 살펴보자

 

 

getBytes() 함수로 바이트값 가져오기

getBytes() 함수는 스토리지에서 내려받은 파일의 바이트값을 가져온다. 이 함수를 이용하면 내려받은 파일의 데이터가 앱의 메모리에 올라가므로 OOM(out of memory) 예외가 발생할 수 있다. 따라서 내려받는 최대 바이트 수를 지정해야 한다.

다음 코드는 imgRef라는 StorageReference 객체가 가리키는 스토리지의 파일을 getBytes() 함수로 내려받는 예이다. getBytes() 함수의 매개변수로 최대 바이트 수를 지정했으며 내려받은 데이터는 addOnSuccessListener() 함수에 등록한 콜백으로 전달된다. 콜백의 매개변수가 내려받은 데이터의 바이트 배열이다. 이렇게 내려받은 데이터를 앱의 이미지 뷰에 출력했다.

// 내려받은 파일의 바이트값 가져오기
val storageRef: StorageReference = storage.reference
val imgRef: StorageReference = storageRef.child("images/a.jpg")
// imgRef라는 StorageReference 객체가 가리키는 스토리지의 파일을~~(두줄아래에서 이어서)
val ONE_MEGABYTE: Long = 1024 * 1024 // 내려받는 최대 바이트 수 지정
imgRef.getBytes(ONE_MEGABYTE).addOnSuccessListener { //~~getBytes() 함수로 내려받음
// getBytes() 함수의 매개변수로 최대 바이트수 지정함
// 내려받은 데이터는 addOnSuccessListener() 함수에 등록한 콜백으로 전달됨
// 콜백의 매개변수가 내려받은 데이터의 바이트 배열임
    val bitmap = BitmapFactory.decodeByteArray(it, 0, it.size.
    binding.downloadImageView.setImageView.setImageBitmap(bitmap))
    // 이렇게 내려받은 데이터를 앱의 이미지 뷰에 출력함
}.addOnFailureListener {
    Log.d("kkang", " failure..............")
}

 

getFile() 함수로 가져오기

getFile() 함수는 내려받은 파일을 매개변수로 지정한 파일 객체에 저장한다. getFile() 함수는 스토리지의 파일을 내려받아 로컬 저장소에 저장할 때 유용하다.

// 로컬 저장소에 파일 내려받기
val imgRef: StorageReference = storageRef.child("images/a.jpg")
val localFile = File.createTempFile("images", "jpg")
imgRef.getFile(localFile).addOnSuccessListener {
// getFile() 함수로 내려받은 파일을 매개변술 지정한 파일 객체에 저장함. 로컬 저장소에 저장할 때 유용함.
    val bitmap = BitmapFactory.decodeFile(localFile.absolutePath)
    binding.downloadImageView.setImageBitmap(bitmap)
}.addOnFailureListener {
    // 오류 처리
}

 

downloadUrl() 함수로 URL 얻기

downloadUrl() 함수는 스토리지에 저장된 파일의 URL을 가져온다. 실제 파일을 내려받지는 않고 단순히 URL만 얻는데에 사용한다. 이렇게 얻은 URL은 Glide 같은 외부 라이브러리로 스토리지의 파일을 내려받을 때 주로 사용한다.

// 스토리지 파일의 URL 얻기
val imgRef: StorageReference = storageRef.child("images/a.jpg")
imgRef.downloadUrl.addOnSuccessListener { // downloadUrl() 함수로 스토리지에 저장된 파일의 URL 가져옴
    Log.d("kkang", "download uri : $it")
}.addOnFailureListener {
    // 오류 처리
}

 

firebase-ui-storage 라이브러리 이용

스토리지에서 이미지 파일을 내려받을 때 1️⃣downloadUrl() 함수로 URL을 얻고 2️⃣Glide 같은 라이브러리로 URL이 가리키는 이미지를 내려받는다.

그런데 이 작업은 firebase-ui-storage 라이브러리를 이용하면 조금 더 쉽게 작성할 수 있다. firegase-ui-storage를 이용하려면 그래들에 다음처럼 라이브러리를 등록해야 한다.

// firebase-ui-storage 라이브러리 등록
plugins {
    (... 생략 ...)
    id 'kotlin-kapt'
}
(... 생략 ...)
dependencies {
    (... 생략 ...)
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    implementation 'com.google.firebase:firebase-storage-ktx'
    implementation 'com.firebaseui:firebase-ui-storage:7.1.0'
    kapt 'com.github.bumptech.glide:compiler:4.11.0'
}

 

firebase-ui-storage를 이용하려면 AppGlideModule을 상속받는 클래스를 작성해야 한다. 참고로 이 클래스에 @GlideModule이라는 애너테이션을 적용하면 앱에서 어디에 작성하든지 자동으로 적용된다.

// 글라이드 모듈 클래스 작성
@GlideModule // 이 애너테이션 작성하면 앱에서 어디에 작성하든 자동으로 적용됨
class MyAppGlideModule : AppGlideModule() {
// firebase-ui-storage 이용하기 위해 AppGlideModule을 상속받는 클래스를 작성함
    override fun registerComponents(context: Context, glide: Glide, registry: Registry)
    {
        registry.append(
            StorageReference::class.java, InputStream::class.java,
            FirebaseImageLoader.Factory()
        )
    }
}

 

firebase-ui-storage 라이브러리를 이용하면 다음처럼 Glide의 load() 함수에 StorageReference를 직접 전달해서 이미지를 쉽게 내려받을 수 있다.

// 이미지 내려받기
val imgRef: StorageReference = storageRef.child("images/a.jpg") // StorageReference 객체
Glide.with(this ... 생략 ...)
    .load(imgRef) // Glide의 load() 함수에 StorageReference 객체를 직접 전달해서 이미지 쉽게 내려받음
    .into(binding.downloadImageView) // 내려받은 이미지를 이미지뷰에 출력함