https://kotlinlang.org logo
x

Xavier Lian

10/04/2019, 1:56 AM
I’m trying to flesh out some architecture for MVVM, but I’m not entirely sure how to bind viewmodels (VMs) to views. My questions are best asked through an example. So here’s a simple VM:
Copy code
class MyVM(
    private val vmDidSummonLoadingScreen: () -> Unit,
    private val vmDidDismissLoadingScreen: () -> Unit
)
{
    fun doAFlip() 
    {
        vmDidSummonLoadingScreen()
        startSuperLongAsyncTask {
            vmDidDismissLoadingScreen()
        }
    }
}
Here’s some example UI in Android
Copy code
class FMMain: Fragment()
{
    val loadingScreenVw: View get() = fm_main_vw_loading_screen	//From XML

    val vm: MyVM by lazy {                                //Problem a1
        MyVM(
            vmDidSummonLoadingScreen = {                  //Problem a0
                loadingScreenVw.visibility = View.VISIBLE
            },
            vmDidDismissLoadingScreen = {                 //Problem a0
                loadingScreenVw.visibility = View.GONE
            }
        )
    }

    override fun onCreate(...)
    {
        super.onCreate(...)
        vm.doAFlip()
    }
}
Problems: a0: The lambdas will leak the instance of FMMain since the lambdas reference a property of FMMain thereby implicitly capturing it. a1: Assuming the lambdas don’t leak, when the device rotates, the UI dies and so does the VM. There is the Android Component Library ViewModel class that solves this, but that can’t be used in the shared module. iOS is excluded because this architecture works pretty well since Swift has [weak self] to prevent strong captures. So my questions are: 1: How does one release lambda captures? 1.1: If not possible, what is the best way to let the UI layer know that an event happened in the VM without using any external libs/dependencies (just idomatic kotlin)? 2: Only in Android, I am considering wrapping my VM with an object that inherits from Android’s Architecture Components’ ViewModel (possibly generic idk if I can do that yet). Is there a better alternative in your opinion? 3. In your opinion, should VMs be injected into Fragments, or should Fragments instantiate and own their own VMs?
a

alex009

10/04/2019, 2:26 AM
Just use https://github.com/icerockdev/moko-mvvm - it's port of Android architecture components to mpp.
x

Xavier Lian

10/04/2019, 5:31 PM
That’s great and all, but I’m looking for a solution where the shared module has no external dependencies
n

Neil

10/05/2019, 3:53 PM
I'm a fan of Android's DataBinding, where basic view logic e.g. visibility is defined declaratively in the XML layout. You inject a reference to the VM when the XML is inflated and your code doesn't have to be littered with findView s. If your VM contains Observables (e.g. LiveData) then the view automatically updates. Is there something similar to this in iOS?
x

Xavier Lian

10/08/2019, 6:05 AM
hmm.. not that I’m familiar with any 1st party dependencies that do that. I think Apple’s recent SwiftUI might have it, but I’m not sure.
Ok, yeah. SwiftUI introduces data binding, but not in the way one would expect it to be. Binding happens in the new declarative-style code and not in the “XML” (iOS’ equivalent is the XIB and interface builder system).
-------------------------- in any case, I’ve decided to wrap my shared VMs in an “android” vm where each lambda gets its own LiveData wrapper. So:
Copy code
class AVMThingy(private val sharedVM: VMThingy)
{
    val vmDidSetLoadingIndicator: LiveData<Boolean>
    get() = _vmDidSetLoadingIndicator
    private val _vmDidSetLoadingIndicator: = MutableLiveData<Boolean>()
    
    init 
    {
        sharedVM.vmDidSetLoadingIndicator = { active ->
            //This implicitly captures this VM which is fine since it's not volatile like AndroidUI, right?
            this._vmDidSetLoadingIndicator = active
        }
    }
}
🙄 yea, it introduces another layer in Android, but whatever. The LiveData libs are too good not to use
2 Views