Nacho Ruiz Martin
11/01/2023, 10:11 PMArkadii Ivanov
11/01/2023, 10:25 PMnavigation-compose
, the previous composable leaves the composition, and then enters again. So all remember
-ed objects are lost.Nacho Ruiz Martin
11/02/2023, 11:48 AMArkadii Ivanov
11/02/2023, 11:51 AMNacho Ruiz Martin
11/02/2023, 11:51 AMArkadii Ivanov
11/02/2023, 11:52 AMNacho Ruiz Martin
11/02/2023, 11:54 AMArkadii Ivanov
11/02/2023, 11:55 AMNacho Ruiz Martin
11/02/2023, 11:55 AMabstract class CompleteBloc<Intent : Any, State : Any, Event : Any, Label : Any>(
mainContext: CoroutineContext,
componentContext: ComponentContext,
storeFactory: () -> Store<Intent, State, Label>,
) : ComponentContext by componentContext,
Bloc<Intent, State, Event> {
protected val store = componentContext.instanceKeeper.getStore(storeFactory)
override val states = store.stateFlow
private val _events = MutableSharedFlow<Event>()
override val events = _events
private val scope = coroutineScope(mainContext + SupervisorJob())
The Store is a normal MviKotlin store and the Compose is:
@Composable
fun FeedScreen(
bloc: FeedBloc,
showSnackbar: (String) -> Unit,
) {
val state by bloc.states.collectAsStateWithLifecycle()
val feedItems = bloc.feedFlow.collectAsLazyPagingItems()
Let me know if this is everything you need.In the original case, there is at least some data visible immediately.Oh, you are right. Same thing is happening there.
Arkadii Ivanov
11/02/2023, 11:56 AMbloc.states.collectAsStateWithLifecycle
being used.collectAsState
?Nacho Ruiz Martin
11/02/2023, 11:58 AMArkadii Ivanov
11/02/2023, 11:58 AMNacho Ruiz Martin
11/02/2023, 11:58 AMcollectAsState
. This is Compose Multiplatform, btw.Arkadii Ivanov
11/02/2023, 11:59 AMlifecycle.doOnDestroy { println("Destroyed: $this" }
and make sure that the component is not destroyed for any reason when moved to the back stack.
2. Try collecting the flow on Main.immediate
dispatcher.Nacho Ruiz Martin
11/02/2023, 12:00 PMMain.immediate
👍 . Let me debug the destruction of the component.Arkadii Ivanov
11/02/2023, 12:03 PMbloc.feedFlow.collectAsLazyPagingItems()
? Not sure what is it.Nacho Ruiz Martin
11/02/2023, 12:04 PMArkadii Ivanov
11/02/2023, 12:18 PMNacho Ruiz Martin
11/02/2023, 12:21 PMArkadii Ivanov
11/02/2023, 12:25 PMNacho Ruiz Martin
11/02/2023, 12:26 PMstates
flow being exposed from the Bloc
is just:
store.stateFlow
(I’m not doing any mapping between store and component model)
The state is storing some PagingData<T>
in the state and the thing is that collectAsLazyPagingItems
is an extension of Flow<PagingData<T>>
. So I need to expose also the flow of paging data directly to the Composable. That’s why the Bloc has two flows exposed:
val states = store.stateFlow //Flow<State>
val feedFlow = store.stateFlow.map { it.feed } // Flow<PagingData<T>>
The Composable is then collecting both (it’s the same flow but the second one is just a map):
val state by bloc.states.collectAsState(Dispatchers.Main.immediate)
val feedItems = bloc.feedFlow.collectAsLazyPagingItems(Dispatchers.Main.immediate)
This was working perfectly when I wasn’t using Decompose components. But now this causes something weird with the recomposition because if I comment out the first line and I only observe the feed items it works as expected.Arkadii Ivanov
11/02/2023, 12:40 PMdata class State(
val items: List<Item>,
val isLoadingMore: Boolean,
)
sealed class Intent {
object LoadMore : Intent()
}
Then in your Executor, when you receive LoadMore
, just load the next page something like this:
if (state.isLoadingMore) {
return
}
dispatch(Msg.LoadingMoreStarted)
val items = repository.loadMore(offset = state.items.size())
dispatch(Msg.LoadedMore(items))
Don't forget to handle Msgs and switch the isLoadingMore
flag.Nacho Ruiz Martin
11/02/2023, 12:40 PMSo this started to happen after converting to MVIKotlin.Sorry for not being clear! I was using MVIKotlin with the nav component. I’m currently just migrating to Decompose.
Arkadii Ivanov
11/02/2023, 12:41 PMNacho Ruiz Martin
11/02/2023, 12:42 PMInstanceKeeper
to keep my stores between config changes. I’m just adding the Decompose layer.Arkadii Ivanov
11/02/2023, 12:43 PMNacho Ruiz Martin
11/02/2023, 12:45 PMComponent
class that wasn’t Decompose, just to hold the Store
instance.InstanceKeeper fits with navigation-composeI used Koin to help me with this. With the
instanceKeeper
extension to retrieve it from the `NavBackStackEntry`:
@Composable
inline fun <reified T> getComponent(
entry: NavBackStackEntry,
vararg params: Any,
): T {
return koinInject {
parametersOf(
entry.instanceKeeper(),
*params,
)
}
}
Component
is just:
interface Component<Intent : Any, State : Any, Event : Any> {
val states: StateFlow<State>
val events: Flow<Event>
fun accept(intent: Intent)
}
class DefaultComponent<Intent : Any, State : Any, Event : Any>(
instanceKeeper: InstanceKeeper,
storeFactory: () -> Store<Intent, State, Event>,
) : Component<Intent, State, Event> {
private val store = instanceKeeper.getStore(storeFactory)
override val states = store.stateFlow
override val events = store.labels
override fun accept(intent: Intent) {
store.accept(intent)
}
}
Arkadii Ivanov
11/02/2023, 12:54 PMNacho Ruiz Martin
11/02/2023, 1:02 PMArkadii Ivanov
11/02/2023, 1:03 PMNacho Ruiz Martin
11/02/2023, 1:08 PMIt should be remembered, and the actual collection should be performed only onceDidn’t follow this.
Can I see how you navigate your Composables?Surely! I think it’s the usual Decompose approach:
MainBloc
that holds everything looks like this:
Children(mainBloc.childStack) {
when (val child = it.instance) {
is MainBloc.Child.Home -> {
HomeContainer(child.bloc, snackbarShower::showSnackbar)
}
is MainBloc.Child.PostDetails ->
PostDetailsScreen(
child.bloc,
child.openCommentBox,
snackbarShower::showSnackbar
)
}
}
The HomeBloc
is
Scaffold(
modifier = Modifier.fillMaxSize(),
bottomBar = {
MainBottomBar(
currentDestination = state.currentDestination,
destinations = state.destinations,
onDestinationClick = {
bloc.accept(HomeContract.Intent.OnDestinationClick(it))
}
)
}
) {
Children(bloc.childStack) {
when (val child = it.instance) {
is HomeBloc.Child.Feed -> FeedScreen(child.bloc, showSnackBar)
HomeBloc.Child.MyProfile -> TODO()
HomeBloc.Child.Notification -> TODO()
HomeBloc.Child.Progress -> TODO()
}
}
}
The two Composables that are causing this are FeedScreen
(the list) and PostDetailsScreen
.
Were you asking for this or the internal Component logic?Arkadii Ivanov
11/02/2023, 1:09 PMNacho Ruiz Martin
11/02/2023, 1:10 PMArkadii Ivanov
11/02/2023, 1:12 PMNacho Ruiz Martin
11/02/2023, 1:14 PMpaging3
library:
https://stackoverflow.com/questions/76120368/in-compose-pagingdata-lazypagingitems-returns-0-items-and-loading-state-initia
The reason why it worked with nav component it’s because the navigation was slower (❓ ) and only the second state with the items already sent was rendered. I checked and it was already getting 0 items and then the real amount, it just wasn’t rendered.Arkadii Ivanov
11/02/2023, 1:47 PMNacho Ruiz Martin
11/02/2023, 1:49 PMArkadii Ivanov
11/02/2023, 1:58 PM