joreilly / MortyComposeKMM

GraphQL based Jetpack Compose and SwiftUI Kotlin Multiplatform project (using https://rickandmortyapi.com/graphql)
Apache License 2.0
380 stars 27 forks source link

Viewmodel state changes don't update swift view #120

Closed JulesBer closed 5 months ago

JulesBer commented 5 months ago

Hello,

I am trying to implement a paged infinite scroll view just like you did in this project. However I use the new multiplatform androidx lifecycle viewmodel. My problem is that my switfui view isn't update when I receive the new data. Do you know if it is possible to implement the same view as you, without using the KMP-ObservableViewModel lib ?

joreilly commented 5 months ago

One alternative at least is to use something like SKIE (https://skie.touchlab.co/features/flows). That's what I'm using in https://github.com/joreilly/FantasyPremierLeague

JulesBer commented 5 months ago

I am using SKIE

Here is what my viewmodel and swiftui view look like if it can help

open class VehiclesListViewModel : ViewModel(), KoinComponent {

    private val vehiclesRepository: VehiclesRepository by inject()
    private val appPreferencesRepository: AppPreferencesRepository by inject()

    companion object {
        const val POS_KEYWORD = "pointOfSales[]"
    }

    private val viewModelState = MutableStateFlow<PagingData<Vehicle>>(PagingData.empty())
    val uiState = viewModelState.asStateFlow()

    private val vehiclesPagingDataPresenter = object : PagingDataPresenter<Vehicle>() {
        override suspend fun presentPagingDataEvent(event: PagingDataEvent<Vehicle>) {
            print(event)
            updateVehiclesSnapshotList()
        }
    }

    val vehiclesSnapshotList: MutableStateFlow<ItemSnapshotList<Vehicle>> = MutableStateFlow<ItemSnapshotList<Vehicle>>(
        vehiclesPagingDataPresenter.snapshot()
    )

    private fun updateVehiclesSnapshotList() {
        vehiclesSnapshotList.value = vehiclesPagingDataPresenter.snapshot()
    }

    fun getElement(index: Int): Vehicle? {
        return vehiclesPagingDataPresenter.get(index)
    }

    fun init() {
        viewModelScope.launch {
            //getVehicles(initFilters)

            uiState.collectLatest {
                vehiclesPagingDataPresenter.collectFrom(it)
            }
        }
    }
}
import SwiftUI
import shared
struct VehiclesView: View {

    @EnvironmentObject var themeManager: ThemeManager
    @State var viewModel: VehiclesListViewModel

    init() {
        viewModel = VehiclesListViewModel()
        viewModel.doInit()
    }

    var body: some View {
        List {
            let _ = print(viewModel.vehiclesSnapshotList.value)
            ForEach(viewModel.vehiclesSnapshotList.value.indices, id: \.self) { index in
                if let vehicle = viewModel.getElement(index: Int32(index)) {
                    Text("\(vehicle.make) \(vehicle.model)")
                }
            }
        }
     }
}
JulesBer commented 5 months ago

I found a way to update the view using touchlab SKIE and updating a state that i added in my viewmodel

my view is now like this

List {
            if (vehicleListScreenState is VehicleListScreenStateFirstLoading) {
                ProgressView()
            } else {
                ForEach(vehicles.indices, id: \.self) { index in
                    if let vehicle = viewModel.getElement(index: Int32(index)) {
                        Text("\(vehicle.make) \(vehicle.model)")
                    }
                }
            }
        }
        .padding(.bottom, themeManager.selectedTheme.mediumPadding)
        .listStyle(.plain)
        .task {
            for await state in viewModel.vehicleListScreenState {
                print(state)
                self.vehicleListScreenState = state
            }
        }

viewModel.vehicleListScreenState is update by using the pagingDataPresenter.addLoadStateListener

DidarSeyidov commented 1 week ago

Hi @JulesBer could you share your full implementation. I have an issue like yours. After ending the first page the second page is not loaded in my project(I mean just about iOS side) . I use KMP-ObservableViewModel. And implemented everything as like as @joreilly ''s example