[Android /Kotlin] Firebase Storage 다중 이미지 업로드 (2) 아이템 삭제

Subeen·2023년 3월 15일
0

Android

목록 보기
12/73
post-thumbnail

지난 포스팅에서는 Firebase Storage를 사용하여 이미지를 다중 업로드 하는 예제를 만들어 봤는데, 업로드에 이어 아이템의 버튼을 클릭했을 때 아이템이 삭제 되는 기능을 추가하였다. 본 포스팅에서는 추가 된 부분의 코드만 작성하려고 하니 자세한 내용은 다중 이미지 업로드 (1) 을 참고해보자

📍 결과 동영상

  • 아이템의 우측 상단에 삭제 버튼을 추가하였다.
  • 삭제 버튼을 눌러야지만 아이템이 삭제되며 삭제된 아이템의 개수도 반영되어 실시간으로 선택 된 이미지의 개수를 확인할 수 있다.

📍 Code

👩🏻‍💻 RecyclerView 아이템

  • item_image.xml

📌 FrameLayout
여러 개의 뷰를 중첩으로 배치하고 그 중 하나를 레이아웃의 전면에 표시할 때 사용하는 레이아웃
이미지뷰 위에 삭제 버튼을 겹쳐서 배치하기 위해 FrameLayout을 사용하였다.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="100dp"
    android:layout_height="100dp">

    <ImageView
        android:id="@+id/imageArea"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"
        android:scaleType="center" />

    <Button
        android:id="@+id/btnDelete"
        android:layout_width="35dp"
        android:layout_height="35dp"
        android:layout_gravity="right"
        android:background="@drawable/delete"
        tools:ignore="RtlHardcoded" />

</FrameLayout>

👩🏻‍💻 RecyclerView Adapter

  • ImageAdapter.kt
    • RecyclerView 클릭 이벤트 구현하기
	// onItemClickListener 인터페이스 선언하기 
    interface onItemClickListener {
        fun onItemClick(position: Int)
    }
	// onItemClickListener 참조 변수 선언하기 
    private lateinit var itemClickListener: onItemClickListener
    // onItemClickListener 등록 메서드 
    fun setItemClickListener(itemClickListener: onItemClickListener) {
        this.itemClickListener = itemClickListener
    }
    // ViewHolder에서 delete 버튼을 눌렀을 때 Listener에 onItemClick 등록하기 
    delete.setOnClickListener {
                val position = adapterPosition
                itemClickListener.onItemClick(position)
            }
class ImageAdapter(val context: Context, val items: ArrayList<Uri>) :
    RecyclerView.Adapter<ImageAdapter.ViewHolder>() {
    interface onItemClickListener {
        fun onItemClick(position: Int)
    }

    private lateinit var itemClickListener: onItemClickListener
    fun setItemClickListener(itemClickListener: onItemClickListener) {
        this.itemClickListener = itemClickListener
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageAdapter.ViewHolder {
        val v =
            LayoutInflater.from(parent.context).inflate(R.layout.item_image, parent, false)
        return ViewHolder(v)
    }

    override fun getItemCount(): Int {
        return items.count()
    }

    override fun onBindViewHolder(holder: ImageAdapter.ViewHolder, position: Int) {
        holder.bindItems(items[position])
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bindItems(item: Uri) {
            val imageArea = itemView.findViewById<ImageView>(R.id.imageArea)
            val delete = itemView.findViewById<Button>(R.id.btnDelete)

            delete.setOnClickListener {
                val position = adapterPosition
                itemClickListener.onItemClick(position)
            }

            Glide.with(context).load(item).into(imageArea)
        }
    }
}

👩🏻‍💻 이미지 업로드/삭제 화면 Activity 구현하기

  • MultiImageFragment.kt
    • Adapter에 onItemClickListener 등록하기
adapter.setItemClickListener(object : ImageAdapter.onItemClickListener {
            override fun onItemClick(position: Int) {
            	// 해당 position의 요소 삭제 
                uriList.removeAt(position)
                // notifyDataSetChanged()를 호출하여 adapter에게 값이 변경 되었음을 알려준다.
                adapter.notifyDataSetChanged()
                printCount()
            }
class MultiImageFragment : Fragment() {
    private lateinit var binding: FragmentMultiImageBinding
    lateinit var mainActivity: MainActivity
    private var uriList = ArrayList<Uri>()
    private val maxNumber = 10
    lateinit var adapter: ImageAdapter

    override fun onAttach(context: Context) {
        super.onAttach(context)

        mainActivity = context as MainActivity
    }

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

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_multi_image, container, false)
        printCount()
        adapter = ImageAdapter(mainActivity, uriList)
        binding.recyclerview.adapter = adapter
        binding.recyclerview.layoutManager =
            LinearLayoutManager(mainActivity, LinearLayoutManager.HORIZONTAL, false)

        binding.imageArea.setOnClickListener {
            if (uriList.count() == maxNumber) {
                Toast.makeText(
                    getActivity(),
                    "이미지는 최대 ${maxNumber}장까지 첨부할 수 있습니다.",
                    Toast.LENGTH_SHORT
                ).show();
                return@setOnClickListener
            }
            val intent = Intent(Intent.ACTION_PICK)
            intent.type = "image/*"
            intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
            registerForActivityResult.launch(intent)
        }

        binding.btnRegister.setOnClickListener {
            for (i in 0 until uriList.count()) {
                imageUpload(uriList.get(i), i)
                try {
                    Thread.sleep(500)
                } catch (e: InterruptedException) {
                    e.printStackTrace()
                }
            }

        }

        adapter.setItemClickListener(object : ImageAdapter.onItemClickListener {
            override fun onItemClick(position: Int) {
                uriList.removeAt(position)
                adapter.notifyDataSetChanged()
                printCount()
            }

        })

        return binding.root
    }

    private val registerForActivityResult =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            when (result.resultCode) {
                AppCompatActivity.RESULT_OK -> {
                    val clipData = result.data?.clipData
                    if (clipData != null) { // 이미지를 여러 개 선택할 경우
                        val clipDataSize = clipData.itemCount
                        val selectableCount = maxNumber - uriList.count()
                        if (clipDataSize > selectableCount) { // 최대 선택 가능한 개수를 초과해서 선택한 경우
                            Toast.makeText(
                                getActivity(),
                                "이미지는 최대 ${selectableCount}장까지 첨부할 수 있습니다.",
                                Toast.LENGTH_SHORT
                            ).show();
                        } else {
                            // 선택 가능한 경우 ArrayList에 가져온 uri를 넣어준다.
                            for (i in 0 until clipDataSize) {
                                uriList.add(clipData.getItemAt(i).uri)
                            }
                        }
                    } else { // 이미지를 한 개만 선택할 경우 null이 올 수 있다.
                        val uri = result?.data?.data
                        if (uri != null) {
                            uriList.add(uri)
                        }
                    }
                    // notifyDataSetChanged()를 호출하여 adapter에게 값이 변경 되었음을 알려준다.
                    adapter.notifyDataSetChanged()
                    printCount()
                }
            }
        }

    private fun printCount() {
        val text = "${uriList.count()}/${maxNumber}"
        binding.countArea.text = text
    }

    private fun imageUpload(uri: Uri, count: Int) {
        // storage 인스턴스 생성
        val storage = Firebase.storage
        // storage 참조
        val storageRef = storage.getReference("image")
        // storage에 저장할 파일명 선언
        val fileName = SimpleDateFormat("yyyyMMddHHmmss_${count}").format(Date())

        val mountainsRef = storageRef.child("${fileName}.png")
        val uploadTask = mountainsRef.putFile(uri)

        uploadTask.addOnSuccessListener { taskSnapshot ->
            // 파일 업로드 성공
            Toast.makeText(getActivity(), "사진 업로드 성공", Toast.LENGTH_SHORT).show();
        }.addOnFailureListener {
            // 파일 업로드 실패
            Toast.makeText(getActivity(), "사진 업로드 실패", Toast.LENGTH_SHORT).show();
        }
    }
}
profile
개발 공부 기록 🌱

1개의 댓글

comment-user-thumbnail
2024년 7월 3일

데이터 불러오기는 없나요?

답글 달기