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;
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.