sebaslogen / resaca

Compose Multiplatform library to scope ViewModels to a Composable, surviving configuration changes and navigation
MIT License
390 stars 9 forks source link

Multiplatform support #91

Closed emilbutiri closed 1 month ago

emilbutiri commented 3 months ago

Hi! Now that the Jetpack lifecycle and view model libraries have multiplatform support, I was wondering if this library could be made multiplatform compatible. In my case, I'm thinking of iOS, and being able to scope a ViewModel to a SwiftUI View. I know of two solutions so far:

  1. KMP-ObservableViewModel solution where using the @StateViewModel annotation will take care of the lifecycle and ensure the view model is cleared when the View is destroyed

  2. This solution, that I'm currently using myself, and seems to work fine, though there's a bit of boilerplate involved (maybe because I don't know any better):

In the iOS app, create a ViewModelOwner class that implements the ViewModelOwner interface. This requires the shared module to export the Jetpack viewmodel library.

// ViewModelStoreOwner.swift

import shared 

class ViewModelStoreOwner: shared.ViewModelStoreOwner {
    internal let viewModelStore = ViewModelStore()

    fileprivate func put<VM: ViewModel>(_ vm: VM) {
        viewModelStore.put(key: VM.self.description(), viewModel: vm)
    }

    // This is called when the View containing this object is destroyed
    deinit {
        viewModelStore.clear()
    }
}

// A helper function to create a ViewModel with an owner
func viewModel<VM: ViewModel>(owner: ViewModelStoreOwner, _ factory: () -> VM) -> VM {
    let vm = factory()
    owner.put(vm)
    return vm
}

Then in a View implementation:

// SomeView.swift

import SwiftUI

struct SomeView: View {
    private let owner = ViewModelStoreOwner()
    private let vm: SomeViewModel

    init() {
        vm = viewModel(owner: owner) {
            SomeViewModel()
        }
    }

    var body: some View {
        // etc
    }
}

I'm using the second method because I don't need all the other stuff KMP-ObservableViewModel offers - I prefer to use SKIE to handle Flows.

But I started wondering if it wouldn't be possible to make viewModelScoped() work inside a SwiftUI View.

Best regards, /Emil

sebaslogen commented 3 months ago

Hi Emil, thanks for the detailed description!

I'm also a user of the KMP-ObservableViewModel lib (when it was called KMMViewModel 😅) in my pet project and indeed the @StateViewModel annotation works like a charm.

As for your second solution, that's exactly how KMP-ObservableViewModel is solving the problem in SwiftUI, although the order is slightly different, in there they register the diInit when the observable is created and the VM Store is created only at the last minute (just to call the protected clear method).

Now for your request, I'm thinking about it but I'm still in the early exploration phase, especially since Google introduced KMP support for Android VIewModels). The thing is that SwiftUI Views have a much more simple lifecycle than Compose on Android, so 90% of the book-keeping that resaca does for all the Android/Compose edge cases is not needed in SwiftUI. So if we strip that, it will end up with basically the same concise code of your solution above (or from KMP-ObservableViewModel), which is definitely good 😃

Next step after testing resaca with the new KMP ViewModel: decide if resaca will support SwiftUI and I'll update this issue with the outcome.

sebaslogen commented 3 months ago

I've just found this other implementation following the same options discussed in this thread, the more alternatives the better for reference https://github.com/joreilly/FantasyPremierLeague/commit/c6376704e1266842669ed6d7aa3cff47eea1bd28

Original reference from this tweet https://x.com/joreilly/status/1795528038781710465

enoler commented 1 month ago

I am really interested in KMM support. I am using the latest androidx.lifecycle viewmodel, and sadly, I can not use this library because I am using compose multiplatform and I share the UI between both iOS and android versions.

Is there any plan to add support for it in a near future?

Thanks in advance

sebaslogen commented 1 month ago

@enoler Yes, the support for Compose Multiplatform is actually in progress, I made some initial feasibility designs and it looks quite simpel for iOS so I´ll start the implementation this week.

By the way, Compose multiplatform is quite different from the SwiftUI support discussed above in this ticket. Since resaca is a library for Compose, the integration without Compose in a SwiftUI project is simpler and different than all the work that resaca does, so it doesn't really fit in this library. I'm still deciding if it's better to leave SwiftUI as a documentation guide or for another project/library.

enoler commented 1 month ago

@enoler Yes, the support for Compose Multiplatform is actually in progress, I made some initial feasibility designs and it looks quite simpel for iOS so I´ll start the implementation this week.

By the way, Compose multiplatform is quite different from the SwiftUI support discussed above in this ticket. Since resaca is a library for Compose, the integration without Compose in a SwiftUI project is simpler and different than all the work that resaca does, so it doesn't really fit in this library. I'm still deciding if it's better to leave SwiftUI as a documentation guide or for another project/library.

Happy to read that, in my case I am not using viewmodels on the swift UI, I am handling everything inside compose, so I just import the main composable on swift and the rest it's doing the magic under the hood inside compose.

sebaslogen commented 1 month ago

Work for Compose Multiplatform support is tracked on this feature branch https://github.com/sebaslogen/resaca/pull/123

sebaslogen commented 1 month ago

Compose multiplatform support has been released 🎉

See 4.0.1 (and 4.0.0) https://github.com/sebaslogen/resaca/releases