Skip to content

Commit

Permalink
Resolve error message display (#1037)
Browse files Browse the repository at this point in the history
* Resolve error message display

* Invalidate data source on update

* Fix content padding on container

* Remove trailing space

---------

Co-authored-by: Ashley Davies <[email protected]>
  • Loading branch information
ashdavies and ashdavies authored Jun 23, 2024
1 parent 5368304 commit 6fb091c
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.ashdavies.activity

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand Down Expand Up @@ -35,7 +34,6 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.paging.LoadState
import com.slack.circuit.runtime.CircuitUiState
import com.slack.circuit.runtime.screen.Screen
import io.ashdavies.android.fade
Expand All @@ -44,14 +42,6 @@ import io.ashdavies.paging.LazyPagingItems
import io.ashdavies.parcelable.Parcelable
import io.ashdavies.parcelable.Parcelize

private val <T : Any> LazyPagingItems<T>.errorMessage: String?
get() = (loadState.append as? LoadState.Error)
?.error
?.message

private val <T : Any> LazyPagingItems<T>.isRefreshing: Boolean
get() = loadState.refresh is LoadState.Loading

@Parcelize
internal object ActivityScreen : Parcelable, Screen {
data class State(val pagingItems: LazyPagingItems<Event>) : CircuitUiState
Expand All @@ -67,26 +57,26 @@ internal fun ActivityScreen(state: ActivityScreen.State, modifier: Modifier = Mo
topBar = { ActivityTopAppBar("Events") },
) { contentPadding ->
val pullRefreshState = rememberPullRefreshState(
refreshing = state.pagingItems.isRefreshing,
refreshing = state.pagingItems.loadState.isRefreshing,
onRefresh = { state.pagingItems.refresh() },
)

Box(modifier = Modifier.pullRefresh(pullRefreshState)) {
val errorMessage = state.pagingItems.errorMessage
if (errorMessage != null) {
EventFailure(errorMessage)
Box(
modifier = Modifier
.padding(contentPadding)
.pullRefresh(pullRefreshState),
) {
if (state.pagingItems.loadState.hasError) {
EventFailure(state.pagingItems.loadState.errorMessage ?: "Unknown Error")
}

PullRefreshIndicator(
modifier = Modifier.align(Alignment.TopCenter),
refreshing = state.pagingItems.isRefreshing,
refreshing = state.pagingItems.loadState.isRefreshing,
state = pullRefreshState,
)

LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = contentPadding,
) {
LazyColumn(Modifier.fillMaxSize()) {
items(state.pagingItems.itemCount) {
EventSection(state.pagingItems[it])
}
Expand Down Expand Up @@ -181,12 +171,9 @@ internal fun PlaceholderText(

@Composable
private fun EventFailure(message: String, modifier: Modifier = Modifier) {
Column(verticalArrangement = Arrangement.Center) {
Row(horizontalArrangement = Arrangement.Center) {
Text(
modifier = modifier.padding(16.dp, 12.dp),
text = message,
)
}
}
Text(
text = message,
modifier = modifier.padding(16.dp, 12.dp),
color = MaterialTheme.colorScheme.error,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.ashdavies.activity

import androidx.paging.CombinedLoadStates
import androidx.paging.LoadState
import androidx.paging.LoadStates

private val CombinedLoadStates.list: List<LoadStates>
get() = listOfNotNull(source, mediator)

private val LoadStates.list: List<LoadState>
get() = listOf(refresh, prepend, append)

internal val CombinedLoadStates.errorMessage: String?
get() = firstNotNullOfOrNull { it as? LoadState.Error }?.error?.message

internal val CombinedLoadStates.isRefreshing: Boolean
get() = refresh is LoadState.Loading

private fun <T> CombinedLoadStates.firstNotNullOfOrNull(predicate: (LoadState) -> T): T? {
return list.firstNotNullOfOrNull { it.list.firstNotNullOfOrNull(predicate) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.ashdavies.events
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.paging.ExperimentalPagingApi
import androidx.paging.InvalidatingPagingSourceFactory
import androidx.paging.Pager
import androidx.paging.PagingConfig
import io.ashdavies.http.LocalHttpClient
Expand All @@ -19,10 +20,20 @@ internal fun rememberEventPager(
initialKey: String = todayAsString(),
pageSize: Int = DEFAULT_PAGE_SIZE,
): Pager<String, Event> = remember(eventsQueries, eventsCallable) {
val pagingSourceFactory = InvalidatingPagingSourceFactory {
EventsPagingSource(eventsQueries)
}

val remoteMediator = EventsRemoteMediator(
eventsQueries = eventsQueries,
eventsCallable = eventsCallable,
onInvalidate = pagingSourceFactory::invalidate,
)

Pager(
config = PagingConfig(pageSize),
initialKey = initialKey,
remoteMediator = EventsRemoteMediator(eventsQueries, eventsCallable),
pagingSourceFactory = { EventsPagingSource(eventsQueries) },
remoteMediator = remoteMediator,
pagingSourceFactory = pagingSourceFactory,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,42 @@ import io.ashdavies.http.common.models.Event as ApiEvent
internal class EventsRemoteMediator(
private val eventsQueries: EventsQueries,
private val eventsCallable: GetEventsCallable,
private val onInvalidate: () -> Unit,
) : RemoteMediator<String, DatabaseEvent>() {

@Suppress("ReturnCount")
override suspend fun load(
loadType: LoadType,
state: PagingState<String, DatabaseEvent>,
): MediatorResult = when (loadType) {
LoadType.PREPEND -> endOfPaginationReached()
else -> when (val lastItem = state.lastItemOrNull()) {
is DatabaseEvent -> load(loadType, lastItem.id)
else -> endOfPaginationReached()
): MediatorResult {
val loadKey = when (loadType) {
LoadType.APPEND -> state.lastItemOrNull() ?: return endOfPaginationReached()
LoadType.PREPEND -> return endOfPaginationReached()
LoadType.REFRESH -> null
}
}

private suspend fun load(
loadType: LoadType,
startAt: String?,
): MediatorResult = when (val result = eventsCallable.result(GetEventsRequest(startAt))) {
is CallableResult.Error<*> -> MediatorResult.Error(result.throwable)
is CallableResult.Success -> {
eventsQueries.transaction {
if (loadType == LoadType.REFRESH) eventsQueries.deleteAll()
return when (val result = eventsCallable.result(GetEventsRequest(loadKey?.dateStart))) {
is CallableResult.Error<*> -> MediatorResult.Error(result.throwable)
is CallableResult.Success -> {
eventsQueries.transaction {
if (loadType == LoadType.REFRESH) eventsQueries.deleteAll()

result.value.forEach {
eventsQueries.insertOrReplace(it.asDatabaseEvent())
result.value.forEach {
eventsQueries.insertOrReplace(it.asDatabaseEvent())
}
}
}

MediatorResult.Success(result.value.isEmpty())
onInvalidate()

MediatorResult.Success(result.value.isEmpty())
}
}
}
}

private fun endOfPaginationReached(): MediatorResult {
return MediatorResult.Success(endOfPaginationReached = true)
}
@ExperimentalPagingApi
private fun endOfPaginationReached(): RemoteMediator.MediatorResult {
return RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
}

private suspend fun GetEventsCallable.result(
Expand Down
4 changes: 3 additions & 1 deletion fused-properties/src/main/kotlin/StringProperty.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import kotlin.properties.ReadOnlyProperty
public fun interface ReadOnlyDelegateProvider<T> : PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, T>>

private fun Project.stringPropertyProvider(propertyName: String): Provider<String> {
val localPropertiesProvider = rootProject.cachedLocalPropertiesProvider()
val rootPropertiesProvider = rootProject.cachedLocalPropertiesProvider()
val localPropertiesProvider = cachedLocalPropertiesProvider()
val startPropertiesProvider = startParameterProvider()

val propertyNameParts = propertyName.split(Regex("(?=[A-Z])"))
Expand All @@ -16,6 +17,7 @@ private fun Project.stringPropertyProvider(propertyName: String): Provider<Strin

return startPropertiesProvider.mapOrNull { it[gradlePropertyName] }
.orElse(localPropertiesProvider.map { it.getProperty(gradlePropertyName) })
.orElse(rootPropertiesProvider.map { it.getProperty(gradlePropertyName) })
.orElse(providers.gradleProperty(gradlePropertyName))
.orElse(providers.environmentVariable(envPropertyName))
}
Expand Down

0 comments on commit 6fb091c

Please sign in to comment.