https://kotlinlang.org logo
k

Kurt Renzo Acosta

12/06/2019, 9:54 AM
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.
Copy code
class MyViewModel(private val useCase: UseCase) : ViewModel() {
    fun doSomething() {
        useCase()
        liveData.value = "New Value"
    }
}
Copy code
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:
Copy code
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:
Copy code
abstract class MyCommonClient(useCase: UseCase) {
    fun doSomething() {
        useCase()
        success("New Value")
    }
    
    abstract fun success(newValue: String)  
}
But now, I'm having multiple supertypes
Copy code
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!
t

Tobi

12/06/2019, 10:07 AM
You could just inject a component that handles the
success
per platform into your common client.
Copy code
expect class Foo {
    fun success(value: String)
}

class MyCommonClient(
    private val foo: Foo,
    private val useCase: UseCase
) {
    fun doSomething() {
        useCase()
        foo.success("New Value")
    }
}
k

Kurt Renzo Acosta

12/06/2019, 10:13 AM
I still wouldn't be able to use it in the ViewModel and Presenter, though. It's still gonna be a supertype.
Copy code
class MyViewModel : ViewModel(), MyCommonClient()
t

Tobi

12/06/2019, 10:14 AM
Why would you need both, the client and the view model?
k

Kurt Renzo Acosta

12/06/2019, 10:17 AM
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

12/06/2019, 10:18 AM
Maybe it helps: Instead of having multiple supertypes you could use interfaces with default functions and delegation:
Copy code
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)
👌 1
k

Kurt Renzo Acosta

12/06/2019, 10:27 AM
Oh my how could I have missed delegation! Gonna try this out right now. Thanks a lot!
e

Ellen Shapiro

12/06/2019, 10:50 AM
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
💯 2
😍 1
k

Kurt Renzo Acosta

12/06/2019, 11:08 AM
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

12/06/2019, 12:57 PM
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?
k

Kurt Renzo Acosta

12/07/2019, 12:13 AM
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

12/07/2019, 3:40 AM
@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

12/07/2019, 6:25 PM
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
2 Views