티스토리 뷰

Android SDK introduced Result API recently and it looks better than just passing target fragment and get the result in onActivityResult. onActivityResult sounds bit weird when we receive the result from a Fragment.

This API works with Fragments and we can use it for DialogFragment as well because DialogFragment is also Fragment.

 

We need to register listener to FragmentManager to observe from a Fragment. But when we create an DialogFragment, ChildFragment handles DialogFragment and Fragment and DialogFragment become relation Parent and Child. So, we need to use ChildFragment instead of FragmentManager.

 

First, we need to call setFragmentResultListener before we open DialogFragment. requestKey in the parameter is just a String like a Extra key. We could use this to differentiate request and I will show it later.

class HomeFragment : Fragment(R.layout.fragment_home)

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        dialogButton.setOnClickLisetner {
            showMyDialog()
        }
    }   
    
    private fun showMyDialog() {
        childFragmentManager.setFragmentResultListener(
           requestKey,
           viewLifecycleOwner,
           { requestKey, bundle ->
              
           })
        
        // TODO : Open DialogFragment
    }  
}

 

Next, we need to show a DialogFragment. Let's create a very simple one.

 

data class DialogInfo(
    val requestKey: String,
    val title: String,
    val message: String,
    val positiveCaption: String,
    val negativeCaption: String? = null
) : Serializable

class MyDialogFragment : DialogFragment() {

    companion object {
        private const val TAG = "MyDialogFragment"
        const val DIALOG_INFO_KEY = "DialogInfo"

        fun show(fm: FragmentManager, dialogInfo: DialogInfo) {
            val fragment = fm.findFragmentByTag(TAG)
            if (fragment != null) return

            MyDialogFragment().apply {
                arguments = bundleOf(DIALOG_INFO_KEY to dialogInfo)
            }.show(fm, TAG)
        }
    }

    private val dialogInfo: DialogInfo by lazyDialogInfo()
    private lateinit var dialogResult: DialogResult

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

        setCancelable(false)
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return MaterialAlertDialogBuilder(requireContext())
            .setTitle(dialogInfo.title(requireContext()))
            .setMessage(dialogInfo.message(requireContext()))
            .setPositiveButton(dialogInfo.positiveCaption) { _, _ ->
                
            }
            .setNegativeButton(dialogInfo.negativeCaption) { _, _ ->
                
            }
            .create()
    }
}

We will pass DialogInfo into MyDialogFragment to control UI elements from outside. 

 

Now, we can set result and pass to the previous Fragment.

sealed class DialogResult {
  object Yes: DialogResult()
  object No: DialogResult()
}

class MyDialogFragment : DialogFragment() {
    
    companion object {
       const val RESULT_KEY = "Result"
       ...
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return MaterialAlertDialogBuilder(requireContext())
            .setTitle(dialogInfo.title(requireContext()))
            .setMessage(dialogInfo.message(requireContext()))
            .setPositiveButton(dialogInfo.positiveCaption) { _, _ ->
                setDialogResult(DialogResult.Yes)
            }
            .setNegativeButton(dialogInfo.negativeCaption) { _, _ ->
                setDialogResult(DialogResult.No)
            }
            .create()
    }
    
    private fun setDialogResult(result: DialogResult) {
        parentFragmentManager.setFragmentResult(
            dialogInfo.requestKey,
            bundleOf(RESULT_KEY to result)
        )
    }
}

Notice that we call parentFragmentManager when we set the result as I mentioned before Fragment and DialogFrament are parent and child relation. Also, we return dialogInfo.resultKey to differentiate each request.

 

Let's handle the result in HomeFragment.

class HomeFragment : Fragment(R.layout.fragment_home) {

    companion object {
       private const val MY_DIALOG_REQUEST_KEY = "MyDialogRequest"
    }

  ... 
    
    private fun showMyDialog() {
        childFragmentManager.setFragmentResultListener(
           MY_DIALOG_REQUEST_KEY,
           viewLifecycleOwner,
           { _, bundle ->
              when (val result = bundle.getSerializable(MyDialogFragment.RESULT_KEY) as DialogResult) {
                 DialogResult.Yes -> dialogYesClicked()
                 DialogResult.No -> dialogNoClicked()
              }
           })
        
        val dialogInfo = DialogInfo(
            requestKey = MY_DIALOG_REQUEST_KEY,
            title = "Title",
            message = "Message"
        )
        MyDialogFragment.show(childFragmentManager, dialogInfo)
    }  
    
    private fun dialogYesClicked() {
    
    }
    
    private fun dialogNoClicked() {
    
    }
}

 

We will remove FragmentResultListener after use for safety.

class HomeFragment : Fragment(R.layout.fragment_home) {
    private fun showMyDialog() {
        childFragmentManager.setFragmentResultListener(
           MY_DIALOG_REQUEST_KEY,
           viewLifecycleOwner,
           { requestKey, bundle ->
             ...
              
              childFragmentManager.clearFragmentResultListener(requestKey)
           })
        
        ...
    }  
   
}

 

The code written sofar is bit verbose and not a clean from can be reused. Let's make those function as extension function.

 

interface DialogListener {
   fun onPositive()
   fun onNegative(){}
}

fun Fragment.onDialogResult(requestKey: String, listener: DialogResultListener) {
    childFragmentManager.setFragmentResultListener(
        requestKey,
        viewLifecycleOwner,
        { _, bundle ->
            when (val result = bundle.getDialogResult()) {
                DialogResult.Yes -> listener.onPositive()
                DialogResult.No -> listener.onNegative()
            }

            childFragmentManager.clearFragmentResultListener(requestKey)
        })
}

private fun Bundle.getDialogResult(): DialogResult {
    return getSerializable(MyDialogFragment.RESULT_KEY) as DialogResult
}

fun Fragment.popDialog(dialogInfo: DialogInfo) {
    MyDialogFragment.show(
        childFragmentManager,
        dialogInfo
    )
}

 

Let's replace the existing functions in HomeFragment with extension functions.

class HomeFragment : Fragment(R.layout.fragment_home) {

    companion object {
       private const val MY_DIALOG_REQUEST_KEY = "MyDialogRequest"
    }

  ... 
    
    private fun showMyDialog() {
        getDialogResult(MY_DIALOG_REQUEST_KEY, object: DialogResultListener) {
           override fun onPositive() {
           
           }
           
           override fun onNegateive() {
           
           }
        })
        
        popDialog(myDialogRequestDialog())
    }  
    
    private fun myDialogRequestDialog(): DialogInfo {
      return DialogInfo(
            requestKey = MY_DIALOG_REQUEST_KEY,
            title = "Title",
            message = "Message"
        )
    }
}

 

Different approach rather than extension function is protocoal like one.

interface DialogProtocol {
   val owner: Fragment
   
   fun onDialogResult(requestKey: String, listener: DialogResultListener) {
        sender.childFragmentManager.setFragmentResultListener(
           requestKey,
           owner.viewLifecycleOwner,
           { _, bundle ->
              when (val result = bundle.getDialogResult()) {
                DialogResult.Yes -> listener.onPositive()
                DialogResult.No -> listener.onNegative()
              }

              owner.childFragmentManager.clearFragmentResultListener(requestKey)
        })
        
   fun popDialog(dialogInfo: DialogInfo) {
      MyDialogFragment.show(
        owner.childFragmentManager,
        dialogInfo
      )
   } 
}
class HomeFragment : Fragment(R.layout.fragment_home), DialogProtocol {

    companion object {
       private const val MY_DIALOG_REQUEST_KEY = "MyDialogRequest"
    }

    override val owner: Fragment get() = this
  ... 
    
    private fun showMyDialog() {
        getDialogResult(MY_DIALOG_REQUEST_KEY, object: DialogResultListener) {
           override fun onPositive() {
           
           }
           
           override fun onNegateive() {
           
           }
        })
        
        popDialog(myDialogRequestDialog())
    }  
    
    private fun myDialogRequestDialog(): DialogInfo {
      return DialogInfo(
            requestKey = MY_DIALOG_REQUEST_KEY,
            title = "Title",
            message = "Message"
        )
    }
}

Extension and Protocol approach are very similar and one big difference is extension function will be visible all Framgnet but Protocol visible to only implement it.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함