-
Notifications
You must be signed in to change notification settings - Fork 81
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
Add recipes/samples module showing common patterns and gotchas #33
Comments
E.g., val stuff by foo.bar().collectAsState() will invoke |
Non-main thread and/or non- |
I have a WIP lint detector for Edit: Structurally this is a bit difficult. It's simple to detect the issue when the @Composable fun createCoolUiState(repo: Repository): CoolUiState {
val data by repo.getDataFlow().collectAsState() // report the issue here!
return CoolUiState(title = data.title)
} but it doesn't make as much sense when the Flow is passed to another composable: @Composable fun createCoolUiState(repo: Repository): CoolUiState {
val dataFlow = repo.getDataFlow()
return CoolUiState(title = title(dataFlow))
}
@Composable fun title(dataFlow: Flow<Data>): String {
val data by dataFlow.collectAsState()
// it doesn't make much sense to report the issue here,
// since `remember`ing the Flow here won't help anything
return CoolUiState(title = data.title)
} In practice, I've been hoisting the @Composable fun createCoolUiState(repo: Repository): CoolUiState {
val dataFlow = repo.getDataFlow() // report the issue here!
return CoolUiState(title = title(dataFlow))
}
@Composable fun title(dataFlow: Flow<Data>): String {
val data by dataFlow.collectAsState()
return CoolUiState(title = data.title)
} but that seems potentially heavy-handed. |
I wonder how to generally manage more complex tasks involving nullability and the general problem that it's quite a ceremony to bridge a suspend function to a compose state. To evluate this as a general architecture pattern, I've rewritten some code to compose, while trying to maintain the original logics: Original Flow fun diaryProFlow(): StateFlow<DiaryProCard?> = userRepo.flow()
.flatMapLatest { user ->
if (user?.isPro == true) return@flatMapLatest flowOf(null)
val purchaseItem = getPurchaseItem() ?: return@flatMapLatest flowOf(null)
offerCardViewState(purchaseItem)
}
.map<PurchaseOfferCardViewState?, DiaryProCard?> { cardViewState ->
when {
cardViewState == null || cardViewState.countdown <= Duration.ZERO -> null
else -> DiaryProCard.Offer(cardViewState)
}
}
.stateIn(scope, SharingStarted.Eagerly, DiaryProCard.Loading) Logics
Compose sealed interface Task<out T> {
object Loading : Task<Nothing>
data class Content<T>(val content: T) : Task<T>
}
@Composable
fun diaryProCardComposable(): DiaryProCard? {
val user = remember { userRepo.flow() }.collectAsState(initial = null).value
?: return DiaryProCard.Loading
if (user.isPro) {
return null
}
val purchaseItemLoadingState = produceState<Task<PurchaseItemBundle?>>(
initialValue = Task.Loading,
producer = {
value = Task.Content(getPurchaseItem())
},
).value
val purchaseItem = if (purchaseItemLoadingState is Task.Content) {
purchaseItemLoadingState.content
} else {
return DiaryProCard.Loading
}
purchaseItem ?: return null
val offerTask = remember(purchaseItem) {
offerCardViewState(purchaseItem).map {
Task.Content(it)
}
}.collectAsState(initial = Task.Loading).value
val cardViewState = if (offerTask is Task.Content) {
offerTask.content
} else {
return DiaryProCard.Loading
}
return if (cardViewState == null || cardViewState.countdown <= Duration.ZERO) {
null
} else {
DiaryProCard.Offer(cardViewState)
}
} ThoughtsI wonder, at which point this will scale and what would happen to the code base if we would apply compose absolutely everywhere. It would probably introduce lots of helpers, around the Task and produce state. The sample in the readme alread ignores lots of complexity: @Composable
fun ProfilePresenter(
userFlow: Flow<User>,
balanceFlow: Flow<Long>,
): ProfileModel {
val user by userFlow.collectAsState(null)
val balance by balanceFlow.collectAsState(0L)
return if (user == null) {
Loading
} else {
Data(user.name, balance)
}
} What if the So to fix it, the balance would also need to be mapped to some form of a Generic LoadingState. Or have fallbacks with an initial null value. But that only works as long as the original type is not nullable as well. Doing the So I wonder: |
No description provided.
The text was updated successfully, but these errors were encountered: