Search code examples
androidkotlinandroid-jetpack-composeretrofit2kotlin-coroutines

API call using Retrofit2: How can I make API calls all the time, to update the data without reopening the app


In a Livescores app, how can I make the results of matches updated without reopening the app? Here is my code sample for ViewModel, if anyone can help.

@HiltViewModel
class LiveMatchesViewModel @Inject constructor(private val liveMatchesRepository: LiveMatchesRepository): ViewModel() {

    private var _liveMatchesState = MutableStateFlow<MatchState>(MatchState.Empty)
    val liveMatchesState: StateFlow<MatchState> =  _liveMatchesState

    init {
        getAllLiveMatches()
    }

    private fun getAllLiveMatches() {
        _liveMatchesState.value = MatchState.Loading

        viewModelScope.launch(Dispatchers.IO) {

            try {
                val liveMatchesResponse = liveMatchesRepository.getLiveMatches()
                _liveMatchesState.value = MatchState.Success(liveMatchesResponse)
            }
            catch (exception: HttpException) {
                _liveMatchesState.value = MatchState.Error("Something went wrong")
            }
            catch (exception: IOException) {
                _liveMatchesState.value = MatchState.Error("No internet connection")
            }
        }
    }
}

Solution

  • You can achieve this using a LaunchedEffect Composable:

    LaunchedEffect(Unit) {
        while (true) {
            liveMatchesViewModel.getAllLiveMatches()
            delay(20000)  // wait for 20 seconds
        }
    }
    

    This will re-execute the getAllLiveMatches function every 20 seconds. You need to make getAllLiveMatches a public function to make this work.

    With this approach, the refreshing will be cancelled when the Composable leaves the composition. You need to judge yourself if this is what you want.

    As an alternative, you can execute the same code in the ViewModel in a viewModelScope. The launch function returns a Job that you can cancel when the ViewModel is destroyed using the onCleared callback.

    // ...
    private var refreshingJob: Job? = null
    
    init {
        getAllLiveMatches()
    }
    
    private fun getAllLiveMatches() {
    
        if (refreshingJob != null) return
        refreshingJob = viewModelScope.launch(Dispatchers.IO) {
    
            while(true) {
    
                _liveMatchesState.value = MatchState.Loading
                try {
                    val liveMatchesResponse = liveMatchesRepository.getLiveMatches()
                    _liveMatchesState.value = MatchState.Success(liveMatchesResponse)
                } catch (exception: HttpException) {
                    _liveMatchesState.value = MatchState.Error("Something went wrong")
                } catch (exception: IOException) {
                    _liveMatchesState.value = MatchState.Error("No internet connection")
                }
    
                delay(20000)  // wait for 20 seconds
            }
        }
    }
    
    // This will be called when the ViewModel is going to be destroyed
    override fun onCleared() {
        super.onCleared()
        refreshingJob?.cancel()
    }
    

    This will refresh the data as long as the ViewModel lives, which might be different from the time that the Composable displaying the data is visible.