Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve error message display #1037

Merged
merged 4 commits into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading