마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

코틀린 리싸이클러뷰 갱신에 관한 질문입니다.

0 추천

리싸이클러뷰 업데이트가 안됩니다. ㅠ 앱을 껏다 키거나, 프래그먼트를 이동했다 돌아오면 업데이트가 되는데리싸이클러뷰 아이템의 다이얼로그에서 API쪽으로 데이터를 보내는 작업을 수행하고 변경된걸 다시 리싸이클러뷰에 업데이트 하기위해 notifyDataSetChanged()를 쓰고싶은데 이것저것 다 시도해봐도 변화가 없거나 앱이 중단되네요... 초보라 개념이 잘 잡히지 않은 상태로 시작했더니 막막합니다.. 코드 쪽 도움 부탁드려요. 아 그리고 혹시 DB쪽 데이터 변화가 있을시 알림이 뜨게 하고싶은데 무엇을 해야할까요? DB는 Mysql 사용 중입니다.

 

코드가 길어 링크로 남깁니다.

https://drive.google.com/file/d/10chih_-4vCP5yrYzz5sIK7uckdsSSgXz/view

idisky (120 포인트) 님이 2023년 5월 24일 질문

2개의 답변

0 추천

서버의 변경사항을 바로 앱에서 반영하시는 건 간단한 작업이 아닙니다. 먼저 서버에서 데이터의 변경을 알 수 있도록 하는 시스템이 필요합니다. 이게 없으면 데이터 변경되었는지를 알 수 없으니, 처리를 못하겠죠. 한가지 방법은 클라이언트가 서버의 데이터를 변경할 때 서버에서 제공하는 API를 통해서만 할 수 있게 제한하고 이  API에서 데이터의 변경이 가해지는 걸  감지할 수 있겠죠. 물론 DB를 직접 조작하는 경우가 생길 경우 이것도100% 처리가 가능하지는 않습니다. 이런 것 까지 감안하려면 메세지큐같은 걸 중간에 두고 여기에서 처리를 하게 하는 수 밖에는 없는데, 이러면 시스템이 점점 더 커지게 되겠죠. 그래서 많은 회사들이 클라우드 시스템(AWS, AZURE)을 사용하는 겁니다.
만약 데이터가 많지 않는 소규모의 프로젝트라면 파이어베이스를 사용할 수도 있습니다. 파이어베이스 자체가 웹소켙 기반이기 때문에 DB 변경시 클라이언트에게 자동으로 푸시가 가게됩니다.
다른 비교적 쉬운 방법으로는 GraphQL을 사용하는 겁니다. GraphQL서버는 기본적으로 push 기능을 탑재하고 있기 때문에 파이어베이스와 비슷하게 데이터 변경시 푸시처리가 가능해 집니다. 물론 파이이베이스 보다는 할게 훨씬 많습니다.


그리고 리사이클러뷰가 갱신안되는 이유는 아무래도 Dialog에서 사용하는 updatedList변수가 갱신이 안되었기 때문이 아닌가 싶네요. 해당 변수가 어디에 선언되어 있고 갱신되는지 확인이 안되네요.

그리고 한가지 코드 개선사항을 말씀드리면, 어댑터나 뷰홀더 안에 itemClick같은 액션을 처리하는 코드를 위치시키지 말라는 겁니다. 이렇게 하면 코드의 재사용성도 떨어지게 되고 액션관련 동작과 어댑터/뷰홀더가 뒤섞여서 가독성도 떨어집니다. 액션에 해당하는 부분은 호출하는 쪽으로 일임하세요.

 

class ListFragment : Fragment() {

    ...

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        ...
        
        adapter.onTransferClick = { item ->
            showTransferConfirmDialog(item)
        }
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(requireContext())

        refreshData()

        return view
    }

    private fun refreshData() {
        val call = RetrofitClient.instance.getData(phoneNumber)
        call.enqueue(object : Callback<List<YourDataClass>> {
            ....
        })

    private fun showTransferConfirmDialog(item: YourDataClass) {
          /  / Create a custom dialog
            val dialog = Dialog(holder.itemView.context)
            dialog.setContentView(R.layout.popup_dialog)

            // 확인 버튼 클릭 리스너
            dialog.findViewById<Button>(R.id.bt_ok).setOnClickListener {
                updateStatus2(item.seq_callboard)
                dialog.dismiss()
            }

           // 무브 클릭시 이관 API보내기
           dialog.findViewById<Button>(R.id.bt_move).setOnClickListener {
               showTranferERPConfirmDialog(item)
               dialog.dismiss()
           }

           // 날짜지정 버튼 클릭 리스너
            dialog.findViewById<Button>(R.id.bt_adddate).setOnClickListener {
                Log.d(MainActivity.TAG, "ADD")
               dialog.dismiss()
           }

          // Find the TextViews in the custom dialog
            val tvDialogSeqcallboard = dialog.findViewById<TextView>(R.id.tv_dialog_seq_callboard)
            val tvDialogRegdate = dialog.findViewById<TextView>(R.id.tv_dialog_reg_date)
            val tvDialogRequestname = dialog.findViewById<TextView>(R.id.tv_dialog_request_name)
            val tvDialogCustomer = dialog.findViewById<TextView>(R.id.tv_dialog_customer)
            val tvDialogRequestdept = dialog.findViewById<TextView>(R.id.tv_dialog_request_dept)
            val tvDialogContents = dialog.findViewById<TextView>(R.id.tv_dialog_contents)
            val tvDialogtvSasreceivedate = dialog.findViewById<TextView>(R.id.tv_dialog_sas_receivedate)
            val tvDialogSasdamdtel = dialog.findViewById<TextView>(R.id.tv_dialog_sas_damdtel)

            // Set the text of the TextViews in the custom dialog
            tvDialogSeqcallboard.text = item.seq_callboard
            tvDialogRegdate.text = item.reg_date
            tvDialogRequestname.text = item.request_name
            tvDialogCustomer.text = item.customer
            tvDialogRequestdept.text = item.request_dept
            tvDialogContents.text = item.contents
            tvDialogtvSasreceivedate.text = item.sas_receivedate
            tvDialogSasdamdtel.text = item.sas_damdtel

            // Show the custom dialog
            dialog.show()
    }

    private fun updateStatus2(callNo: String) {
           val postNo = RetrofitClient.instance.getOk(callNo.toString())
           postNo.enqueue(object : Callback<ResponseBody> {
                 override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
                     Log.d(MainActivity.TAG, "OK:"+callNo)

                 }

                 override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
                        Log.e("Ok Error", "Error: $t")
                    }
           })
    }

   private fun showTranferERPConfirmDialog(item: YourDataClass) {
         Log.d(MainActivity.TAG, "MOVE")

         val itemCallNo2 = item.seq_callboard
         val moveDialog = Dialog(holder.itemView.context)
          moveDialog.setContentView(R.layout.move_list)

          val lvMove = moveDialog.findViewById<ListView>(R.id.listvw)
          // ERP 이관 목록
         val adapter = ArrayAdapter<String>(holder.itemView.context, android.R.layout.select_dialog_item,
                    listOf("테스터1"."테스터2","테스터3"))
         lvMove.adapter = adapter

         // 무브 클릭시 이관 API보내기
         lvMove.setOnItemClickListener { _, _, position, _ ->
              transferItem(item)
              moveDialog.dismiss()
         }

        moveDialog.show()
    }

    private fun transferItem(item: YourDataClass) {
           // POST 요청
            val call = RetrofitClient.instance.getMoveOk(itemCallNo2.toString(), adapter.getItem(position).toString())
            call.enqueue(object : Callback<ResponseBody> {
                        override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
                            if (response.isSuccessful) {
                                Log.d(MainActivity.TAG, "POST Success")
                                adapter.updateData(updatedList) //<---
                            } else {
                                Log.e(MainActivity.TAG, "POST Failed")
                            }
                        }

                        override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
                            Log.e("POST Error", "Error: $t")
                        }
             }) 
    }
}

 

class ItemAdapter(private var itemList: List<YourDataClass>, private val listener: OnClickListener? = null) : RecyclerView.Adapter<ItemAdapter.ViewHolder>(), View.OnClickListener { 

   var onItemClick: ((YourDataClass) -> Unit)? = null

    ...


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

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        ...

      holder.itemView.setOnClickListener {
           onItemClick?.invoke(item)
      }
    }

     ...

}

 

spark (224,800 포인트) 님이 2023년 5월 25일 답변
spark님이 2023년 5월 25일 수정
0 추천

그리고 어댑터 아이템 클릭시 다이얼로그를 보여주는 부분은 그 자체로 코드가 너무 길고 기능이 분리가 분명하게 되어 있으므로, 이런 경우에는 별도의 클래스로 분리하시는게 코드를 읽고 변경하기가 훨씬 쉽습니다.

class DialogHelper(
    private val context: Context,
    var onUpdateStatusClick: ((Int) -> Unit)? = null,
    var onAddDateClick: (() -> Unit)? = null,
    var onTransferItem: ((YourDataClass) -> Unit)?= null
) {

   private var mDialog: Dialog? = null

   private fun isDialogShowing() = mDialog?.isShowing == true

   fun onDestory() {
        mDialog = null
        onUpdateStatusClick = null
        onAddDateClick = null
        onTransferItem = null
   }

   fun showTransferConfirmDialog(item: YourDataClass) {
        if (isDialogShowing()) return

        //Create a custom dialog
        dialog = Dialog(holder.itemView.context).also {
           mDialog = it
        }
        dialog.setContentView(R.layout.popup_dialog)
 
        // 확인 버튼 클릭 리스너
        dialog.findViewById<Button>(R.id.bt_ok).setOnClickListener {
            onUpdateStatusClick?.invoke(item.seq_callboard)
            dialog.dismiss()
        }
 
        // 무브 클릭시 이관 API보내기
        dialog.findViewById<Button>(R.id.bt_move).setOnClickListener {
            showTranferERPConfirmDialog(item)
            dialog.dismiss()
        }

        // 날짜지정 버튼 클릭 리스너
        dialog.findViewById<Button>(R.id.bt_adddate).setOnClickListener {
            Log.d(MainActivity.TAG, "ADD")
            onAddDateClick?.invoke()
            dialog.dismiss()
        }
        
        findAndSetText((R.id.tv_dialog_seq_callboard, text = item.seq_callboard)
        findAndSetText(R.id.tv_dialog_reg_date, text = item.reg_date)
        findAndSetText(R.id.tv_dialog_request_name, text = item.request_name)
        findAndSetText((R.id.tv_dialog_customer, text = item.customer)
        findAndSetText(R.id.tv_dialog_request_dept, text = item.request_dept)
        findAndSetText(R.id.tv_dialog_contents, text = item.contents)
        findAndSetText(R.id.tv_dialog_sas_receivedate, text = item.sas_receivedate)
        findAndSetText(R.id.tv_dialog_sas_damdtel, text = item.sas_damdtel)

        // Show the custom dialog
        dialog.show()
    }

    private fun showTranferERPConfirmDialog(item: YourDataClass) {
         Log.d(MainActivity.TAG, "MOVE")
 
         val itemCallNo2 = item.seq_callboard
         val dialog = Dialog(holder.itemView.context)
         dialog.setContentView(R.layout.move_list)
 
          val lvMove = dialog.findViewById<ListView>(R.id.listvw)
          // ERP 이관 목록
         val adapter = ArrayAdapter<String>(
            holder.itemView.context, 
            android.R.layout.select_dialog_item,
            listOf("테스터1"."테스터2","테스터3")
         )
         lvMove.adapter = adapter
 
         // 무브 클릭시 이관 API보내기
         lvMove.setOnItemClickListener { _, _, position, _ ->
              onTransferItem?.invoke(item)
              dialog.dismiss()
         }
 
        dialog.show()
    }

    // Find amndthe TextViews and set text in the custom dialog
    private fun findAndSetText(viewId: Int, text: String) {
        mDialog!!.findViewById<TextView>(viewId).text = text
    }
}



class ListFragment : Fragment() {
 
    ...

    private lateinit var dialogHelper: DialogHelper

    override fun onViewCreated (view: View, savedInstanceState: Bundle) {
        super.onViewCreated(view, savedInstanceState)
        dialogHelper = DialogHelper(
            context = requireContext,
            onUpdateStatusClick = ::updateStatus2,
            onAddDateClick = {},
            onTransferItem = ::transferItem
        )
    }

    override fun onDestroyView () {
        super.onDestroyView()
        dialogHelper.onDestory()
    }
 
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        ...
         
        adapter.onTransferClick = { item ->
            dialogHelper.showTransferConfirmDialog(item)
        }
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(requireContext())
 
        refreshData()
 
        return view
    }
 
    ...
}

 

코드가 더 익숙해지면 마찬가지로 데이터를 가져오는 코드도 별도의 클래스로 분리하면 좀 더 깜끔해 지겠죠.

spark (224,800 포인트) 님이 2023년 5월 25일 답변
...