coroutineとViewModelを使ってDialogFragmentを実装する

https://qiita.com/FKbelm/items/f85949dff7a3c0e7781d で試した内容の改善版です。

上の記事では、メモリリークを起こしていたので、ViewModelの力を使って再実装を目指しました。

## ViewModelの作成

```kotlin:MainActivityViewModel.kt
class MainActivityViewModel() : ViewModel() {

    val hogeDialogEvent = MutableLiveData<Unit>()

    private val _hogeDialogChannel = Channel<Boolean>()
    val hogeDialogChannel = _hogeDialogChannel as SendChannel<Boolean>

    private suspend fun showHogeDialog(): Boolean {
        // ダイアログを表示
        hogeDialogEvent.postValue(Unit)

        // suspendで結果を取得
        // Channelに値が入るまで待つ
        val result = _hogeDialogChannel.receive()

        // 結果からなんかやって返す
        return TODO(result)
    }
}
```

`showHogeDialog`はActivityにダイアログを開くよう通知する`hogeDialogEvent`に通知を出して、`_hogeDialogChannel.receive()`で結果が入ってくるのを待ちます。


## Activityの作成

```MainActivity.kt
class MainActivity : AppCompatActivity() {

    private val viewModel by lazy {
        ViewModelProviders.of(this).get(MainActivityViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        viewModel.hogeDialogChannel.observe(this, Observer {
            // ダイアログが存在しないときに開く処理
            if (supportFragmentManager.findFragmentByTag(TAG_HOGE) == null) {
                supportFragmentManager
                    .beginTransaction()
                    .add(AlertDialogFragment(), TAG_HOGE)
                    .commitAllowingStateLoss()
            }
        })
    }
}
```

ActivityはDialogFragmentを開く処理のみを担っています。

## DialogFragmentの作成

```kotlin:ChannelDialogFragment.kt
// 大元になるDialogFragment
abstract class ChannelDialogFragment<T> : DialogFragment() {
    protected abstract val channel: SendChannel<T>?
}
```

`SendChannel<T>`はcoroutinesの`Channel<T>`でのうち、送信機能のみのインターフェイスです。
ここから結果を送信してあげます。

```kotlin:AlertDialogFragment.kt
// 実際に使うDialogFragment
class AlertDialogFragment : ChannelDialogFragment<AlertDialogFragmentResult>() {

    // ActivityのViewModelを取得し、そこからchannelを取得する
    override val channel by lazy {
        activity?.let {
            ViewModelProviders.of(it).get(MainActivityViewModel::class.java).hogeDialogChannel
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val context = this.context ?: return super.onCreateDialog(savedInstanceState)

        return AlertDialog.Builder(context).apply {
            setTitle(R.string.hoge_title)
            setMessage(R.string.hoge_message)

            setPositiveButton(R.string.hoge_positive) { _, _ ->
                channel?.offer(true) // ViewModelのreceiveが受け取る
            }
            setNegativeButton(R.string.hoge_negative) { _, _ ->
                channel?.offer(false) // ViewModelのreceiveが受け取る
            }
        }.create()
    }
}
```


ChannelをViewModelに持たせることでActivityの破棄,再生成後にもChannelを容易に再取得できるようになりました。

Channelを直接公開せずにViewModelにインターフェイスを実装させることでも実現できそうですが、複数のDialogFragmentを持ちたいといったことがあった場合はChannelを直接扱う方が楽かなと思いました。

ViewModelの力は大きいですね。

## 参考

Coroutine時代のDialogFragment
https://qiita.com/idaisuke/items/b4f3c2e0a872544b97d0

Prism 5とReactiveProperty
https://blog.okazuki.jp/entry/2014/05/09/083315

Android Architecture ComponentsのViewModelとDialogFragment
http://sys1yagi.hatenablog.com/entry/2017/08/24/125416