Thread
#multiplatform
    Kurt Renzo Acosta

    Kurt Renzo Acosta

    2 years ago
    Hello! Was anyone able to share ViewModels/Presenters between Android and iOS? I've been trying for a while but can't seem to get a solution. Here's my setup so far.
    class MyViewModel(private val useCase: UseCase) : ViewModel() {
        fun doSomething() {
            useCase()
            liveData.value = "New Value"
        }
    }
    class MyPresenter(private val myView: MyView, private val useCase: UseCase) : BasePresenter() {
        fun doSomething() {
            useCase()
            myView.updateView("New Value")
        }
    }
    I wanted to extract the common code between them so initially, I created an expected class and wanted something like this:
    expect class MyCommonClient(useCase: UseCase) {
        fun doSomething() {
            useCase()
            success("New Value")
        }
        
        expect fun success(newValue: String)  
    }
    But then,
    Expected declaration must not have a body
    So I tried out a class:
    abstract class MyCommonClient(useCase: UseCase) {
        fun doSomething() {
            useCase()
            success("New Value")
        }
        
        abstract fun success(newValue: String)  
    }
    But now, I'm having multiple supertypes
    class MyViewModel : ViewModel(), MyCommonClient() <--- Error
    Am I running towards a wall or is there any workaround or pattern to achieve something like this? Thanks in advance!
    Tobi

    Tobi

    2 years ago
    You could just inject a component that handles the
    success
    per platform into your common client.
    expect class Foo {
        fun success(value: String)
    }
    
    class MyCommonClient(
        private val foo: Foo,
        private val useCase: UseCase
    ) {
        fun doSomething() {
            useCase()
            foo.success("New Value")
        }
    }
    Kurt Renzo Acosta

    Kurt Renzo Acosta

    2 years ago
    I still wouldn't be able to use it in the ViewModel and Presenter, though. It's still gonna be a supertype.
    class MyViewModel : ViewModel(), MyCommonClient()
    Tobi

    Tobi

    2 years ago
    Why would you need both, the client and the view model?
    Kurt Renzo Acosta

    Kurt Renzo Acosta

    2 years ago
    Android uses ViewModel from the Lifecycle Arch Components then the Common Client class shares the code between that ViewModel and the Presenter for iOS. They're the same but Android uses LiveData to propagate it to the View while iOS just uses a View interface. Right now, it's duplicated code so I'm trying to find a way to extract the same logic into a common class.
    a

    Andreas Jost

    2 years ago
    Maybe it helps: Instead of having multiple supertypes you could use interfaces with default functions and delegation:
    class ClientImpl(override val useCase: UseCase) : MyCommonClient {
        override fun success(newValue: String) {
            // Impl
        }
    }
    
    interface MyCommonClient {
        val useCase: UseCase
        
        fun doSomething() {
            useCase()
            success("New Value")
        }
        
        fun success(newValue: String)
    }
    
    class MyViewModel(useCase: UseCase) : ViewModel(), MyCommonClient by ClientImpl(useCase)
    Kurt Renzo Acosta

    Kurt Renzo Acosta

    2 years ago
    Oh my how could I have missed delegation! Gonna try this out right now. Thanks a lot!
    e

    Ellen Shapiro

    2 years ago
    I just did a whole talk about this - basically I came down to using unidirectional data flow to avoid retain cycles https://speakerdeck.com/designatednerd/native-and-what-maybe-shouldnt-kotlinconf-copenhagen-denmark-december-2019
    Kurt Renzo Acosta

    Kurt Renzo Acosta

    2 years ago
    Wow. That's gonna be one of the things I'm looking forward to on Monday.
    Did a quick look and it seems that I guess I'm gonna have to give up on Android's ViewModel for a bit. I'm still trying to find a way so I don't have to manage lifecycle and configuration changes as much but we'll see.
    Okay, so made the delegation pattern work, however, I lost the expect/actual mechanism. What are your thoughts about this? Is this be okay? I'm torn between retaining ViewModels/Presenters on each platform vs just using a unified Presenter.
    d

    darkmoon_uk

    2 years ago
    Interesting subject; I'm currently using vanilla Model-View-Presenter pattern where the Model, Presenter and Contracts are in shared code. Views are typically Fragments on Android. This is probably bad; but I appear to be side-stepping the life-cycle concern of the Fragments by holding a reference to them in the Presenter - forcing Android to keep them in memory. Nothing has gone wrong yet, so what is wrong with this, provided I don't actually exceed available memory?
    Kurt Renzo Acosta

    Kurt Renzo Acosta

    2 years ago
    Yeah, seems like a vanilla presenter's the common route to go here. Well, ever since arch components in Android, I never wanted to give up on it because they already have the ViewModel's caching behavior and it doesn't get destroyed on configuration changes. Moving back to the presenter would mean handling that again, which I'm ready to do, but I'm looking for a way where I won't so I'm looking at the common code between the two of them and maybe extract them away and leave whatever differences they have so they still work both the same way.
    d

    Dmitri Sh

    2 years ago
    @Ellen Shapiro Very interesting. do you have a vide link? Implemented something similar for colors ands strings - as a Gradle build task, inspecting android colors and strings and then converting to common singleton to be used by iOS - https://github.com/dmitrish/kinsight-multiplatform
    e

    Ellen Shapiro

    2 years ago
    Should be a video link up as soon as all the KotlinConf stuff gets posted (last year they had it up in less than a week). There’s a repo for what I did though: https://github.com/bakkenbaeck/PorchPirateProtector