Thread
#multiplatform
    x

    Xavier Lian

    2 years ago
    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:
    class MyVM(
        private val vmDidSummonLoadingScreen: () -> Unit,
        private val vmDidDismissLoadingScreen: () -> Unit
    )
    {
        fun doAFlip() 
        {
            vmDidSummonLoadingScreen()
            startSuperLongAsyncTask {
                vmDidDismissLoadingScreen()
            }
        }
    }
    Here’s some example UI in Android
    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?
    alex009

    alex009

    2 years ago
    Just use https://github.com/icerockdev/moko-mvvm - it's port of Android architecture components to mpp.
    x

    Xavier Lian

    2 years ago
    That’s great and all, but I’m looking for a solution where the shared module has no external dependencies
    n

    Neil

    2 years ago
    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

    2 years ago
    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:
    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