본문 바로가기
Android/Kotlin

[Android/Kotlin] 구글 Firebase - 2. Storage를 이용하여 사진, 파일 업로드하기

by 깐테 2021. 7. 23.

2021.07.19 - [Android] - [Android/Kotlin] 구글 Firebase - 1. 인증을 이용하여 로그인하기

 

[Android/Kotlin] 구글 Firebase - 1. 인증을 이용하여 로그인하기

2021.07.16 - [Android] - [Android] 구글 Firebase(파이어베이스) 연동하기 [Android] 구글 Firebase(파이어베이스) 연동하기 아마 요즘 가장 많이 사용하는 데이터베이스가 아닌가 할 생각이 들 정도로 많이들..

kante-kante.tistory.com

 

지난 포스트에서는 Auth. 인증을 이용하여 로그인을 진행했었다.

 

이번에는 구글 Firebase 콘솔에서 제공하는 Storage라는 기능을 이용하여 사진을 업로드 및 저장하는 것까지 진행해보도록 하겠다.

 


1. 구글 Firebase Storage, Firestore

 

1. Firebase Cloud Stroage

https://firebase.google.com/docs/storage?hl=ko 

 

Firebase용 Cloud Storage

Firebase용 Cloud Storage는 사진, 동영상 등의 사용자 제작 콘텐츠를 저장하고 제공해야 하는 앱 개발자를 위해 만들어졌습니다.

firebase.google.com

Firebase용 Cloud Storage는 사진, 동영상 등의 사용자 제작 콘텐츠를 저장하고 제공해야 하는 앱 개발자를 위해 만들어졌다.  구글 Firebase SDK 자체에서 앱의 파일 업로드 및 다운로드에 보안이 적용되어 있기 때문에 안전하고 이미지, 오디오, 동영상 등의 사용자 제작 콘텐츠를 저장할 수 있다.

 

 

주요 기능

  1.  Cloud Storage용 Firebase SDK는 네트워크 품질에 관계없이 업로드 및 다운로드를 실행합니다. 업로드 및 다운로드가 중지된 위치부터 다시 시작되므로 사용자의 시간과 대역폭이 절약됩니다.
  2.  Firebase 인증과 통합되어 개발자에게 간단하고 직관적인 인증을 제공. 파일 이름, 크기, 콘텐츠 유형 및 기타 메타데이터를 기준으로 액세스를 허용할 수 있습니다.
  3. Spotify, Google 포토 같은 앱에 사용되는 것과 동일한 인프라로 프로토타입부터 프로덕션까지 원활하게 성장할 수 있습니다.(높은 확장성)

 

 

2. Firebase Firestore

https://firebase.google.com/docs/firestore/rtdb-vs-firestore?hl=ko 

 

데이터베이스 선택: Cloud Firestore 또는 실시간 데이터베이스  |  Firebase

Firebase는 실시간 데이터 동기화를 지원하며 클라이언트에서 액세스할 수 있는 2가지 클라우드 기반 데이터베이스 솔루션을 제공합니다. Cloud Firestore는 모바일 앱 개발을 위한 Firebase의 최신 데이

firebase.google.com

Cloud Firestore는 모바일 앱 개발을 위한 Firebase의 최신 데이터베이스로서 실시간 데이터베이스의 성공을 바탕으로 더욱 직관적인 새로운 데이터 모델을 선보인다. 또한 실시간 데이터베이스보다 풍부하고 빠른 쿼리와 원활한 확장성을 제공한다.

 

이전 인증 포스트에서도 설명했지만 구글의 데이터베이스는 NoSQL이기 때문에 빠르다는 장점이 있다.

또 사용자가 직접 호스팅 비용을 추가 지불하거나 여러 데이터베이스를 구축하기 위해 들어가는 시간을 한번에 절약해줄 수 있기 때문에 소규모 프로젝트에서 많이 사용한다.

 

 


2. 기본 설정하기

 

1. Firebase 콘솔에서 만들기 및 앱에서 연결

Cloud Firestore 화면.

 

firebase 콘솔로 접속해서 Firestore를 클릭해준다.

 

 

그런 다음 테스트 모드에서 시작을 눌러주며, 지역은 northeast3로 설정해준다.

 

 

규칙을 다음과 같이 true로 설정해준다.

규칙은 다음과 같이 true로 설정해준다.

이렇게 설정해주지 않으면 추후에 자유롭게 DB를 사용하지 못할 우려가 있으므로 다음과 같이 설정해준다.

 

 

Stroage는 Firestore가 생성되면 자동으로 생성될 것이다.

 

Storage도 True로 설정해준다.

 

 

 

 

Tools - Firebase - Firestore로 들어가준다.

 

일반적으로는 위의 Firestore를 눌러 연결해주면 되지만 Kotlin 언어로 진행할 예정이므로 아래 버튼을 눌러준다.

 

 

역시 Storage도 연결 설정을 해주도록 한다.

 

 

2. MainActivity

 

MainActivity의 화면을 다음과 같이 설정해준다.

 

사진을 추가할 수 있는 버튼과 RecyclerView를 하나 추가해주었다.

 

RecyclerView에 대한 설명은 다음 블로그에 잘 설명되어 있다.

 

https://yhk-0811.tistory.com/38

 

[안드로이드] RecyclerView(1) - 리사이클러뷰란?

1. 많은 데이터를 화면에 보여주는 좋은 방법은 무엇일까? 안드로이드를 개발하다보면 리스트화된 데이터 화면에 표현해야 할 경우가 많다. (ex 이메일) 그렇다면 많은 데이터를 화면에 처리하려

yhk-0811.tistory.com

 

레이아웃에서 item_photo라는 xml 파일을 하나 만들어 주고 레이아웃을 다음과 같이 설정한다.

 

이 파일은 RecyclerView에서 반복되는 이미지들을 표현하는 item이다.

 

3. 사진 추가 - AddPhotoActivity

 

activity_add_photo라는 이름으로 xml 레이아웃을 하나 만들어 주고 화면은 다음과 같이 설정한다.

 

ImageView 하나와 간단한 설명. 그리고 업로드 버튼까지 설정해 두었다.

 

이후 ImageView를 클릭하면 사용자의 휴대폰에서 이미지를 선택할 수 있도록 처리했다.

 

AddPhotoActivity.kt

//add_photo
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.storage.FirebaseStorage
import java.text.SimpleDateFormat
import java.util.*

class AddPhotoActivity :AppCompatActivity() {

    lateinit var imageIv:ImageView
    lateinit var textEt: EditText
    lateinit var uploadBtn:Button

    val IMAGE_PICK=1111

    var selectImage:Uri?=null

    lateinit var storage:FirebaseStorage
    lateinit var firestore:FirebaseFirestore

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_add_photo)

        storage= FirebaseStorage.getInstance()
        firestore=FirebaseFirestore.getInstance()

        imageIv=findViewById(R.id.image_iv)
        textEt=findViewById(R.id.text_et)
        uploadBtn=findViewById(R.id.upload_btn)

        imageIv.setOnClickListener {
            var intent= Intent(Intent.ACTION_PICK) //선택하면 무언가를 띄움. 묵시적 호출
            intent.type="image/*"
            startActivityForResult(intent,IMAGE_PICK)
        }
        uploadBtn.setOnClickListener {
            if(selectImage!=null) {
                var fileName =
                    SimpleDateFormat("yyyyMMddHHmmss").format(Date()) // 파일명이 겹치면 안되기 떄문에 시년월일분초 지정
                storage.getReference().child("image").child(fileName)
                    .putFile(selectImage!!)//어디에 업로드할지 지정
                    .addOnSuccessListener {
                        taskSnapshot -> // 업로드 정보를 담는다
                            taskSnapshot.metadata?.reference?.downloadUrl?.addOnSuccessListener {
                                it->
                                    var imageUrl=it.toString()
                                    var photo=Photo(textEt.text.toString(),imageUrl)
                                    firestore.collection("photo")
                                        .document().set(photo)
                                        .addOnSuccessListener {
                                            finish()
                                        }
                            }
                    }
            }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if(requestCode==IMAGE_PICK&&resultCode==Activity.RESULT_OK){
            selectImage=data?.data
            imageIv.setImageURI(selectImage)
        }
    }
}

 

코드는 다음과 같이 정의해 주었다.

 

선택한 이미지와 결과값이 같아야 하므로 requestCode 정의 및 IMAGE_PICK을 상수로 선언해 주었다.

 

버튼을 클릭했을때, 업로드한 사진이 DB에 잘 저장되도록 코드를 작성했는데, 파일명을 날짜로 설정하고

taskSnapShot을 이용하여 Firestore DB에 저장할 위치를 설정해준다.

 

 

4. 데이터베이스 구조 만들기

//Photo.kt
package com.example.test // 패키지명

import java.util.*

class Photo(
    var description:String="",   // 사진 설명
    var imageUrl:String="", // 사진이 저장된 경로
    var date: Date =Date(),
    var id:String?=null
) {
}

Kotlin 클래스를 Photo라는 이름으로 하나 만들어주고 내부의 소스코드는 다음과 같이 정의한다.

 

해당코드는 Firestore에서 데이터베이스의 구조를 미리 만들어주는 클래스로

사진의 설명, Storage에 저장될 이미지 경로, 업로드 날짜 등등의 정보를 담아준다.

 

 

5. Adapter 만들기 - PhotoAdapter

 

https://blog.naver.com/zboizz/221781518089

 

[Android.2020] 안드로이드 스튜디오, 어댑터(Adapter), 어댑터 뷰(Adapter View), 리스트 뷰, 그리드 뷰

>>어댑터(Adapter) -Adapter는 데이터 테이블을 목록 형태로 보여주기 위해 사용되는 것으로 데이...

blog.naver.com

 

Adapter는 데이터와 리스트 뷰(또는 리사이클러 뷰) 사이에 통신을 위한 일종의 다리 역할을 하는 클래스라고 생각하면 된다.

 

위에서 만들어두었던 MainActivity의 RecyclerView에 데이터를 넣어주는 클래스라고 생각하면 된다.

 

 

PhotoAdapter.kt

package com.example.test //패키지명

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide

class PhotoAdapter(var context: Context, var photoList:ArrayList<Photo>) : RecyclerView.Adapter<PhotoAdapter.ViewHolder>(){

    var onItemClickListener:OnItemClickListener?=null

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView!!){
        var imageIv: ImageView =itemView.findViewById(R.id.image_iv)

        fun bind(photo:Photo){
            Glide.with(context).load(photo.imageUrl).into(imageIv)
            imageIv.setOnClickListener {
                if(onItemClickListener!=null)
                onItemClickListener?.onItemClick(photo)
            }
        }
    }

    interface OnItemClickListener{
        fun onItemClick(photo:Photo)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        var view=LayoutInflater.from(context).inflate(R.layout.item_photo,parent,false)
        return ViewHolder(view)
    }

    override fun getItemCount(): Int {
        return photoList.size
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        var photo=photoList[position]
        holder.bind(photo)
    }
}

코드를 살펴보면 bind 함수 내부에 Glide라고 하는 것이 보일 것이다.

 

Glide는 일종의 이미지 라이브러리로, 서버에 저장되어 있는 이미지 데이터를 앱에 출력할 수 있도록 도와주는 라이브러리이다. 안드로이드에서 서버 -> 앱으로 데이터를 표현하기가 뭔가 어려운 부분이 있는것 같은데 해당 라이브러리는 이를 어느정도 해소시켜주는 역할을 한다.

 

 

https://github.com/bumptech/glide

 

GitHub - bumptech/glide: An image loading and caching library for Android focused on smooth scrolling

An image loading and caching library for Android focused on smooth scrolling - GitHub - bumptech/glide: An image loading and caching library for Android focused on smooth scrolling

github.com

 

 

해당 깃허브에 들어가보면 Download 부분에 Gradle을 수정하는 코드가 나온다.

 

이를 앱 단의 Gradle에서 수정해 주고 Sync Now를 눌러 적용한다.

 

 

 

6. 이미지 세부 설명 - PhotoActivity

 

역시 activity_photo.xml 파일을 하나 만들고 레이아웃을 다음과 같이 설정한다.

 

RecyclerView에서 업로드된 이미지를 클릭했을 때 설명이 표시되도록 하는 화면이다.

 

PhotoActivity.kt

package com.example.test

import android.os.Bundle
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.bumptech.glide.Glide
import com.google.firebase.firestore.FirebaseFirestore

class PhotoActivity : AppCompatActivity() {

    lateinit var imageIv:ImageView
    lateinit var descriptionTv:TextView

    lateinit var firestore:FirebaseFirestore

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_photo)

        var id=intent.getStringExtra("id")

        firestore= FirebaseFirestore.getInstance()
        imageIv=findViewById(R.id.image_iv)
        descriptionTv=findViewById(R.id.description_tv)

        if(id!=null){
            firestore.collection("photo").document(id).get().addOnCompleteListener {
                task ->
                    if(task.isSuccessful){
                        var photo=task.result?.toObject(Photo::class.java)
                        Glide.with(this).load(photo?.imageUrl).into(imageIv)
                        descriptionTv.text=photo?.description
                    }
            }
        }
    }
}

역시 Glide를 사용한다.

 

위에서 처음에 업로드할 때 photo라는 컬렉션에 저장했던 코드를 확인할 수 있다.

여기서는 photo 컬렉션에서 해당하는 이미지의 id값을 찾아 화면에 불러오도록 처리하였다.

 

 

7. MainActivity 코드

package com.example.test

import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.DocumentChange
import com.google.firebase.firestore.FirebaseFirestore

class MainActivity : AppCompatActivity(),PhotoAdapter.OnItemClickListener {

    lateinit var email: TextView
    lateinit var auth:FirebaseAuth
    
    lateinit var addPhotoBtn: Button
    lateinit var listRv: RecyclerView

    lateinit var photoAdapter:PhotoAdapter
    lateinit var photoList:ArrayList<Photo>

    lateinit var firestore: FirebaseFirestore

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        auth = FirebaseAuth.getInstance()

        email = findViewById(R.id.email_tv)
        email.text = auth.currentUser?.email

        firestore= FirebaseFirestore.getInstance()

        addPhotoBtn=findViewById(R.id.add_photo_btn)
        listRv=findViewById(R.id.list_rv)

        photoList= ArrayList()
        photoAdapter=PhotoAdapter(this,photoList)

        listRv.layoutManager= GridLayoutManager(this, 3)
        listRv.adapter=photoAdapter

        photoAdapter.onItemClickListener=this

        firestore.collection("photo").addSnapshotListener {
                querySnapshot, FirebaseFIrestoreException ->
            if(querySnapshot!=null){
                for(dc in querySnapshot.documentChanges){
                    if(dc.type== DocumentChange.Type.ADDED){
                        var photo=dc.document.toObject(Photo::class.java)
                        photo.id=dc.document.id
                        photoList.add(photo)
                    }
                }
                photoAdapter.notifyDataSetChanged()
            }
        }

        addPhotoBtn.setOnClickListener {
            var intent= Intent(this,AddPhotoActivity::class.java)
            startActivity(intent)
        }
    }

    override fun onItemClick(photo: Photo) {
        var intent= Intent(this,PhotoActivity::class.java)
        intent.putExtra("id",photo.id)
        startActivity(intent)
    }
}

RecyclerView에서 한줄에 표시될 이미지의 갯수를 3개로 설정하였고

해당 아이템을 클릭했을 때, PhotoActivity에 있는 정보가 표시될 수 있도록 처리하였다.

 


3. 결과

메니페스트에 Activity를 추가해 주고 결과를 확인해보면

 

 

 

잘 나오는 모습을 확인할 수 있다.

반응형