Search code examples
kotlinandroid-jetpack-composeretrofit2

How to list API call results in Jetpack Compose


I am trying to build a simple listing app and to fetch data from url I use retrofit2. And then, I store in MutableLiveData<Resource> object. How can I list retrofit2 results in LazyColumn?

My composable:

@ExperimentalAnimationApi
@Composable
fun CarsScreen(
    viewModel: CarListViewModel = hiltViewModel()
){
    viewModel.getCarsFromAPI()
    val carsLiveData = viewModel.carsLiveData.observeAsState()

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
    ){
        GreetingSection()
    }
    LazyColumn(
        modifier = Modifier.fillMaxSize(),

    ) {

        itemsIndexed( ){
        //TODO
        }


    }

}

Viewmodel:

@HiltViewModel
class CarListViewModel @Inject constructor(
    private val carRepository: CarRepository
): ViewModel() {

    val carsLiveData:MutableLiveData<Resource<CarsResponse>> = MutableLiveData()

    fun getCarsFromAPI() = viewModelScope.launch {
        carsLiveData.postValue(Resource.Loading())
        val response = carRepository.getCarsFromAPI()
        carsLiveData.postValue(handleCarResponse(response))

    }

    private fun handleCarResponse(response: Response<CarsResponse>) : Resource<CarsResponse> {
        if(response.isSuccessful){
            response.body()?.let{resultResponse ->
                return Resource.Success(resultResponse)
            }
        }
        return Resource.Error(response.message())
    }

}

Solution

  • observeAsState doesn't return a LiveData; it returns the data contained within the LiveData that you're observing.

    Whenever that LiveData's value changes, recomposition is triggered, and you'll get a new value.

    Change the name of property carsLiveData to just cars. You can use that directly as the items in your LazyColumn.

    One other note - you're calling viewModel.getCarsFromAPI() inside the CarsScreen composable every time it's recomposed. You probably don't want to do that.

    If you only want to get the list once, you could use a LaunchedEffect inside CarsScreen, something like:

    // Copyright 2023 Google LLC.
    // SPDX-License-Identifier: Apache-2.0
    
    @Composable
    fun CarsScreen(...) {
        LaunchedEffect(Unit) {
            viewModel.getCarsFromAPI()
        }
        ...
    }
    

    If you want to update that list, pass some state into LaunchedEffect instead of Unit - whenever that state changes, the LaunchedEffect will be canceled (if currently running) and restarted.