Ryan Simon
06/11/2020, 12:51 AMStateFlow<State>
setup to track state changes of a screen in Android. these state changes are observed by the View
.
additionally, i use a Channel<Intent>
to offer
some Intent
from the View
to the ViewModel
. the problem is that my code will suspend and it feels like the main thread is blocked until an Intent
is fully processed by the ViewModel
.
i've added some of my code below to hopefully help with understanding the problem
// ViewModel
val intentChannel = Channel<Intent>(Channel.UNLIMITED)
private val _state = MutableStateFlow<State>(Idle)
val state: StateFlow<State>
get() = _state
init {
viewModelScope.launch {
handleIntents()
}
}
// TODO: we seem to suspend the main thread every time we process an intent; NOT GOOD!
private suspend fun handleIntents() {
intentChannel.consumeAsFlow().collect {
when (it) {
is Init -> with(it) {
// do something on initialization
}
is Load -> with(it) {
_state.value = Loading(page == 0)
getBrandProducts(page = page)
}
is Sort -> with(it) {
_state.value = Sorting
getBrandProducts(page = 0)
}
}
}
}
// brandRepository flows on a <http://Dispatcher.IO|Dispatcher.IO>
private suspend fun getBrandProducts(page: Int) {
brandRepository.getPromotedProducts(
categorySlug = categorySlug,
sortBy = currentSortBy,
sortOrder = currentSortOrder,
offset = page * requestLimit,
limit = requestLimit
)
.map { result -> result.getOrNull()!! }
.collect { result -> _state.value = Loaded(result) }
}
Zach Klippenstein (he/him) [MOD]
06/11/2020, 12:53 AMmy code will suspend and block the main thread“suspend” and “block” mean different things. Blocking the main thread is (generally) bad. Suspending a coroutine that’s running on the main thread is perfectly fine.
Ryan Simon
06/11/2020, 12:53 AMLoad
intent with a new page, the main thread hangs as the user scrolls and delays the scrolling animation until the work is doneZach Klippenstein (he/him) [MOD]
06/11/2020, 12:56 AMgetPromotedProducts
isn’t the culprit?Ryan Simon
06/11/2020, 12:57 AMfun getPromotedProducts(
categorySlug: String,
sortBy: String?,
sortOrder: String?,
offset: Int,
limit: Int
): Flow<Either<List<BrandProduct>>> {
// TODO need to solve for retry/error handling for Retrofit
return flow {
val userLocation = locationRepository.getLocation().receive().getOrNull()
try {
val result = userLocation?.run {
api.getPromotedProducts(
categorySlug = categorySlug,
latlng = "${this.latitude},${this.longitude}",
sortBy = sortBy,
sortOrder = sortOrder,
offset = offset,
limit = limit
).run { success(this.data?.brandProducts!!) }
} ?: Either.error<List<BrandProduct>>(ServerError)
emit(result)
} catch (e: Exception) {
when (e) {
is HttpException -> {
Timber.d("HttpError ${e.response()?.errorBody()?.string()}")
}
}
emit(Either.error(ServerError))
}
}.flowOn(dispatcher)
}
Zach Klippenstein (he/him) [MOD]
06/11/2020, 12:57 AMconsumeAsFlow().collect
is unnecessary, you can just do consumeEach
.Ryan Simon
06/11/2020, 12:57 AM<http://Dispatchers.IO|Dispatchers.IO>
Zach Klippenstein (he/him) [MOD]
06/11/2020, 1:04 AMdispatcher
here is <http://Dispatchers.IO|Dispatchers.IO>
?
(in the future, big code posts like this are generally easier to read when posted as snippets instead of inline)Ryan Simon
06/11/2020, 1:04 AM<http://Dispatchers.IO|Dispatchers.IO>
Zach Klippenstein (he/him) [MOD]
06/11/2020, 1:05 AMgetPromotedProducts
isn’t even returning immediately?Ryan Simon
06/11/2020, 1:06 AMZach Klippenstein (he/him) [MOD]
06/11/2020, 1:07 AMRyan Simon
06/11/2020, 1:09 AMZach Klippenstein (he/him) [MOD]
06/11/2020, 1:10 AMLoaded
while you’re scrolling, and Loaded
doesn’t include any items, does your RecyclerView keep its current list until it gets a new one from a Loaded
state?Ryan Simon
06/11/2020, 1:12 AMZach Klippenstein (he/him) [MOD]
06/11/2020, 1:13 AMRyan Simon
06/11/2020, 1:14 AMZach Klippenstein (he/him) [MOD]
06/11/2020, 1:16 AM_state.value = Loading(page == 0)
line, does that change anything?Ryan Simon
06/11/2020, 1:17 AMZach Klippenstein (he/him) [MOD]
06/11/2020, 1:18 AMwithContext(<http://Dispatchers.IO|Dispatchers.IO>) { }
around your entire getBrandProducts
method body?Ryan Simon
06/11/2020, 1:19 AMZach Klippenstein (he/him) [MOD]
06/11/2020, 1:27 AMRyan Simon
06/11/2020, 1:29 AMdelay(1000)
to the network request Flow
we make to fetch items, and the other video has no delay
the video with no delay
has the very obvious frame drops, and the one with a delay(1000)
doesn't