ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Attach your presenters to view layer

Michael Spitsin
ProAndroidDev
Published in
6 min readOct 26, 2019

--

How it was

abstract class BasePresenter<V> {

private var view: V? = null

fun
attachView(view: V) {
this.view = view
}

open fun resume() {}

open fun pause() {}

fun detachView() {
view = null
}
}
abstract class BasePresenter<V> : ViewModel() {

...

final override fun onCleared() = detachView()
}
abstract class BaseFragment<V, P : BasePresenter<V>> : Fragment() {

protected abstract val presenter: P

protected abstract val view: V

override fun onAttach(context: Context) {
super.onAttach(context)
AndroidSupportInjection.inject(this)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.attachView(this.view)
}

override fun onResume() {
super.onResume()
presenter.resume()
}

override fun onPause() {
presenter.pause()
super.onPause()
}

override fun onDestroyView() {
presenter.detachView()
super.onDestroyView()
}
}
class SampleFragment : BaseFragment<SampleView, SamplePresenter>(), SampleView {
@Inject
override lateinit var presenter: SamplePresenter

override val view = this

...
//implementation
}

Steps we will make

How we can

Remove one fragment-to-one-presenter connection ()

abstract class BaseFragment<V> : Fragment() {

private val presenters: MutableList<BasePresenter<V>> = mutableListOf()

protected abstract val view: V

override fun onAttach(context: Context) {
super.onAttach(context)
AndroidSupportInjection.inject(this)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenters.forEach { it.attachView(this.view) }
}

override fun onResume() {
super.onResume()
presenters.forEach { it.resume() }
}

override fun onPause() {
presenters.forEach { it.pause() }
super
.onPause()
}

override fun onDestroyView() {
presenters.forEach { it.detachView() }
super
.onDestroyView()
}

protected fun BasePresenter<V>.attach() = presenters.add(this)
}
class SamplePresenter1 : BasePresenter<SampleView>()
class SamplePresenter2 : BasePresenter<SampleView>()

class SampleFragment : BaseFragment<SampleView>(), SampleView {
@Inject
lateinit var presenter1: SamplePresenter1

@Inject
lateinit var presenter2: SamplePresenter2

...//implementation

override fun onAttach(context: Context) {
super.onAttach(context)
presenter1.attach()
presenter2.attach()
}

...//implementation
}

Bind not only one view to presenters (↑)

private class PresenterViewLinker<V>(
private val presenter: BasePresenter<V>,
private val view: V
) {
fun link() = presenter.attachView(view)
fun resume() = presenter.resume()
fun pause() = presenter.pause()
fun unlink() = presenter.detachView()
}
abstract class BaseFragment : Fragment() {

private val linkers: MutableList<PresenterViewLinker<*>> = mutableListOf()

...//similarly handle all lifecycle methods

protected fun <V> BasePresenter<V>.attach(view: V) =
linkers.add(PresenterViewLinker(this, view))

private class PresenterViewLinker<V>(
private val presenter: BasePresenter<V>,
private val view: V
) {
fun link() = presenter.attachView(view)
fun resume() = presenter.resume()
fun pause() = presenter.pause()
fun unlink() = presenter.detachView()
}
}
class SamplePresenter1 : BasePresenter<SampleView1>()
class SamplePresenter2 : BasePresenter<SampleView2>()

class SampleFragment : BaseFragment(), SampleView1 {

@Inject
lateinit var presenter1: SamplePresenter1

@Inject
lateinit var presenter2: SamplePresenter2

private val sampleView2: SampleView2 by lazy { /*something*/ }

...//implementaion

override fun onAttach(context: Context) {
super.onAttach(context)
presenter1.attach(this)
presenter2.attach(sampleView2)
}

...//implementaion
}

⓷ Make a delegate for reduce boilerplate (↑)

inner class PresenterDelegate<V, P : BasePresenter<V>>(
private val view: V
) : ReadWriteProperty<Any, P> {

private var presenter: P? = null

override fun
getValue(thisRef: Any, property: KProperty<*>) =
presenter ?: throw IllegalStateException("AndroidSupportInjection.inject was not called")

override fun setValue(thisRef: Any, property: KProperty<*>, value: P) {
innerAttach(value, view)
this.presenter = value
}
}
abstract class BaseFragment : Fragment() {

...

protected inline fun <reified V : Any, reified P : BasePresenter<V>> V.presenter() = PresenterDelegate<V, P>(this)
protected fun <V> BasePresenter<V>.attach(view: V) = innerAttach(this, view)

protected fun <V> innerAttach(presenter: BasePresenter<V>, view: V) =
linkers.add(PresenterViewLinker(presenter, view))
...
}
class SampleFragment : BaseFragment(), SampleView1 {

private val sampleView2: SampleView2 by lazy { /*something*/ }

@set:Inject
var presenter1: SamplePresenter1 by presenter()

@set:Inject
var presenter2: SamplePresenter2 by sampleView2.presenter()

...//implementation
}

⓸ Move all base logic into separate class (↑)

interface PresenterContainer {

fun <V : Any, P : BasePresenter<V>> V.presenter(): PresenterDelegate<V, P>

fun <V> BasePresenter<V>.attach(view: V)
}
class PresenterLifecycleContainer : PresenterContainer, LifecycleObserver {

private val presenters: MutableList<PresenterViewLinker<*>> = mutableListOf()

override fun <V> BasePresenter<V>.attach(view: V) {
presenters.add(PresenterViewLinker(this, view))
}

fun onViewCreated() = presenters.forEach { it.link() }

@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume() = presenters.forEach { it.resume() }

@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause() = presenters.forEachReversed { it.pause() }

fun
onDestroyView() = presenters.forEachReversed { it.unlink() }

override fun
<V : Any, P : BasePresenter<V>> V.presenter() = PresenterDelegate<V, P>(this, this@PresenterLifecycleContainer)
}
abstract class BaseFragment(
private val container: PresenterLifecycleContainer = PresenterLifecycleContainer()
) : Fragment(), PresenterContainer by container {

init {
lifecycle.addObserver(container)
}
...//attaching phase override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
container.onViewCreated()
}

override fun onDestroyView() {
container.onDestroyView()
super.onDestroyView()
}
}
abstract class BaseBottomSheet(
private val container: PresenterLifecycleContainer = PresenterLifecycleContainer()
) : Fragment(), PresenterContainer by container {
init {
lifecycle.addObserver(container)
}
...//attaching phase override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
container.onViewCreated()
}

override fun onDestroyView() {
container.onDestroyView()
super.onDestroyView()
}
}

Conclusion

--

--

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Written by Michael Spitsin

Love being creative to solve some problems with an simple and elegant ways

No responses yet