joreilly / MortyComposeKMM

GraphQL based Jetpack Compose and SwiftUI Kotlin Multiplatform project (using https://rickandmortyapi.com/graphql)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Viewmodel state changes don't update swift view

JulesBer opened this issue · comments

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 ?

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

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)")
                }
            }
        }
     }
}

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