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.Arkadii Ivanov
11/01/2023, 10:25 PMNacho 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 AMArkadii Ivanov
11/02/2023, 11:53 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.Nacho Ruiz Martin
11/02/2023, 11:55 AMIn 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 AMArkadii Ivanov
11/02/2023, 11:57 AMbloc.states.collectAsStateWithLifecycle
being used.Arkadii Ivanov
11/02/2023, 11:57 AMcollectAsState
?Nacho Ruiz Martin
11/02/2023, 11:58 AMArkadii Ivanov
11/02/2023, 11:58 AMNacho Ruiz Martin
11/02/2023, 11:58 AMNacho Ruiz Martin
11/02/2023, 11:59 AMcollectAsState
. This is Compose Multiplatform, btw.Arkadii Ivanov
11/02/2023, 11:59 AMArkadii Ivanov
11/02/2023, 12:00 PMlifecycle.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 PMNacho Ruiz Martin
11/02/2023, 12:01 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.Arkadii Ivanov
11/02/2023, 12:03 PMNacho Ruiz Martin
11/02/2023, 12:04 PMNacho Ruiz Martin
11/02/2023, 12:04 PMNacho Ruiz Martin
11/02/2023, 12:15 PMNacho Ruiz Martin
11/02/2023, 12:16 PMArkadii Ivanov
11/02/2023, 12:18 PMNacho Ruiz Martin
11/02/2023, 12:21 PMNacho Ruiz Martin
11/02/2023, 12:24 PMNacho Ruiz Martin
11/02/2023, 12:24 PMArkadii Ivanov
11/02/2023, 12:25 PMNacho Ruiz Martin
11/02/2023, 12:26 PMNacho Ruiz Martin
11/02/2023, 12:33 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.Nacho Ruiz Martin
11/02/2023, 12:34 PMArkadii 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 PMArkadii 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.Nacho Ruiz Martin
11/02/2023, 12:45 PMNacho Ruiz Martin
11/02/2023, 12:46 PMInstanceKeeper 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,
)
}
}
Nacho Ruiz Martin
11/02/2023, 12:52 PMComponent
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)
}
}
Nacho Ruiz Martin
11/02/2023, 12:53 PMNacho Ruiz Martin
11/02/2023, 12:53 PMArkadii Ivanov
11/02/2023, 12:54 PMArkadii Ivanov
11/02/2023, 12:58 PMNacho Ruiz Martin
11/02/2023, 1:02 PMArkadii Ivanov
11/02/2023, 1:03 PMArkadii Ivanov
11/02/2023, 1:03 PMArkadii Ivanov
11/02/2023, 1:04 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 PMArkadii Ivanov
11/02/2023, 1:10 PMArkadii Ivanov
11/02/2023, 1:10 PMNacho Ruiz Martin
11/02/2023, 1:10 PMArkadii Ivanov
11/02/2023, 1:12 PMNacho Ruiz Martin
11/02/2023, 1:14 PMNacho Ruiz Martin
11/02/2023, 1:43 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 PMNacho Ruiz Martin
11/02/2023, 1:50 PMArkadii Ivanov
11/02/2023, 1:58 PM