MVP de verão. Quão flexível é o Kotlin?


/ Original /


A sintaxe da linguagem Kotlin é uma coisa bastante flexível e a concisão do código, que em Java pode ser alcançado apenas com a ajuda da geração de código, o Kotlin é frequentemente implementado usando ferramentas de linguagem padrão ( uma , duas ).


, Kotlin ( ), MVP- Summer Kotlin Multiplatform.


MVP ?


Android, Fragment Activity . , , , view view . , . MVP- , view . , , view, . . , Moxy APT (Annotation Processing Tool). Moxy MVP-. , .


APT?


  1. Moxy
  2. @InjectViewState Fragment
  3. , , . Moxy .

e: <...>/kapt3/stubs/debug/adev/TestFragment.java:16: error: cannot find symbol
    public TestPresenterFactory presenterFactory;
           ^
  symbol:   class TestPresenterFactory
  location: class TestFragment

e: <...>/kapt3/stubs/debug/adev/ui/TestFragment.java:22:
error: [ComponentProcessor:MiscError] dagger.internal.codegen.ComponentProcessor was unable to process this class because not all of its dependencies could be resolved. Check for compilation errors or a circular dependency with generated code.
public final class TestFragment extends adev.ui.BaseFragment implements adev.presentation.TestView {
             ^


, , Dagger , . 4 , , . , .


Fragment . . Clean Project , , .



. — AutoFactory, , .


Dagger. , , . , . APT .


APT Kotlin/Multiplatform

Kotlin/Multiplatform API . Annotation Processing . API .


?


Android-only APT. Koin/Kodein Dagger, FragmentArgs. Moxy . , Google MVVM, - , , . , , .


Kotlin/JS 2017 . property- VueJS , . : " Fragment/Activity?" . :


interface View {
    var counter: Int
}

class Presenter : BasePresenter<View>() {

    override fun createViewProxy(view: View) = object : View {
        override var counter by state(view::counter, initial = 0)
    }

    override fun onEnter() {
        viewProxy.counter = 1
    }
}

view viewProxy, view. view property delegate, state. view view . , , view , . Map. view createViewProxy state Map .


:


  1. createViewProxy viewProxy. . , BottomSheet .
  2. , view.

. "".


№1.


, viewProxy view. , , . viewProxy view , , state, view c .


:


interface View {
    var counter: Int
}

class Presenter : BasePresenter<View>() {

    override val viewProxy = object : View {
        override var counter by state({ ::counter }, initial = 0)
    }

    override fun onCreate() {
        viewProxy.counter = 1
    }
}

abstract class BasePresenter<TView> {
    fun <T> state(
        rule: TView.() -> KMutableProperty0<T>
    ): StateDelegate<T> {}
}

Kotlin : obj::prop. :


class A {
    var b = 1
    val prop = ::b
}

: ", , this receiver- !".


class A {
    var b = 1
}

fun A.foo() {
    val prop = ::b
}

. ! !


:


interface View {
    var counter: Int?
}

class Presenter : BasePresenter<View>() {
    override val viewProxy = object : View {
        override var counter by state({ ::counter }, initial = null)
    }
}

...


e: <...>/adev/presentation/Presenter.kt: (7, 22): Type of 'counter' doesn't match the type of the overridden var-property 'public abstract var counter: Int? defined in adev.presentation.View'


, Android Studio . . ? , frontend Kotlin, , viewProxy, , IDE, , , this , state. , counter ::counter, this::counter, .


view : this .


abstract class BasePresenter<TView> {
    fun <T> state(
        rule: (TView) -> KMutableProperty0<T>
    ): StateDelegate<T> {}
}

:


interface View {
    var counter: Int?
}

class Presenter : BasePresenter<View>() {
    override val viewProxy = object : View {
        override var counter by state({ it::counter }, initial = null)
    }
}

. . , view . , viewProxy Intellij IDEA/Android Studio.



: ?


:


. . .
object View {
    interface State {
        var counter: Int
    }
    interface Methods {
        fun showMessage(message: String)
    }
}

class Presenter : BasePresenter<View.State, View.Methods>() {

    override val viewStateProxy = object : View.State {
        override var counter by state({ it::counter }, initial = 0)
    }

    override fun onEnter() {
        viewStateProxy.counter = 1
        viewMethods?.showMessage("Hello!")
    }
}

, viewMethods . , . :


. . .
object View {
    interface State {
        var counter: Int
    }
    interface Methods {
        fun showMessage(message: String)
    }
}

interface Router {
    fun toNext()
}

class Presenter : BasePresenter<View.State, View.Methods, Router>() {

    override val viewStateProxy = object : View.State {
        override var counter by state({ it::counter }, initial = 0)
    }

    override fun onEnter() {
        viewStateProxy.counter = 1
        viewMethods?.showMessage("Hello!")
        router.toNext()
    }
}

, , . , , . - . : Dagger, Retrofit Moxy.


Moxy . , view.
AddToEndSingleStrategy view.
OneExecutionStrategy , , view .
SkipStrategy .


AddToEndSingleStrategy Summer state. .


, Kotlin , , .


interface View {
    var counter: Int?
    fun showMessage()
}

class Presenter : BasePresenter<View>() {
    override val viewProxy = object : View {
        override var counter by state({ it::counter }, initial = null)
        override fun showMessage = event { it::showMessage }
    }
}

abstract class BasePresenter<TView> {
    fun event(
        getAction: (TView) -> (() -> Unit)
    ) = Event(getAction)
}

class Event<TView>(
    getAction: (TView) -> (() -> Unit)
) : () -> Unit

. :


interface View {
    var counter: Int?
}

interface Events {
    fun showMessage(message: String)
}

class Presenter : BasePresenter<View>() {

    override val viewProxy = object : View {
        override var counter by state({ it::counter }, initial = null)
    }

    val showMessage = event { it::showMessage }
}

, View . , , .


?


  1. -, — .
  2. . -, .
  3. .

– . - , . , , , . , . Kotlin , - . . , , , . , .


- . ?


Kotlin:


override fun showMessage(message: String) {
    Toast.makeText(requireContext(), "message", Toast.LENGTH_LONG).show()
}

Kotlin:


override val showMessage = { message: String ->
    Toast.makeText(requireContext(), "message", Toast.LENGTH_LONG).show()
}

Swift:


fun showMessage(message: String) {
    printMessage(message)
}

Swift:


lazy var showMessage: (String) -> Void = { message in
    self.printMessage(message)
}

(つ✧ω✧)つ


override val showMessage = { message: String -> Unit in
    printMessage(message)
}

- . , . 5-6 , , .


№2.


view, . :


interface View {
    val showMessage: () -> Unit
}

class Presenter : BasePresenter<View>() {
    override val viewProxy = object : View {
        override val showMessage = doOnlyWhenAttached { it.showMessage }
    }
}

abstract class BasePresenter<TView> {
    fun doOnlyWhenAttached(
        getAction: (TView) -> (() -> Unit)
    ) = Event(getAction)
}

class Event<TView>(
    action: (TView) -> (() -> Unit)
) : () -> Unit

. , Kotlin . vararg, . view.


interface View {
    val showMessage: (message: String) -> Unit
}

class Presenter : BasePresenter<View>() {
    override val viewProxy = object : View {
        override val showMessage = doOnlyWhenAttached { it.showMessage }
    }
}

abstract class BasePresenter<TView> {

    fun doOnlyWhenAttached(
        getAction: (TView) -> (() -> Unit)
    ) = Event0(getAction)

    fun <T1> doOnlyWhenAttached(
        getAction: (TView) -> ((T1) -> Unit)
    ) = Event1(getAction)
}

class Event0<TView>(
    action: (TView) -> (() -> Unit)
) : () -> Unit

class Event1<TView, T1>(
    action: (TView) -> ((T1) -> Unit)
) : (T1) -> Unit

e: <...>/adev/presentation/View.kt: (10, 22): Type of 'showMessage' is not a subtype of the overridden property 'public abstract val showMessage: (message: String) -> Unit defined in adev.presentation.View'


Kotlin , . , Kotlin , . doOnlyWhenAttached TView , T1. , .


"" – Event 2 :


  1. TView
  2. Event

interface View {
    val showMessage: (message: String) -> Unit
}

class Presenter : BasePresenter<View>() {
    override val viewProxy = object : View {
        override val showMessage = event { it.showMessage }.doOnlyWhenAttached()
    }
}

abstract class BasePresenter<TView> {
    fun <TAction> event(getAction: (TView) -> TAction) = EventBuilder(getAction)

    fun EventBuilder<TView, () -> Unit>.doOnlyWhenAttached() {
        return Event0(this.getAction)
    }

    fun <T1> EventBuilder<TView, (T1) -> Unit>.doOnlyWhenAttached() {
        return Event1(this.getAction)
    }
}

inline class EventBuilder<TView, TAction>(
    val getAction: (TView) -> TAction
)

class Event0<TView>(
    action: (TView) -> (() -> Unit)
) : () -> Unit

class Event1<TView, T1>(
    action: (TView) -> ((T1) -> Unit)
) : (T1) -> Unit

. shorthands . :


interface View {
    val showMessage: (message: String) -> Unit
    val showMessage1: (message: String) -> Unit
    val showMessage2: (message: String) -> Unit
}

class Presenter : BasePresenter<View>() {
    override val viewProxy = object : View {
        override val showMessage = event { it.showMessage }.build(RepeatLastStrategy())
        override val showMessage1 = event { it.showMessage1 }.doOnlyWhenAttached()
        override val showMessage2 = event { it.showMessage2 }.doExactlyOnce()
    }
}

, !



. , 30. , , production. 2- . , , !


Summer 5 production . 2 – . MaksimNovikov, metnyov deks1309 , . XanderZhu umpteenthdev mowenik iOS .



, Summer, . :



android.widget.View, UIView . , - . , . , .


interface View {}

class Presenter : SummerPresenter<View> {
    override val viewProxy = object : View {}
    val subPresenter = SubPresenter(
        onAction = { doSomething() }
    )
}

class Fragment : SummerFragment(R.layout.fragment), View {
    private val presenter by bindPresenter { Presenter() }

    private lateinit var popup: Popup
    override fun onViewCreated(...) {
        super.onViewCreated(...)
        popup = Popup(presenter.subPresenter)
    }
}

interface SubView {
    var counter: Int
}

class SubPresenter(
    private val onAction: () -> Unit
): SummerPresenter<SubView>() {
    override val viewProxy = object : SubView {
        override var counter by state({ it::counter }, initial = 0)
    }
}

class Popup(
    private val presenter: SubPresenter
) : <...>, SubView {
    init {
        bindPresenter { presenter }
    }
}

- VueJS ReactJS.


sealed State


AddToEndSingleTagStrategy Moxy, Summer state sealed .


sealed class ViewState {
    object Loading : ViewState()
    class Content(val counter) : ViewState()
}

interface View {
    var state: ViewState
}

class Presenter : SummerPresenter<View>() {
    override val viewProxy = object : View {
        override var state by state({ it::state }, initial = ViewState.Loading)
    }
}

MaksimNovikov .


didSet in Kotlin


Kotlin set . didSet didSetNotNull, didSet Swift.



:


data class Item(val id: Int, val count: Int)

interface View {
    var items: List<Item>
}

class Presenter : SummerPresenter<View>() {

    override val viewProxy = object : View {
        override var items by state({ it::items }, initial = emptyList())
    }

    fun increment(id: Int) {
        viewProxy.items = viewProxy.items.map { item ->
            if (item.id == id)
                item.copy(count = item.count + 1)
            else
                item
        }
    }
}

, view, . , state, store view .



Summer Kotlin/Multiplatform :


Kotlin/Multiplatform . iOS-, , , . , , . iOS 0. , .


Summer iOS-. XCode. Swift. Kotlin XCode TouchLab. , , iOS-. VIPER, Clean Swift .. MVP.


, Kotlin. Summer , unsafe casts , Kotlin . , , — iOS Swift view. .


, , APT, Summer, .


, viewProxy boilerplate.


interface View {
    var counter: Int
}

class Presenter : SummerPresenter<View>() {
    override val viewProxy = object : View {
        override var state by state({ it::counter }, initial = 0)
    }
}

{ it::counter } . , . , - . , lint .


view

state , , Swift. Kotlin didSet didSetNotNull view. Swift view . , , . nil. — , , .


, , state initial , state, event RepeatLastStrategy init .


, Moxy Summer Summer Moxy . 15-20 . iOS Android- Summer 3-4 . , VIPER. ViewController-, iOS- . , Kotlin. iOS, , .


Você também pode traduzir 1-2 telas para tentar. Kotlin tem uma boa interoperabilidade com o Swift, e o Swift não importa em qual idioma o código está escrito. As aulas escritas em Kotlin olham para Swift como se tivessem sido escritas no Objective-C com anotações de compatibilidade colocadas nos lugares certos.


Conclusão


Obrigado a todos que leram até o fim! Espero que você tenha aprendido algo novo sobre Kotlin por si mesmo e talvez até tente Summer . Ficaria muito grato pelo feedback construtivo.


All Articles