fix(android): surface concert-fetch errors instead of swallowing them
Events were fetched with runCatching{}.getOrDefault(emptyList()), so any
network/deserialization failure looked identical to "no concerts". The
backend + Ticketmaster work (verified), so the failure is client-side.
- EventsClient: expectSuccess + timeouts so a non-2xx (e.g. an SSO/HTML
redirect) becomes a real exception instead of an obscure body() failure.
- ArtistViewModel: capture the failure into eventsError (+ Logcat) rather
than defaulting to empty.
- ConcertsSheet: show "Konzerte konnten nicht geladen werden" + the reason
when the fetch failed, vs. "Keine Konzerte angekündigt" when genuinely none.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
7b4599c50a
commit
f27eaf9c82
4 files changed
+29
-9
No files matched your search
@@ -4,6 +4,7 @@ import com.novasonic.app.api.model.ConcertEvent
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.engine.android.*
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
@@ -12,6 +13,10 @@ import kotlinx.serialization.json.Json
|
||||
object EventsClient {
|
||||
|
||||
private val http = HttpClient(Android) {
|
||||
// Surface non-2xx as a real exception (e.g. an SSO redirect / HTML login page) instead of
|
||||
// letting .body() fail obscurely — the ViewModel turns it into a visible "load failed".
|
||||
expectSuccess = true
|
||||
install(HttpTimeout) { requestTimeoutMillis = 15_000; connectTimeoutMillis = 10_000 }
|
||||
install(ContentNegotiation) {
|
||||
json(Json { ignoreUnknownKeys = true; coerceInputValues = true })
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ fun ConcertsSheet(
|
||||
artistName: String,
|
||||
events: List<ConcertEvent>,
|
||||
onDismiss: () -> Unit,
|
||||
eventsError: String? = null,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
@@ -47,17 +48,25 @@ fun ConcertsSheet(
|
||||
)
|
||||
|
||||
if (events.isEmpty()) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 40.dp),
|
||||
.padding(horizontal = 20.dp, vertical = 40.dp),
|
||||
) {
|
||||
Text(
|
||||
"Keine Konzerte angekündigt",
|
||||
if (eventsError != null) "Konzerte konnten nicht geladen werden" else "Keine Konzerte angekündigt",
|
||||
fontFamily = Outfit, fontSize = 14.sp,
|
||||
color = NsOnSurface2,
|
||||
)
|
||||
if (eventsError != null) {
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Text(
|
||||
eventsError,
|
||||
fontFamily = Outfit, fontSize = 11.sp,
|
||||
color = NsOnSurface2.copy(alpha = 0.6f),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
|
||||
@@ -92,9 +92,10 @@ fun ArtistDetailScreen(
|
||||
val artist = state.artist
|
||||
if (artist != null) {
|
||||
ConcertsSheet(
|
||||
artistName = artist.name,
|
||||
events = state.events,
|
||||
onDismiss = vm::dismissEventsSheet,
|
||||
artistName = artist.name,
|
||||
events = state.events,
|
||||
onDismiss = vm::dismissEventsSheet,
|
||||
eventsError = state.eventsError,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ data class ArtistUiState(
|
||||
val partialAlbum: PartialAlbumState? = null,
|
||||
// Concerts
|
||||
val events: List<ConcertEvent> = emptyList(),
|
||||
val eventsError: String? = null, // non-null → the fetch failed (vs. genuinely no concerts)
|
||||
val showEventsSheet: Boolean = false,
|
||||
// Deezer fan count of the matched artist (shown under the name), null if unknown
|
||||
val fanCount: Long? = null,
|
||||
@@ -113,8 +114,12 @@ class ArtistViewModel(app: Application, savedState: SavedStateHandle) : AndroidV
|
||||
launch { loadItunesData(artist.name, pin) }
|
||||
launch {
|
||||
val backendUrl = repo.getBackendUrl()
|
||||
val ev = runCatching { EventsClient.fetchEvents(backendUrl, artist.name) }.getOrDefault(emptyList())
|
||||
_state.value = _state.value.copy(events = ev)
|
||||
runCatching { EventsClient.fetchEvents(backendUrl, artist.name) }
|
||||
.onSuccess { _state.value = _state.value.copy(events = it, eventsError = null) }
|
||||
.onFailure { e ->
|
||||
android.util.Log.w("NovaSonic", "Konzert-Fetch fehlgeschlagen für ${artist.name}", e)
|
||||
_state.value = _state.value.copy(events = emptyList(), eventsError = e.message ?: e.toString())
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_state.value = ArtistUiState(isLoading = false, itunesLoading = false, error = e.message ?: e.toString())
|
||||
|
||||
Reference in new issue
Block a user