Search code examples
androidkotlinandroid-viewmodel

How to use view model to fetch multiple data at same time in Android?


I am building a stock comparison feature in my Android app where users can:

  1. Search for a stock by name or ticker symbol.
  2. Display the details of the selected stock in a column.
  3. Add more columns by clicking a "+" icon to search and compare details of additional stocks side-by-side.

Current Implementation:

  • ViewModel: Fetches stock details from a backend API one stock at a time.
  • UI: Includes a search field, a display column for stock details, and a "+" button to add another column.

Here’s my current code setup:

class StockViewModel : ViewModel() {
    private val _stockDetails = MutableLiveData<Stock>()
    val stockDetails: LiveData<Stock> = _stockDetails

    fun fetchStockDetails(ticker: String) {
        viewModelScope.launch {
            try {
                val stock = repository.getStockDetails(ticker)
                _stockDetails.value = stock
            } catch (e: Exception) {
                Log.e("StockViewModel", "Error fetching stock details", e)
            }
        }
    }
}

This view model fetches one stock at a time. How can I get new stock details while keeping previous and refreshing each stock to get fresh data using the refresh button.


Solution

  • You need to store a list of stocks somewhere. You could do that in the view model as you already do it with the single stock you have so far, but this is something that should be stored in the repository instead.

    You would usually use a Flow for that:

    class Repository {
        private val _stocks = MutableStateFlow<List<Stock>>(emptyList())
        val stocks = _stocks.asStateFlow()
    
        suspend fun loadStockDetails(ticker: String) {
            try {
                val stock = getStockDetails(ticker)
                _stocks.update { it + stock }
            } catch (e: Exception) {
                Log.e("StockViewModel", "Error fetching stock details", e)
            }
        }
    
        private suspend fun getStockDetails(ticker: String): Stock {
            // ...
        }
    }
    

    The view model then only needs to convert the flow to a LiveData:

    val stockDetails: LiveData<List<Stock>> = repository.stocks.asLiveData()
    

    Better yet, just pass the flow through to the UI (i.e. val stockDetails = repository.stocks) and let the UI directly collect the flow, removing the need for LiveData altogether. If you use Compose for your UI that is especially simple:

    val stocks: List<Stock> by viewModel.stockDetails.collectAsStateWithLifecycle()
    

    Now that loading stocks is encapsualted in the repository you can add additional logic there, for example what should happen when the stock was already loaded or a way to persist the data in a database so it doesn't need to be reloaded every time the app starts.