mirror of
https://github.com/msfjarvis/compose-lobsters
synced 2025-08-14 22:17:03 +05:30
refactor: hoist state out of UserProfile
Solves the issue of every pop of the backstack causing data to be re-fetched, but now has the issue of the data being stale for a few frames. Still better than the current state, so I'll take it.
This commit is contained in:
parent
5d65d1ea51
commit
0d3c08c10a
5 changed files with 76 additions and 34 deletions
|
@ -222,7 +222,6 @@ fun LobstersPostsScreen(
|
||||||
) { dest ->
|
) { dest ->
|
||||||
UserProfile(
|
UserProfile(
|
||||||
username = dest.username,
|
username = dest.username,
|
||||||
getProfile = viewModel::getUserProfile,
|
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
openUserProfile = { clawBackStack.add(User(it)) },
|
openUserProfile = { clawBackStack.add(User(it)) },
|
||||||
)
|
)
|
||||||
|
|
|
@ -161,17 +161,6 @@ constructor(
|
||||||
suspend fun getLinkMetadata(url: String) =
|
suspend fun getLinkMetadata(url: String) =
|
||||||
withContext(ioDispatcher) { linkMetadataRepository.getLinkMetadata(url) }
|
withContext(ioDispatcher) { linkMetadataRepository.getLinkMetadata(url) }
|
||||||
|
|
||||||
suspend fun getUserProfile(username: String) =
|
|
||||||
withContext(ioDispatcher) {
|
|
||||||
when (val result = api.getUser(username)) {
|
|
||||||
is Success -> result.value
|
|
||||||
is Failure.NetworkFailure -> throw result.error
|
|
||||||
is Failure.UnknownFailure -> throw result.error
|
|
||||||
is Failure.HttpFailure -> throw result.toError()
|
|
||||||
is Failure.ApiFailure -> throw IOException("API returned an invalid response")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun importPosts(input: InputStream) = dataTransferRepository.importPosts(input)
|
suspend fun importPosts(input: InputStream) = dataTransferRepository.importPosts(input)
|
||||||
|
|
||||||
suspend fun exportPostsAsJson(output: OutputStream) =
|
suspend fun exportPostsAsJson(output: OutputStream) =
|
||||||
|
|
|
@ -25,6 +25,8 @@ android {
|
||||||
namespace = "dev.msfjarvis.claw.common"
|
namespace = "dev.msfjarvis.claw.common"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
whetstone.addOns.compose = true
|
||||||
|
|
||||||
androidComponents { beforeVariants { (it as HasUnitTestBuilder).enableUnitTest = false } }
|
androidComponents { beforeVariants { (it as HasUnitTestBuilder).enableUnitTest = false } }
|
||||||
|
|
||||||
anvil { generateDaggerFactories.set(true) }
|
anvil { generateDaggerFactories.set(true) }
|
||||||
|
@ -40,6 +42,7 @@ dependencies {
|
||||||
api(libs.androidx.compose.ui)
|
api(libs.androidx.compose.ui)
|
||||||
api(libs.dagger)
|
api(libs.dagger)
|
||||||
api(libs.javax.inject)
|
api(libs.javax.inject)
|
||||||
|
api(projects.api)
|
||||||
api(projects.core)
|
api(projects.core)
|
||||||
api(projects.database.core)
|
api(projects.database.core)
|
||||||
api(projects.model)
|
api(projects.model)
|
||||||
|
|
|
@ -20,17 +20,14 @@ import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.produceState
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.text.LinkAnnotation
|
import androidx.compose.ui.text.LinkAnnotation
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.github.michaelbull.result.coroutines.runSuspendCatching
|
import com.deliveryhero.whetstone.compose.injectedViewModel
|
||||||
import com.github.michaelbull.result.fold
|
|
||||||
import dev.msfjarvis.claw.common.NetworkState
|
|
||||||
import dev.msfjarvis.claw.common.NetworkState.Error
|
import dev.msfjarvis.claw.common.NetworkState.Error
|
||||||
import dev.msfjarvis.claw.common.NetworkState.Loading
|
import dev.msfjarvis.claw.common.NetworkState.Loading
|
||||||
import dev.msfjarvis.claw.common.NetworkState.Success
|
import dev.msfjarvis.claw.common.NetworkState.Success
|
||||||
|
@ -44,35 +41,25 @@ import dev.msfjarvis.claw.model.User
|
||||||
@Composable
|
@Composable
|
||||||
fun UserProfile(
|
fun UserProfile(
|
||||||
username: String,
|
username: String,
|
||||||
getProfile: suspend (username: String) -> User,
|
|
||||||
openUserProfile: (String) -> Unit,
|
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
|
openUserProfile: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
viewModel: UserProfileViewModel = injectedViewModel(),
|
||||||
) {
|
) {
|
||||||
val user by
|
LaunchedEffect(username) { viewModel.loadProfile(username) }
|
||||||
produceState<NetworkState>(Loading) {
|
when (val state = viewModel.userProfile) {
|
||||||
runSuspendCatching { getProfile(username) }
|
|
||||||
.fold(
|
|
||||||
success = { profile -> value = Success(profile) },
|
|
||||||
failure = {
|
|
||||||
value = Error(error = it, description = "Failed to load profile for $username")
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
when (user) {
|
|
||||||
is Success<*> -> {
|
is Success<*> -> {
|
||||||
UserProfileInternal(
|
UserProfileInternal(
|
||||||
user = (user as Success<User>).data,
|
user = (state as Success<User>).data,
|
||||||
openUserProfile = openUserProfile,
|
openUserProfile = openUserProfile,
|
||||||
modifier = modifier.padding(contentPadding),
|
modifier = modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is Error -> {
|
is Error -> {
|
||||||
val error = user as Error
|
|
||||||
Box(modifier = Modifier.padding(contentPadding).fillMaxSize()) {
|
Box(modifier = Modifier.padding(contentPadding).fillMaxSize()) {
|
||||||
NetworkError(
|
NetworkError(
|
||||||
label = error.description,
|
label = state.description,
|
||||||
error = error.error,
|
error = state.error,
|
||||||
modifier = Modifier.align(Alignment.Center),
|
modifier = Modifier.align(Alignment.Center),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* Copyright © Harsh Shandilya.
|
||||||
|
* Use of this source code is governed by an MIT-style
|
||||||
|
* license that can be found in the LICENSE file or at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*/
|
||||||
|
package dev.msfjarvis.claw.common.user
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import com.deliveryhero.whetstone.app.ApplicationScope
|
||||||
|
import com.deliveryhero.whetstone.viewmodel.ContributesViewModel
|
||||||
|
import com.github.michaelbull.result.coroutines.runSuspendCatching
|
||||||
|
import com.github.michaelbull.result.fold
|
||||||
|
import com.slack.eithernet.ApiResult
|
||||||
|
import com.slack.eithernet.ApiResult.Failure
|
||||||
|
import com.squareup.anvil.annotations.optional.ForScope
|
||||||
|
import dev.msfjarvis.claw.api.LobstersApi
|
||||||
|
import dev.msfjarvis.claw.api.toError
|
||||||
|
import dev.msfjarvis.claw.common.NetworkState
|
||||||
|
import dev.msfjarvis.claw.common.NetworkState.Error
|
||||||
|
import dev.msfjarvis.claw.common.NetworkState.Loading
|
||||||
|
import dev.msfjarvis.claw.common.NetworkState.Success
|
||||||
|
import dev.msfjarvis.claw.core.injection.IODispatcher
|
||||||
|
import dev.msfjarvis.claw.model.User
|
||||||
|
import java.io.IOException
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@ContributesViewModel
|
||||||
|
class UserProfileViewModel
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val api: LobstersApi,
|
||||||
|
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
|
@ForScope(ApplicationScope::class) context: Context,
|
||||||
|
) : AndroidViewModel(context as Application) {
|
||||||
|
|
||||||
|
var userProfile by mutableStateOf<NetworkState>(Loading)
|
||||||
|
|
||||||
|
suspend fun loadProfile(username: String) {
|
||||||
|
userProfile =
|
||||||
|
runSuspendCatching<User> {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
|
when (val result = api.getUser(username)) {
|
||||||
|
is ApiResult.Success -> result.value
|
||||||
|
is Failure.NetworkFailure -> throw result.error
|
||||||
|
is Failure.UnknownFailure -> throw result.error
|
||||||
|
is Failure.HttpFailure -> throw result.toError()
|
||||||
|
is Failure.ApiFailure -> throw IOException("API returned an invalid response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fold(
|
||||||
|
success = { profile -> Success(profile) },
|
||||||
|
failure = { Error(error = it, description = "Failed to load profile for $username") },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue