android DialogFragment を ViewModel と LiveDataで実装

レガシーcallbackスタイルからの脱却

Androidでダイアログのcallbackを受け取るのってめんどくさいですよね。
お作法的にはcallback interfaceを定義してそいつを通じてイベントを受け取る感じで。

・DialogFragmentにinterfaceを定義
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は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()
    }
}

これがめんどくさい。
という訳で↓

ViewModelとLiveDataでクリックイベントを通知

今時はViewModeとLiveDataで通知するのがイケてますね。

・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で通知の状態定義
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.
                }
            }
        }
    }
}

こんな感じでしょうか。
Activityからも同じ感じで使えます。

ソースはこちらにあげてます。
https://github.com/sakony/android_samples