android DialogFragment with ViewModel of AAC

Breaking away from legacy callback style

Using dialogs in android development is very cumbersome.
The most standard way is to define a callback interface and receive click events through it.

Like this.

・Define callback interface in DialogFragment
class LegacyDialogFragment : DialogFragment() {
    interface DialogListener {
        fun onOkClick(dialog: LegacyDialogFragment)
    }
  
    override fun onAttach(context: Context) {
        super.onAttach(context)
        listener = context as DialogListener
    }
  
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return AlertDialog.Builder(requireActivity())
            .setTitle(title)
            .setMessage(message)
            .setPositiveButton("OK") { _, _ ->
                listener.onOkClick(this)
            }
            .create()
    }
}
・Activity implements the interface.
class LegacyDialogActivity : AppCompatActivity(), LegacyDialogFragment.DialogListener {
    companion object {
        val TAG = LegacyDialogActivity::class.java.simpleName ?: ""
    }
  
    private lateinit var binding: ActivityLegacyDialogBinding
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_legacy_dialog)
        binding.button.setOnClickListener {
            LegacyDialogFragment.show(
                "legacy",
                "Are you sure you want to finish this activity?",
                supportFragmentManager,
                TAG
            )
        }
    }
  
    override fun onOkClick(dialog: LegacyDialogFragment) {
        finish()
    }
}

I don’t like it very much.

Observe onClick event via LiveData on ViewModel.

But now we can use ViewModel and LiveData of AAC(Android Architecture Component) to observe on click event from dialogfragment to activities or fragments.

・build.gradle
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
  
// ktx
implementation 'androidx.core:core-ktx:1.1.0'
implementation "androidx.fragment:fragment-ktx:1.1.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-beta01"
・DialogFragment
class SimpleDialogFragment : DialogFragment() {
    companion object {
        private const val BUNDLE_KEY_TITLE = "bundle_key_title"
        private const val BUNDLE_KEY_MESSAGE = "bundle_key_message"
  
        private fun newInstance() = SimpleDialogFragment()
  
        private fun newInstance(title: String, message: String): SimpleDialogFragment {
            return newInstance().apply {
                arguments = bundleOf(
                    Pair(BUNDLE_KEY_TITLE, title),
                    Pair(BUNDLE_KEY_MESSAGE, message)
                )
            }
        }
  
        fun show(title: String, message: String, fragmentManager: FragmentManager, tag: String) {
            newInstance(title, message).run {
                show(fragmentManager, tag)
            }
        }
    }
  
    lateinit var title: String
    lateinit var message: String
  
    private val viewModel: DialogViewModel by viewModels({ requireActivity() })
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments!!.run {
            title = getString(BUNDLE_KEY_TITLE)!!
            message = getString(BUNDLE_KEY_MESSAGE)!!
        }
    }
  
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return AlertDialog.Builder(requireActivity())
            .setTitle(title)
            .setMessage(message)
            .setPositiveButton("OK") { _, _ ->
                viewModel.state.value = DialogState.Ok(this@SimpleDialogFragment)
            }
            .create()
    }
  
    override fun onCancel(dialog: DialogInterface) {
        super.onCancel(dialog)
        viewModel.state.value = DialogState.Cancel(this@SimpleDialogFragment)
    }
}
・DialogState
sealed class DialogState<T : DialogFragment> {
    data class Ok<T : DialogFragment>(val dialog: T) : DialogState<T>()
    data class Cancel<T : DialogFragment>(val dialog: T) : DialogState<T>()
}
・DialogViewModel
class DialogViewModel: ViewModel() {
    val state = MutableLiveData<DialogState<SimpleDialogFragment>>()
}
・DialogShowFragment
class DialogShowFragment : Fragment() {
    companion object {
        val TAG = DialogShowFragment::class.java.simpleName ?: ""
    }
  
    private lateinit var binding: FragmentDialogShowBinding
  
    private val dialogViewModel: DialogViewModel by viewModels({ requireActivity() })
  
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_dialog_show, container, false)
  
        binding.showDialogButton.setOnClickListener {
            SimpleDialogFragment.show("from fragment.", "Are you sure you want to finish this activity?", childFragmentManager, TAG)
        }
  
        return binding.root
    }
  
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
  
        dialogViewModel.state.observe(this) {
            when (it) {
                is DialogState.Ok -> requireActivity().finish()
                is DialogState.Cancel -> {
                    // do nothing.
                }
            }
        }
    }
}

It’s pretty cool.
I like it.

Complete codes here.
https://github.com/sakony/android_samples