sockeqwe / mosby-conductor

Plugin for conductor to integrate Mosby
Apache License 2.0
131 stars 22 forks source link

Handle FragmentDialog displayed by a controller #28

Open mradzinski opened 7 years ago

mradzinski commented 7 years ago

Hey @sockeqwe, I have an use case where I need to display a FragmentDialog through a controller. I've noticed that there's a PR on the conductor repo to include a DialogController.

How would you handle such situations using mosby-conductor? I guess that following the MVP pattern the FragmentDialog shouldn't have much logic and that the logic should be handled by a presenter, but I can't seem to think of a way of having a presenter attached to it.

mradzinski commented 7 years ago

Based on that PR I linked above, I'm wondering if something like this might work...

MvpRestoreViewOnCreateController (Based on your MvpController class):

abstract class MvpRestoreViewOnCreateController<V : MvpView, P : MvpPresenter<V>>(args: Bundle?) :
        RestoreViewOnCreateController(args), MvpView, MvpConductorDelegateCallback<V, P> {

    private var presenter: P? = null

    init {
        addLifecycleListener(getMosbyLifecycleListener());
    }

    constructor(): this(null)

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
        throw UnsupportedOperationException("not implemented")
    }

    override fun getMvpView(): V {
        return this as V
    }

    override fun setPresenter(presenter: P) {
        this.presenter = presenter
    }

    override fun getPresenter(): P? {
        return presenter
    }

    /**
     * This method is for internal purpose only.
     *
     * **Do not override this until you have a very good reason**
     * @return Mosby's lifecycle listener so that
     */
    private fun getMosbyLifecycleListener(): Controller.LifecycleListener {
        return MvpConductorLifecycleListener(this)
    }
}

And then BaseMvpDialogController based on the PR but extending MvpRestoreViewOnCreateController

/**
 * A controller that displays a dialog window, floating on top of its activity's window.
 * This is a wrapper over [Dialog] object like [android.app.DialogFragment].
 * Implementations should override this class and implement [onCreateDialog] to create a custom
 * dialog, such as an [android.app.AlertDialog]
 */
abstract class MvpDialogController<V : MvpView, P : MvpPresenter<V>>(args: Bundle?) :
        RestoreViewOnCreateController<V, P>(args) {

    companion object {
        private val SAVED_DIALOG_STATE_TAG = "android:savedDialogState"
    }

    @get:Nullable
    private lateinit var dialog: Dialog
        private set

    private var dismissed: Boolean = false

    /**
     * Convenience constructor for use when no arguments are needed.
     */
    protected constructor() : this(null)

    @NonNull
    override fun onCreateView(@NonNull inflater: LayoutInflater, @NonNull container: ViewGroup, @Nullable savedViewState: Bundle?): View {
        dialog = onCreateDialog(savedViewState)

        dialog.ownerActivity = activity
        dialog.setOnDismissListener({ dismissDialog() })
        if (savedViewState != null) {
            val dialogState = savedViewState.getBundle(SAVED_DIALOG_STATE_TAG)
            if (dialogState != null) {
                dialog.onRestoreInstanceState(dialogState)
            }
        }

        return View(activity) //stub view
    }

    override fun onSaveViewState(@NonNull view: View, @NonNull outState: Bundle) {
        super.onSaveViewState(view, outState)
        val dialogState = dialog.onSaveInstanceState()
        outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState)
    }

    override fun onAttach(@NonNull view: View) {
        super.onAttach(view)
        dialog.show()
    }

    override fun onDetach(@NonNull view: View) {
        super.onDetach(view)
        dialog.hide()
    }

    override fun onDestroyView(@NonNull view: View) {
        super.onDestroyView(view)
        dialog.setOnDismissListener(null)
        dialog.dismiss()
    }

    /**
     * Display the dialog, create a transaction and pushing the controller.
     * @param router The router on which the transaction will be applied
     * @param tag The tag for this controller
     */
    fun showDialog(@NonNull router: Router, @Nullable tag: String) {
        dismissed = false
        router.pushController(RouterTransaction.with(this)
                .pushChangeHandler(VerticalChangeHandler(150, false))
                .popChangeHandler(VerticalChangeHandler(150, false))
                .tag(tag))
    }

    /**
     * Dismiss the dialog and pop this controller
     */
    @Suppress("MemberVisibilityCanPrivate")
    fun dismissDialog() {
        if (dismissed) {
            return
        }
        router.popController(this)
        dismissed = true
    }

    /**
     * Build your own custom Dialog container such as an [android.app.AlertDialog]
     *
     * @param savedViewState A bundle for the view's state, which would have been created in [onSaveViewState] or `null` if no saved state exists.
     * @return Return a new Dialog instance to be displayed by the Controller
     */
    @NonNull
    protected abstract fun onCreateDialog(@Nullable savedViewState: Bundle?): Dialog
}

Let me know you thoughts mate.