Skip to content

Commit

Permalink
Merge pull request DroidKaigi#767 from DroidKaigi/fix-error-handring
Browse files Browse the repository at this point in the history
Fix error handring feature branch
  • Loading branch information
takahirom authored Sep 28, 2022
2 parents bc09557 + 5f08b72 commit 56ad640
Show file tree
Hide file tree
Showing 29 changed files with 371 additions and 79 deletions.
1 change: 1 addition & 0 deletions app-ios/Sources/ContributorFeature/ContributorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public let contributorReducer = Reducer<ContributorState, ContributorAction, Con
switch action {
case .refresh:
return .run { @MainActor subscriber in
try await environment.contributorsRepository.refresh()
for try await result: [Contributor] in environment.contributorsRepository.contributors().stream() {
await subscriber.send(
.refreshResponse(
Expand Down
1 change: 1 addition & 0 deletions app-ios/Sources/SponsorFeature/SponsorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public let sponsorReducer = Reducer<SponsorState, SponsorAction, SponsorEnvironm
case .refresh:
state.isLoading = true
return .run { @MainActor subscriber in
try await environment.sponsorsRepository.refresh()
for try await result: [Sponsor] in environment.sponsorsRepository.sponsors().stream() {
await subscriber.send(
.refreshResponse(
Expand Down
1 change: 1 addition & 0 deletions app-ios/Sources/StaffFeature/StaffView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public let staffReducer = Reducer<StaffState, StaffAction, StaffEnvironment> { s
switch action {
case .refresh:
return .run { @MainActor subscriber in
try await environment.staffRepository.refresh()
for try await result: [Staff] in environment.staffRepository.staff().stream() {
await subscriber.send(
.refreshResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,30 @@ package io.github.droidkaigi.confsched2022.data.contributors
import io.github.droidkaigi.confsched2022.model.Contributor
import io.github.droidkaigi.confsched2022.model.ContributorsRepository
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.callbackFlow

// TODO: Move to core-testing, once contributors server is created
public class DataContributorsRepository(
private val contributorsApi: ContributorsApi,
) : ContributorsRepository {
private val contributorsStateFlow =
MutableStateFlow<PersistentList<Contributor>>(persistentListOf())

override fun contributors(): Flow<PersistentList<Contributor>> {
return callbackFlow {
send(
contributorsApi
.contributors()
.toPersistentList()
)
awaitClose { }
contributorsStateFlow.collect {
send(it)
}
}
}

override suspend fun refresh() {
TODO("Not yet implemented")
contributorsStateFlow.value = contributorsApi
.contributors()
.toPersistentList()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@ public class FakeContributorsRepository : ContributorsRepository {
}

override suspend fun refresh() {
TODO("Not yet implemented")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,25 @@ package io.github.droidkaigi.confsched2022.data.sponsors
import io.github.droidkaigi.confsched2022.model.Sponsor
import io.github.droidkaigi.confsched2022.model.SponsorsRepository
import kotlinx.collections.immutable.PersistentList
import kotlinx.coroutines.channels.awaitClose
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.callbackFlow

public class DataSponsorsRepository(
private val sponsorsApi: SponsorsApi
) : SponsorsRepository {
private val sponsorsStateFlow =
MutableStateFlow<PersistentList<Sponsor>>(persistentListOf())
override fun sponsors(): Flow<PersistentList<Sponsor>> =
callbackFlow {
send(
sponsorsApi.sponsors()
)
awaitClose { }
sponsorsStateFlow.collect {
send(it)
}
}

override suspend fun refresh() {
sponsorsStateFlow.value = sponsorsApi
.sponsors()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ import kotlinx.coroutines.flow.flowOf

public class FakeSponsorsRepository : SponsorsRepository {
override fun sponsors(): Flow<PersistentList<Sponsor>> = flowOf(Sponsor.fakes())

override suspend fun refresh() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,29 @@ package io.github.droidkaigi.confsched2022.data.staff
import io.github.droidkaigi.confsched2022.model.Staff
import io.github.droidkaigi.confsched2022.model.StaffRepository
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.callbackFlow

public class DataStaffRepository(
private val staffApi: StaffApi
) : StaffRepository {
private val staffStateFlow =
MutableStateFlow<PersistentList<Staff>>(persistentListOf())

override fun staff(): Flow<PersistentList<Staff>> {
return callbackFlow {
send(
staffApi
.staff()
.toPersistentList()
)
awaitClose { }
staffStateFlow.collect {
send(it)
}
}
}

override suspend fun refresh() {
staffStateFlow.value = staffApi
.staff()
.toPersistentList()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ public class FakeStaffRepository : StaffRepository {
override fun staff(): Flow<PersistentList<Staff>> {
return flowOf(staff)
}

override suspend fun refresh() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ import kotlinx.coroutines.flow.Flow

public interface SponsorsRepository {
public fun sponsors(): Flow<PersistentList<Sponsor>>

public suspend fun refresh()
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ import kotlinx.coroutines.flow.Flow

public interface StaffRepository {
public fun staff(): Flow<PersistentList<Staff>>

public suspend fun refresh()
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.github.droidkaigi.confsched2022.feature.announcement
package io.github.droidkaigi.confsched2022.feature.common

import androidx.compose.material3.SnackbarDuration.Long
import androidx.compose.material3.SnackbarHostState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import io.github.droidkaigi.confsched2022.designsystem.components.KaigiScaffold
import io.github.droidkaigi.confsched2022.designsystem.components.KaigiTopAppBar
import io.github.droidkaigi.confsched2022.designsystem.theme.KaigiColors
import io.github.droidkaigi.confsched2022.designsystem.theme.KaigiTheme
import io.github.droidkaigi.confsched2022.feature.common.AppErrorSnackbarEffect
import io.github.droidkaigi.confsched2022.model.AnnouncementsByDate
import io.github.droidkaigi.confsched2022.model.fakes
import io.github.droidkaigi.confsched2022.strings.Strings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
Expand All @@ -19,6 +21,7 @@ import io.github.droidkaigi.confsched2022.designsystem.components.KaigiScaffold
import io.github.droidkaigi.confsched2022.designsystem.components.KaigiTopAppBar
import io.github.droidkaigi.confsched2022.designsystem.components.UsernameRow
import io.github.droidkaigi.confsched2022.designsystem.theme.KaigiTheme
import io.github.droidkaigi.confsched2022.feature.common.AppErrorSnackbarEffect
import io.github.droidkaigi.confsched2022.model.Contributor
import io.github.droidkaigi.confsched2022.model.fakes
import io.github.droidkaigi.confsched2022.strings.Strings
Expand All @@ -38,6 +41,8 @@ fun ContributorsScreenRoot(
uiModel = uiModel,
showNavigationIcon = showNavigationIcon,
onNavigationIconClick = onNavigationIconClick,
onRetryButtonClick = { viewModel.onRetryButtonClick() },
onAppErrorNotified = { viewModel.onAppErrorNotified() },
onLinkClick = onLinkClick
)
}
Expand All @@ -47,10 +52,15 @@ fun Contributors(
uiModel: ContributorsUiModel,
showNavigationIcon: Boolean,
onNavigationIconClick: () -> Unit,
onRetryButtonClick: () -> Unit,
onAppErrorNotified: () -> Unit,
onLinkClick: (url: String, packageName: String?) -> Unit,
modifier: Modifier = Modifier,
) {
val snackbarHostState = remember { SnackbarHostState() }

KaigiScaffold(
snackbarHostState = snackbarHostState,
modifier = modifier,
topBar = {
KaigiTopAppBar(
Expand All @@ -64,9 +74,17 @@ fun Contributors(
)
}
) { innerPadding ->
AppErrorSnackbarEffect(
appError = uiModel.appError,
snackBarHostState = snackbarHostState,
onAppErrorNotified = onAppErrorNotified,
onRetryButtonClick = onRetryButtonClick
)
Box {
when (uiModel.state) {
is Error -> TODO()
is Error -> {
// Do nothing
}
Loading -> Box(
modifier = Modifier.padding(innerPadding).fillMaxSize(),
contentAlignment = Alignment.Center,
Expand Down Expand Up @@ -103,10 +121,13 @@ fun ContributorsPreview() {
uiModel = ContributorsUiModel(
state = Success(
Contributor.fakes()
)
),
appError = null,
),
showNavigationIcon = true,
onNavigationIconClick = {},
onRetryButtonClick = {},
onAppErrorNotified = {},
onLinkClick = { _, _ -> },
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package io.github.droidkaigi.confsched2022.feature.contributors

import io.github.droidkaigi.confsched2022.model.AppError
import io.github.droidkaigi.confsched2022.model.Contributor
import io.github.droidkaigi.confsched2022.ui.UiLoadState
import kotlinx.collections.immutable.PersistentList

data class ContributorsUiModel(val state: UiLoadState<PersistentList<Contributor>>)
data class ContributorsUiModel(
val state: UiLoadState<PersistentList<Contributor>>,
val appError: AppError?
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,64 @@ package io.github.droidkaigi.confsched2022.feature.contributors
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.cash.molecule.AndroidUiDispatcher
import app.cash.molecule.RecompositionClock.ContextClock
import dagger.hilt.android.lifecycle.HiltViewModel
import io.github.droidkaigi.confsched2022.model.AppError
import io.github.droidkaigi.confsched2022.model.ContributorsRepository
import io.github.droidkaigi.confsched2022.ui.UiLoadState
import io.github.droidkaigi.confsched2022.ui.asLoadState
import io.github.droidkaigi.confsched2022.ui.moleculeComposeState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class ContributorsViewModel @Inject constructor(
contributorsRepository: ContributorsRepository,
private val contributorsRepository: ContributorsRepository,
) : ViewModel() {
private val moleculeScope =
CoroutineScope(viewModelScope.coroutineContext + AndroidUiDispatcher.Main)

val uiModel: State<ContributorsUiModel>
private val contributorsFlow = contributorsRepository
.contributors()
.asLoadState()

private var appError by mutableStateOf<AppError?>(null)

val uiModel: State<ContributorsUiModel> = moleculeScope.moleculeComposeState(
clock = ContextClock
) {
val contributorLoadState by contributorsFlow.collectAsState(initial = UiLoadState.Loading)
ContributorsUiModel(
state = contributorLoadState,
appError = appError
)
}

init {
val dataFlow = contributorsRepository.contributors().asLoadState()
refresh()
}

uiModel = moleculeScope.moleculeComposeState(clock = ContextClock) {
val data by dataFlow.collectAsState(initial = UiLoadState.Loading)
ContributorsUiModel(data)
fun onRetryButtonClick() {
refresh()
}

private fun refresh() {
viewModelScope.launch {
try {
contributorsRepository.refresh()
} catch (e: AppError) {
appError = e
}
}
}

fun onAppErrorNotified() {
appError = null
}
}
Loading

0 comments on commit 56ad640

Please sign in to comment.