[Android/Kotlin] 리사이클러뷰 아이템 클릭 시 데이터 넘기기 (SharedPreferences를 Json 포맷으로 관리)

코코아의 개발일지·2023년 7월 16일
0

Android-Kotlin

목록 보기
4/31
post-thumbnail

✍🏻 요구사항 분석

서버에서 받는 값은 전체 리스트밖에 없었고, 서버 통신으로 받아온 정보를 담은 리사이클러뷰의 아이템을 클릭하면 조회한 데이터를 넘겨 수정 화면에 불러와야했다.

TemplateMyFragment AddMyTemplateActivity

위의 사진을 보면 아마 이해가 더 쉬울 것이다.
리사이클러뷰 아이템 2개 중에서, 아래를 클릭하면 제목, 카테고리, 기간, Todo 리스트를 수정 화면으로 넘겨야 했다.

데이터를 넘기는 화면이 Activity였으면 Bundle에 데이터를 담아 argument에 넣어주고, 데이터를 받는 fragment에서 argument에 있는 데이터를 꺼내서 사용했을텐데 ..
(어라 잠만, 내가 왜 안 썼더라..?)

2023.11.30 업데이트
👉🏻 [Android/Kotlin] 화면을 이동할 때 데이터를 전달하는 방법
위 글을 함께 참고하면 좋을 것 같다.

💻 구현 코드

1. TemplateMyFragment에서 데이터 저장

companion object {
        private const val KEY_PREFS = "template"
        private const val KEY_DATA = "template_data"
        private const val KEY_EDITABLE = "editable"
        private const val KEY_IDX = "templateIdx"
    }
    
private fun savePref(dataSet: Template) {
        val spf = requireActivity().getSharedPreferences(KEY_PREFS, Context.MODE_PRIVATE)
        val editor = spf.edit()
        val gson = Gson()
        val json = gson.toJson(dataSet) // 템플릿 데이터 변환

        editor.putString(KEY_DATA, json)
        editor.putBoolean(KEY_EDITABLE, true)
        editor.putInt(KEY_IDX, dataSet.id)

        editor.apply()
        Log.d("debug", "Data saved")
    }

참고로 Template은 서버에서 받아오는 데이터 클래스로,

data class Template(
    @SerializedName("ID") val id: Int = 0,
    @SerializedName("CATEGORY") val category: Category,
    @SerializedName("TITLE") val title: String = "",
    @SerializedName("DESC") val desc: String = "",
    @SerializedName("SCHEDULE") val schedule: Int = 0,
    @SerializedName("START_DATE") val start_date: String = "",
    @SerializedName("END_DATE") val end_date: String = "",
    @SerializedName("TODO") val todo: ArrayList<TemplateTodoList>,
    @SerializedName("CURSOR") val cursor: String = ""
)
data class TemplateTodoList(
    @SerializedName("ID") val id: Int? = 0,
    @SerializedName("TODO") val todo: String? = ""
)

이렇게 구성된다.

2. 리사이클러뷰 클릭 시 데이터를 넘겨줌

private fun initRecyclerView(result: ArrayList<Template>) {

        // adapter
        binding.myTemplateRv.apply {
            val templateAdapter = MyTemplateRVAdapter(result)
            templateAdapter.setData(-1)

            templateAdapter.setItemClickListener(object: MyTemplateRVAdapter.MyItemClickListener {
                // item click
                override fun onItemClick(template: Template, position: Int) {
                    Log.d("TemplateMyFragment", "편집 화면으로 이동")

                    // 데이터 저장
                    savePref(template)

                    // MY템플릿 수정 화면으로 이동
                    startActivity(Intent(activity, AddMyTemplateActivity()::class.java))
                }

            })
            binding.myTemplateRv.adapter = templateAdapter
            binding.myTemplateRv.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)

        }
    }

initRecyclerView 함수는 템플릿 조회 서버 통신이 성공했을 때 파라미터로 그 response를 넣어서 호출한다. (아래 참고)

override fun onGetTemplateSuccess(result: ArrayList<Template>) {
        initRecyclerView(result)
    }

3. 어댑터 (MyTemplateRVAdapter)

리사이클러뷰 어댑터 코드는 참고 삼아 ..

class MyTemplateRVAdapter(val result: ArrayList<Template>) : RecyclerView.Adapter<MyTemplateRVAdapter.ViewHolder>() {

    interface MyItemClickListener: NewGoalView {
        fun onItemClick(template: Template, position: Int)
    }

    private lateinit var mItemClickListener: MyItemClickListener //전달받은 리스너 객체를 저장할 변수 (어댑터 내에 사용)

    fun setItemClickListener(itemClickListener: MyItemClickListener) {
        mItemClickListener = itemClickListener
    } //외부에서 전달받을 수 있는 함수

    inner class ViewHolder(val binding: ItemAddTemplateBinding, val context: Context) : RecyclerView.ViewHolder(binding.root) {

        fun bind(item: Template){
            with(binding){

                val period = item.schedule / 7
                itemTemplateWeeksTv.text = "${period}주"
                itemTemplateTitleTextTv.text = item.title
                itemTemplateDetailTextTv.text = item.desc
                itemTemplatePeriodStartTv.text = item.start_date.replace("-", ".")
                itemTemplatePeriodEndTv.text = item.end_date.replace("-", ".")

                val itemTemplateTodoTvList = listOf(
                    itemTemplateTodo1Tv,
                    itemTemplateTodo2Tv,
                    itemTemplateTodo3Tv,
                    itemTemplateTodo4Tv,
                    itemTemplateTodo5Tv,
                    itemTemplateTodo6Tv
                )
                val size = item.todo.size
				// 조회한 투두 개수만큼 아이템에 넣어줌
                for ( i:Int in 0 until size) {
                    itemTemplateTodoTvList[i].visibility = View.VISIBLE
                    itemTemplateTodoTvList[i].text = item.todo[i].todo
                }

            }
        }

    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
        ViewHolder(
            ItemAddTemplateBinding.inflate(LayoutInflater.from(parent.context), parent, false),
            parent.context
        )


    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(result[position])
        // 전체 아이템 클릭
        holder.itemView.setOnClickListener {
            mItemClickListener.onItemClick(result[position], position)
        }
    }


    override fun getItemCount(): Int = result.size
}

4. json 포맷의 데이터 받기 (AddMyTemplateActivity)

// 받는 쪽
private fun loadPref() {
        val spf = getSharedPreferences(KEY_PREFS, Context.MODE_PRIVATE)
        if (spf.contains(KEY_DATA)) {
            val gson = Gson()
            val json = spf.getString(KEY_DATA, "")
            try {
                // 데이터에 타입을 부여하기 위한 typeToken
                val typeToken = object : TypeToken<Template>() {}.type
                // 데이터 받기
                val data: Template = gson.fromJson(json, typeToken)

                // 뷰에 받아온 데이터 값 넣어주기
                with(binding) {
                    // 카테고리
                    categoryId = data.category.id
                    addMyTemplateCategoryEt.setCompoundDrawablesWithIntrinsicBounds(categoryImgList[categoryId], 0, R.drawable.ic_arrow_down, 0)
                    addMyTemplateCategoryEt.setText(categoryNameList[categoryId])
                    // 제목
                    addMyTemplateGoalEt.setText(data.title)
                    // 설명 (선택)
                    addMyTemplateDescEt.setText(data.desc)
                    // 기간
                    addMyTemplatePeriodEt.setText("${data.schedule/7}주")
					// 투두
                    val size = data.todo.size
                    for( i:Int in 0 until size ) {
                        todoList[i].visibility = View.VISIBLE
                        todoList[i].setText(data.todo[i].todo)
                    }
                }

            } catch (e: JsonParseException) { // 파싱이 안 될 경우
                e.printStackTrace()
            }
            Log.d("debug", "Data loaded")
        }
    }

onCreate() 에서 위의 loadPref() 함수를 호출해주면 됨
그럼 앞에서 봤던

TemplateMyFragment AddMyTemplateActivity

화면이 완성된다.


📚 참고 자료

profile
우당탕탕 성장하는 개발자

4개의 댓글

comment-user-thumbnail
2023년 7월 16일

잘봤습니다.

1개의 답글
comment-user-thumbnail
2023년 7월 17일

저도 개발자인데 같이 교류 많이 해봐요 ㅎㅎ! 서로 화이팅합시다!

1개의 답글