Search code examples
androidkotlinretrofitkotlin-coroutines

Android Kotlin Coroutines - Call Api Every X Seconds on Background


I've build clean architectured app (with mvvm, use cases, compose). I've a CoinListViewModel for list all crypto coins by using CoinPaprika API. It is like;

@HiltViewModel
class CoinListViewModel @Inject constructor(
    private val getCoinsUseCase: GetCoinsUseCase
) : ViewModel() {

    private val _state = mutableStateOf(CoinListState())
    val state: State<CoinListState> = _state

    init {
        getCoins()
    }

    private fun getCoins() {
        getCoinsUseCase().onEach { result ->
            when (result) {
                is Resource.Success -> {
                    _state.value = CoinListState(coins = result.data ?: emptyList())
                }
                is Resource.Error -> {
                    _state.value = CoinListState(
                        error = result.message ?: "An unexpected error occured"
                    )
                }
                is Resource.Loading -> {
                    _state.value = CoinListState(isLoading = true)
                }
            }
        }.launchIn(viewModelScope)
    }
}

And this viewmodel is used in my CoinListScreen like;


@Composable
fun CoinListScreen(
    navController: NavController,
    viewModel: CoinListViewModel = hiltViewModel()
) {
    val state = viewModel.state.value
    Box(modifier = Modifier.fillMaxSize()) {
        LazyColumn(modifier = Modifier.fillMaxSize()) {
            items(state.coins) { coin ->
                CoinListItem(
                    coin = coin,
                    onItemClick = {
                        navController.navigate(Screen.CoinDetailScreen.route + "/${coin.id}")
                    }
                )
            }
        }
        if(state.error.isNotBlank()) {
            Text(
                text = state.error,
                color = MaterialTheme.colors.error,
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 20.dp)
                    .align(Alignment.Center)
            )
        }
        if(state.isLoading) {
            CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
        }
    }
}

And that is my GetCoinsUseCase:

class GetCoinsUseCase @Inject constructor(
    private val repository: CoinRepository
) {
    operator fun invoke(): Flow<Resource<List<Coin>>> = flow {
        try {
            emit(Resource.Loading<List<Coin>>())
            val coins = repository.getCoins().map { it.toCoin() }
            emit(Resource.Success<List<Coin>>(coins))

        } catch(e: HttpException) {
            emit(Resource.Error<List<Coin>>(e.localizedMessage ?: "An unexpected error occured"))
        } catch(e: IOException){
            emit(Resource.Error<List<Coin>>("Couldn't reach to server. Check your internet connection."))
        }
    }
}

I have 2 questions;

  1. How can I make this API call every 3 seconds ?
  2. How can I continue to do API call on background in phone ?

Solution

  • You could add a loop with a delay in your use case:

    class GetCoinsUseCase @Inject constructor(
        private val repository: CoinRepository
    ) {
        operator fun invoke(): Flow<Resource<List<Coin>>> = flow {
            while (currentCoroutineContext().isActive) {
                try {
                    emit(Resource.Loading<List<Coin>>())
                    val coins = repository.getCoins().map { it.toCoin() }
                    emit(Resource.Success<List<Coin>>(coins))
    
                } catch(e: HttpException) {
                    emit(Resource.Error<List<Coin>>(e.localizedMessage ?: "An unexpected error occured"))
                } catch(e: IOException){
                   emit(Resource.Error<List<Coin>>("Couldn't reach to server. Check your internet connection."))
                }
    
                // Wait until next request
                delay(3000)
            }
        }
    }
    

    You could also move the loop and delay call around depending on the precise functionality you are looking for. For example, to stop once there is an error you could do

    class GetCoinsUseCase @Inject constructor(
        private val repository: CoinRepository
    ) {
        operator fun invoke(): Flow<Resource<List<Coin>>> = flow {
            emit(Resource.Loading<List<Coin>>())
            try {
                while (currentCoroutineContext().isActive) {
                    val coins = repository.getCoins().map { it.toCoin() }
                    emit(Resource.Success<List<Coin>>(coins))
    
                    // Wait until next request
                    delay(3000)
                }
            } catch(e: HttpException) {
                emit(Resource.Error<List<Coin>>(e.localizedMessage ?: "An unexpected error occured"))
            } catch(e: IOException){
                emit(Resource.Error<List<Coin>>("Couldn't reach to server. Check your internet connection."))
            }
        }
    }
    

    This should already naturally "run in the background" as long as your app is running and you are not cancelling the coroutine manually when going in the background.