Hi <@UHAJKUSTU>, i have social media kind of app j...
# decompose
s
Hi @Arkadii Ivanov, i have social media kind of app just like instagram, IN root component, I have created 5 components for each tab, and lets say now from home component also, i can go to feed user's profile, and from search tab also i can go to some user's profile same like Instagram with nested navigation, So how should i structure this user profile component, Where i should keep this so that i can reuse it from home and search both components? And also i should be able to reuse the same profile component for loggged in user as well in profile tab of root component. Please guide me
a
If I understand it correctly, you can create a separate component for your tabs: MainComponent. This would be a fullscreen component with a bottom bar. Then your root component could have [MainComponent] as initial stack. And push UserProfileComponent when needed
a
This is how I have structured similar apps, just created a bottom navigation component with a router to go between tabs and is then used from the root component. Anything you want to take the full screen from the bottom navigation component sets an output for the root component to add to the navigation stack.
s
Thanx @Arkadii Ivanov, @Andrew Steinmetz Ok i got this point. But i have one more scenario, Just like instagram if i want to reuse that UserProfileComponent in one of tabs as well(in profile tab). how should i do that. So for UserProfileComponent, 3 scenario will be there 1. Use it as full screen component without bottom bar 2. Use it as nested component with bottom tab from either inside home or search tab 3. Use it as one of the tab components ie. in Profile tab What should be the best approach for reusing this? Should i create a separate (e.g HomeUserProfileComponent ) component inside home and similarily for search tab component and only reuse the UI or should i reuse the same UserProfileComponent that is in Root Component at all places from home, search, and in Profile Tab?
a
Use the same component in the root component and the bottom tab component. Pushing the component on the root router would use the whole screen, pushing it on the bottom tab router will show it as nested. In either scenario it's up to the root of bottom tab component to handle the output from the profile component. Should be able to reuse UI too assuming you broke it up into one composable the takes that profile component as the parameter.
Last I checked decompose enforces you don't push the same instance of a component onto a router, but since root and bottom tab each have their own router should not need to worry about that problem.
s
Thanx @Andrew Steinmetz, that should be the approach. thnku
Well i have one more dilemma, how i should handle WebHistoryController for web history, currently i was using it in the RootComponent, So now how i should be able to use it inside other child components like BottomNavComponent, should i pass it down to that component by constructor param or should i handle it completely inside RootComponent?
Hi @Arkadii Ivanov I dont think, we can connect WebHistoryController with multiple stacks, right? Then how i should handle above case, I want to connect WebHistoryController with root component and bottom nav component(inside root) as well? how can i do that, how can i use WebHistoryController with multiple stacks?
a
Ah, yes vested navigation in the browser is pretty tricky, WebHostoryController doesn't support it. Well, you can try having only one stack, e.g. in the root component. And put all components in it.
s
But then nested navigation will be difficult to achieve, and i will have to restructure complete app. Isn't there any workaround ?
I think i will have to use RootComponent and RootContent's platform specific implemenations in jsMain module and there i will have to use single navigation. Apart from that i cant see any common solution for this. for android, ios and desktop these RootComponent and RootContent will be used from common module.
a
Yes, you will have to restructure your root component, I guess. The workaround is to put all your components in one stack in root. You can add a
isFullscreen
flag to each child configuration and RootComponent.Child sealed class. This will allow displaying the bottom bar when needed, and having the same component on the stack as fullscreen and not fullscreen.
You can separate your top components by platform, so the workaround is only applied on js.
s
I did nt got one point here, if suppose we will be using
isFullscreen
flag in each config and sealed child classes and put all components in root component with separate platform implementation of Rootcomponent and usign that flag to show or hide botton bar, then how we will be handling the components which have nested navigations like home? I have this flow currently:- RootComponent -> BottomNavComponent & StoryComponent BottomNavComponent -> Home, Search, Reels, History and Profile components each with nested navigation, So now how i will manage these components with nested navigation. Which components i will have to separate out according to platform, So that workaround will be applied in js only?
a
I think there are two options. 1. You implement your root component in
commonMain
using
isFullscreen
flag. 2. You implement your root component in JS and non-JS targets separately (two implementations), and use
isFullscreen
flag only in JS. If you go with
isFullscreen
, then your root component will look something like this.
Copy code
interface RootComponent {
  val stack: Value<ChildStack<*, Child>>

  sealed class Child {
    abstract val isFullscreen: Boolean

    class Story(val component: StoryComponent, override val isFullscreen: Boolean) : Child()
    class Home(val component: HomeComponent, override val isFullscreen: Boolean) : Child()
    class Search(val component: SearchComponent, override val isFullscreen: Boolean) : Child()
    // ...
  }
}
If you go platform specific, then you have the above root component in
jsMain
, and the following in
nonJsMain
.
Copy code
interface RootComponent {
  val stack: Value<ChildStack<*, Child>>

  sealed class Child {
    class Story(val component: StoryComponent) : Child()
    class BottomNav(val component: BottomNavComponent) : Child()
    // ...
  }
}

interface BottomNavComponent {

  sealed class Child {
    class Home(val component: HomeComponent) : Child()
    class Search(val component: HomeComponent) : Child()
    // ...
  }
}
Both
RootComponent
and
BottomNavComponent
are platform specific, and the rest (
Home
,
Search
, etc.) are in
commonMain
. Does it make sense?
s
Yes,got it. But then i think 1st approach looks good to me, instead of managing RootComponent and BottomNavComponent in 2 places, better to manage in one place. What is the best approach, which approach you will go with keeping in mind the scalabity and maintainability of the app in future?
a
The second approach is much better in terms of scalability. It also allows good transitions between screens. E.g. your BottomNavComponent with the bottom bar goes to the back stack, and Story appears on top. You will have good animations between those two screens automatically. Also switching tabs in BottomNavComponent will be smooth, because your bottom bar will stay on the screen.
But you can try implementing the first approach in
commonMain
first. Then see how it works. Then you can always move it to
jsMain
and implement for
nonJsMain
separately.
s
Thanku so much @Arkadii Ivanov , got it. Then i think instead of approach 1 , approach2 will be best... got the reasons why approach 2 will be best.. i will directly try approach 2. Once again,thanku for your support from my heart, i have never ever seen this kind of support for any library, where someone explains so deeply and answers almost each and every question of everyone.. Amazing.. thanx🙌
a
Another way could be to not use WebHostoryController in your BottomNavComponent, use it only in root. In this case you can have the second (proper) variant in all platforms. But you won't have your tabs as part of the browser history. Depends on your requirements.
s
Yeah that was the current situation, but i want complete web history foreach and every component ,just like we have in normal webapp. Becoz in web i will be using nav rail or nav drawer along with 2 panes instead of bottom tabs. So i would like to have complete web experience there with all history and paths.. So that second approach will be a good fit here. Thnku for another suggestion as well.
Hi @Arkadii Ivanov i have created expect class in common main like this :
Copy code
expect class RootComponentImpl @OptIn(ExperimentalDecomposeApi::class) constructor(
    componentContext: ComponentContext,
    deepLink: DeepLink = DeepLink.None,
    webHistoryController: WebHistoryController? = null,
) : RootComponent
ANd have there actual implementations in respective platform modules. For all other platform its working fine, But in jsMain build i am getting following error:
Copy code
java.lang.IllegalStateException: IdSignature clash: core.component/DeepLink.None|null[0]; Existed declaration CLASS OBJECT name:None modality:FINAL visibility:public superTypes:[core.component.DeepLink] clashed with new CLASS OBJECT name:None modality:FINAL visibility:public superTypes:[core.component.DeepLink]
	at org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsUniqIdClashTracker.commit(JsDeclarationTable.kt:27)
	at org.jetbrains.kotlin.backend.common.serialization.GlobalDeclarationTable.computeSignatureByDeclaration(DeclarationTable.kt:48)
	at org.jetbrains.kotlin.backend.common.serialization.DeclarationTable.computeSignatureByDeclaration(DeclarationTable.kt:83)
	at org.jetbrains.kotlin.backend.common.serialization.DeclarationTable.signatureByDeclaration(DeclarationTable.kt:92)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.protoIdSignature(IrFileSerializer.kt:290)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeIrSymbol(IrFileSerializer.kt:353)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeSimpleType(IrFileSerializer.kt:390)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeIrTypeData(IrFileSerializer.kt:427)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeIrType(IrFileSerializer.kt:494)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeExpression(IrFileSerializer.kt:1006)
Any idea about this?
it seems its related to DeepLink sealed interface
a
That looks like a bug in the JS compiler. Could you share the code of DeepLink? Maybe you are hitting? https://youtrack.jetbrains.com/issue/KT-57984. If not, I suggest reporting it on https://youtrack.jetbrains.com/
s
Copy code
sealed interface DeepLink {
    object None : DeepLink
    class Web(val path: String) : DeepLink
}
So what can be the workaround for now?
a
Then it doesn't look like KT-57984. It doesn't look possible to find a workaround without a reproducer. But this is an issue in the JS compiler. I recommend creating a simplest reproducer and reporting it on YouTrack. Then I could check it for a workaround.
Maybe you don't need expect/actual for the root component. That could fix the compiler crash.
s
Without expect actual, how i am supposed to implement different RootComponent for jsMain and all others, ?
a
You could put separate classes in each source set.
I assume you don't need to access root from commonMain
s
You mean creating diff RootComponentImpl in different source sets. right?
Copy code
I assume you don't need to access root from commonMain
this one i didnt got?
a
Yes. I think you can just have two independent classes
RootComonentImpl
. One in
jsMain
, and another one in a kinda
nonJsMain
source set.
s
ok, got it, i will try that, let see. And By this point
Copy code
I assume you don't need to access root from commonMain
Do you mean root content ?
a
If you don't need to use RootComponentImpl in commonMain, then there should be no need for it to be present in commonMain.
s
Yea, you r right, actually i dont think we need this in commonMain, this will be needed from all diff source sets, but not needed in common main, got your point. thnx