# Complete Guide to Becoming a Fullstack Kotlin Developer - application architecture with Decompose

In the previous [post](https://hashnode.com/post/clyl39acc000009kxheb83i01), I discussed common HTTP clients for Android, iOS, and web applications. We are gradually reaching the stage where we can present the data to the users. To achieve this, we need to properly structure our code, especially since we have already completed the repository layer. Now, it’s time to focus on building the presentation layer.

For this purpose, we will use [Decompose](https://arkivanov.github.io/Decompose/), a Kotlin multiplatform library. Decompose allows us to create lifecycle-aware business logic components and manage routing effectively. One of the reasons I chose Decompose is its robust support for **wasm** (WebAssembly), which makes it highly versatile for different platforms.

Decompose provides a clean way to organize our code, ensuring that our business logic is separated from the UI components. This separation is crucial for maintaining a clean architecture and making our codebase more manageable and scalable. Additionally, Decompose's routing capabilities will help us navigate between different screens or components within our application seamlessly.

By leveraging Decompose, we can ensure that our application is not only well-structured but also capable of handling complex business logic across multiple platforms. This will ultimately lead to a better user experience and a more maintainable codebase.

> *The complete project is avaiable on* [*GitHub*](https://github.com/mkonkel/GameShop)

---

## Dependencies

`libs.versions.toml`

```kotlin
[versions] 
decompose = "3.0.0-alpha04" 
essenty = "2.0.0-alpha02" 

[libraries] 
decompose-core = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" } 
decompose-extensions-compose = { module = "com.arkivanov.decompose:extensions-compose", version.ref = "decompose" } 
essenty-lifecycle = { group = "com.arkivanov.essenty", name = "lifecycle", version.ref = "essenty" } 
essenty-stateKeeper = { group = "com.arkivanov.essenty", name = "state-keeper", version.ref = "essenty" } 
essenty-instanceKeeper = { group = "com.arkivanov.essenty", name = "instance-keeper", version.ref ="essenty" } 
essenty-backHandler = { group = "com.arkivanov.essenty", name = "back-handler", version.ref = "essenty" }
```

`shared/build.gradle.kts`

```kotlin
commonMain.dependencies { 
    ... 
    implementation(libs.decompose.core) 
    implementation(libs.essenty.lifecycle) 
    api(libs.essenty.stateKeeper) 
    api(libs.essenty.backHandler) 
} 
```

## Components

With Decompose, we can create classes called `Components` that act as our presenters. These components need to implement the `ComponentContext` interface. This is where all interactions occur: API calls, building UI models, updating views, handling user interactions, and more. If you're an Android developer, you can think of them as `ViewModels`.

Decompose builds a stack of components and provides an easy way to navigate through them. `Components` that aren’t currently visible are not destroyed; they can keep working in the background without an attached UI. For more details about Decompose, I encourage you to read the documentation.

With this brief overview, we can create the `RootComponent`, the entry point of our application. This component will host all other components (sub-components/screens) and will live as long as the entire application.

`shared/features/RootComponent.kt`

```kotlin
interface RootComponent { 
    val childStack: Value<ChildStack<*, Child>> 
 
    sealed class Child { 
        class LoginChild(val component: LoginComponent) : Child() 
        class RegisterChild(val component: RegisterComponent) : Child() 
    } 
} 
```

Every Component that can host other components needs to provide a `childStack` – a holder value for current components. The `childStack` is visible to the platform, and based on its values, the **UI** will be determined. It can return the actual (top child) and backStack (inactive children) or all of the items. The `Child` sealed class represents components that can be hosted by the `RootComponent`. It also acts as a wrapper for the other `Components`.

`shared/features/RealRootComponent.kt`

```kotlin
internal class RealRootComponent( 
    componentContext: ComponentContext, 
) : RootComponent, ComponentContext by componentContext {}
```

The `context` provides us with the whole `lifecycle-aware` features and helps us implement behaviors like stack navigation. The navigation requires a `Configuration` of the set of parameters, values, or any other things that are necessary to create sub-components. The configuration should be created by us and needs to be `serializable` – the most common pattern is to use a sealed class again.

`shared/commonMain/features/RealRootComponent.kt`

```kotlin
private val navigation = StackNavigation<Config>() 

@Serializable 
sealed interface Config { 
    @Serializable 
    data object Login : Config 
    @Serializable 
    data object Register : Config 
} 
```

Following the same rules, we can create `LoginComponent` and `RegisterComponent` that will hold the login form. All the screens that can be reached from the `RootComponent` must be defined in child and config classes. Of course, different configurations may lead us to the same components.

`shared/commonMain/features/login/LoginComponent.kt`

```kotlin
interface LoginComponent { 
    fun onRegisterClick() 
} 
```

`shared/commonMain/features/login/RealLoginComponent.kt`

```kotlin
internal class RealLoginComponent( 
    componentContext: ComponentContext, 
    private val onRegister: () -> Unitt 
) : LoginComponent, ComponentContext by componentContext { 
     
    override fun onRegisterClick() { 
        onRegister() 
    } 
} 
```

Now we can write a function that will use the configuration to create a child component. Let's call this function `childFactory`. This function will take the configuration as an input parameter and return the appropriate child component based on the configuration type.

`shared/commonMain/features/RealRootComponent.kt`

```kotlin
private fun childFactory( 
    config: Config, 
    componentContext: ComponentContext, 
) = when (config) { 
    Config.Login ->  
       RootComponent.Child.LoginChild( 
             RealLoginComponent(componentContext = componentContext) 
       ) 
} 
```

## Navigation

The last thing is to create a `childStack` that will manage the navigation. It will also be responsible for creating children and managing the components. It should have a **unique key**, **navigation source**, **serializer** that determines how to serialize the configuration, a flag that determines if the stack should handle the **back button**, an **initial stack** from which the component should start, and a **child factory** for creating new subcomponents.

```kotlin
 private val stack = childStack( 
    key = "RootComponent", 
    source = navigation, 
    serializer = Config.serializer(), 
    handleBackButton = true, 
    initialStack = { listOf(Config.Login) }, 
    childFactory = ::childFactory, 
) 

override val childStack: Value<ChildStack<*, RootComponent.Child>> = stack 
```

The return type of the stack is `Value`, an internal **Decompose** way to provide an observable state (similar to the state in Jetpack Compose). It’s a custom class that gives us the flexibility to use the library on any platform we want.

With the above configuration, we can now handle the navigation events more effectively. By using the `onRegister` lambda in the `LoginComponent`, we can trigger a screen change in the `RootComponent`. This is achieved through the `pushNew` function, which pushes a new configuration onto the top of the current stack.

This approach offers several advantages. Firstly, it ensures that the entire navigation logic is decoupled from the underlying platforms. This means that the navigation logic is not tied to any specific platform, making it more versatile and easier to maintain. Secondly, this separation simplifies the navigation logic, allowing it to be unit-tested within the shared codebase. This eliminates the need to run additional devices or emulators for testing purposes, thereby speeding up the development and testing process.

```kotlin
private fun childFactory( 
    config: Config, 
    componentContext: ComponentContext, 
) = when (config) { 
    Config.Login -> { 
        RootComponent.Child.LoginChild( 
            RealLoginComponent( 
                componentContext = componentContext, 
                onRegister = { 
                    navigation.pushNew(Config.Register) 
                }, 
            ), 
        ) 
    } 
 
    Config.Register -> { 
        RootComponent.Child.RegisterChild( 
            RealRegisterComponent( 
                componentContext = componentContext, 
            ), 
        ) 
    } 
}
```

The last step is to create the `RootComponent` in the shared platform **UI** and start using the created stack. We begin at the entry point for all platforms with the `App()` function, which will initially only provide the `RootScreen`. 

`composeApp/commonMain/App.kt`

```kotlin
@Composable 
fun App( 
    component: RootComponent, 
    modifier: Modifier, 
) { 
    RootScreen(component = component, modifier = modifier) 
}
```

`composeApp/commonMain/features/RootScreen.kt`

```kotlin
@Composable 
private fun RootScreen( 
    component: RootComponent, 
    modifier: Modifier = Modifier, 
) { 
    Children( 
        stack = component.childStack, 
        modifier = modifier, 
        animation = stackAnimation(fade()), 
    ) { 
        when (val child = it.instance) { 
            is RootComponent.Child.LoginChild -> 
                LoginScreen(child.component) 
 
            is RootComponent.Child.RegisterChild -> 
                RegisterScreen(child.component) 
        } 
    } 
} 
```

The `Children()` function is part of the **Decompose** library responsible for handling the `childStack` values. The last parameter of this function is a **@Composable** lambda that returns the current child and allows us to handle UI changes depending on the current child.

`composeApp/commonMain/features/login/LoginScreen.kt`

```kotlin
@Composable 
internal fun LoginScreen( 
    component: LoginComponent, 
    modifier: Modifier = Modifier, 
) { 
    Button( 
        onClick = { 
            component.onRegisterClick() 
        }, 
        content = { 
            Text("Register") 
        }, 
    ) 
} 
```

`composeApp/commonMain/features/register/RegisterScreen.kt`

```kotlin
@Composable 
internal fun RegisterScreen( 
    component: RegisterComponent, 
    modifier: Modifier = Modifier, 
) { 
    Text("This is the Register screen") 
} 
```

Finally, it is time to use our `App()` function on every platform. On Android, we need to use the `retainedComponent` function that will handle orientation changes.

### Android

`composeApp/androidMain/gameshop/MainActivity.kt`

```kotlin
class MainActivity : ComponentActivity() { 
    override fun onCreate(savedInstanceState: Bundle?) { 
        super.onCreate(savedInstanceState) 
 
        val root = retainedComponent { RealRootComponent(componentContext = it) } 
        setContent { App(component = root, modifier = Modifier.fillMaxSize()) } 
    } 
} 
```

### iOS

`composeApp/iosMain/gameshop/main.kt`

```kotlin
fun MainViewController() = ComposeUIViewController { 
    val root = remember { RealRootComponent(componentContext = DefaultComponentContext(LifecycleRegistry()),) } 
 
    App(component = root, modifier = Modifier.fillMaxSize()) 
} 
```

### Web

`composeApp/wasmJsMain/gameshop/main.kt`

```kotlin
fun main() { 
    val root = RealRootComponent(componentContext = DefaultComponentContext(lifecycle = LifecycleRegistry()),) 
 
    CanvasBasedWindow(title = "GameShop", canvasElementId = "gameShopCanvas") { 
        App(component = root, modifier = Modifier.fillMaxSize()) 
    } 
}
```

This was the basic setup of the **Decompose,** which we can follow for other screens. Nevertheless, having direct access to components implementation and creating them on your own is not a great approach. Since we have created a simple **DI** and we are using it for the repository layer we should extend it with the methods for creating components. 

## Dependency Injection

`composeApp/commonMain/features/factory/ComponentFactory.kt`

```kotlin
interface ComponentFactory { 
    fun createRootComponent( 
        componentContext: ComponentContext, 
    ): RootComponent 
 
    fun createRegisterComponent( 
        componentContext: ComponentContext 
    ): RegisterComponent 
 
    fun createLoginComponent( 
        componentContext: ComponentContext, 
        onRegister: () -> Unit, 
    ): LoginComponent 
}
```

Now, with the usage of the factory, we can inject it via the constructor into the `RootComponent`. This allows us to delegate the creation of sub-components to the factory, ensuring a more modular and maintainable codebase.

`composeApp/commonMain/features/RealRootComponent.kt`

```kotlin
internal class RealRootComponent( 
    componentContext: ComponentContext, 
    private val componentFactory: ComponentFactory, 
) : RootComponent, ComponentContext by componentContext { 
    ... 
    Config.Register -> { 
        RootComponent.Child.RegisterChild( 
            componentFactory.createRegisterComponent( 
                componentContext = componentContext, 
            ), 
        ) 
    } 
    ... 
} 
```

The implementation of `ComponentFactory` will have all the necessary dependencies to create any component. With this approach, we don’t have to worry about creating any additional classes. We can also extend the `LoginComponent` and the `RegisterComponent` with additional properties such as repositories and lambda functions for user interactions. Lambdas will be passed from parent to child, but the repository is injected into the factory.

`composeApp/commonMain/features/factory/RealComponentFactory.kt`

```kotlin
internal class RealComponentFactory( 
    private val remoteRepository: RemoteRepository, 
) : ComponentFactory { 
    override fun createRootComponent( 
        componentContext: ComponentContext, 
    ): RootComponent { 
        return RealRootComponent( 
            componentContext = componentContext, 
            componentFactory = this, 
        ) 
    } 
 
    override fun createRegisterComponent(componentContext: ComponentContext): RegisterComponent { 
        return RealRegisterComponent( 
            componentContext = componentContext, 
            loginRepository = remoteRepository.loginRepository(), 
        ) 
    } 
 
    override fun createLoginComponent( 
        componentContext: ComponentContext, 
        onLogin: () -> Unit, 
        onRegister: () -> Unit, 
    ): LoginComponent { 
        return RealLoginComponent( 
            loginRepository = remoteRepository.loginRepository(), 
            onLogin = onLogin, 
            onRegister = onRegister, 
        ) 
    } 
}
```

The last thing to do is to add the factory and its implementation to the **DI** class and use it on the platforms.

`composeApp/commonMain/di/DI.kt`

```kotlin
object DI { 
    private val tokenStorage: TokenStorage = RealTokenStorage() 
    private val httpClientFactory: HttpClientFactory = HttpClientFactory(tokenStorage) 
    private val remoteRepository: RemoteRepository = RealRemoteRepository(httpClientFactory.create(), tokenStorage) 
 
    fun rootComponent( 
        componentContext: ComponentContext, 
    ): RootComponent { 
        return RealComponentFactory(remoteRepository = remoteRepository) 
            .createRootComponent(componentContext = componentContext) 
    } 
} 
```

`composeApp/androidMain/gameshop/MainActivity.kt`

```kotlin
class MainActivity : ComponentActivity() { 
    override fun onCreate(savedInstanceState: Bundle?) { 
        super.onCreate(savedInstanceState) 
 
        val root = retainedComponent { DI.rootComponent(componentContext = it) } 
        setContent { App(component = root, modifier = Modifier.fillMaxSize()) } 
    } 
} 
```

## Wiring things up

With the work we’ve done so far, where we introduced the presentation layer with various components, it's now time to integrate the **API** calls. Here's a detailed explanation of the process:

Assume the user clicks on the **login** button on the login screen. At this moment, the app will send an **API** request containing the user's credentials. This request is handled by our `remoteRepository`, which was set up earlier with the necessary **HTTP client** and **token storage**.

Upon receiving a successful response from the server, indicating that the login was successful, the `onLogin` lambda will be triggered. This lambda function is responsible for performing the next steps in the app's flow. Specifically, it will navigate the user to the **home screen**.

On the home screen, the user will be greeted with multiple sections. Two primary sections include the **games list**, which displays a curated list of games available to the user, and the **orders** section, where users can view their past and current orders.

The `HomeComponent` will be similar to the `RootComponent` as it will have its own stack with `GamesListComponent` and `OrdersComponent`. The screen will have bottom navigation that will allow you to switch views.

The API calls are made with **suspend functions**; for this reason, we need to invoke them from a **coroutine scope**. Therefore, every component should be able to create and provide its lifecycle-aware scope that can be used for making such requests. We can introduce a `BaseComponent` abstract class that will be responsible for handling the coroutine scope creation. Every component implementation should use it to avoid code duplication.

`composeApp/commonMain/features/BaseComponent.kt`

```kotlin
internal abstract class BaseComponent( 
    componentContext: ComponentContext, 
    coroutineContext: CoroutineContext, 
) : ComponentContext by componentContext { 
 
    protected val scope by lazy { coroutineScope(coroutineContext + Dispatchers.Default + SupervisorJob()) } 
 
    private fun CoroutineScope( 
        context: CoroutineContext, 
        lifecycle: Lifecycle, 
    ): CoroutineScope { 
        val scope = CoroutineScope(context) 
        lifecycle.doOnDestroy { scope.coroutineContext.cancelChildren() } 
        return scope 
    } 
 
    private fun LifecycleOwner.coroutineScope(context: CoroutineContext): CoroutineScope = 
        CoroutineScope(context, lifecycle) 
} 
```

`shared/features/login/RealLoginComponent.kt`

```kotlin
internal class RealLoginComponent( 
    componentContext: ComponentContext, 
    coroutineContext: CoroutineContext, 
    private val loginRepository: LoginRepository, 
    private val onLogin: () -> Unit, 
    private val onRegister: () -> Unit, 
) : BaseComponent(componentContext, coroutineContext), LoginComponent { 
    ... 
} 
```

Let’s move forward and create a component factory, which will be used all over the app to help create components.

`composeApp/commonMain/factory/ComponentFactory.kt`

```kotlin
interface ComponentFactory { 
    fun createRootComponent(componentContext: ComponentContext): RootComponent 
    fun createRegisterComponent(componentContext: ComponentContext): RegisterComponent 
    fun createLoginComponent(componentContext: ComponentContext, onLogin: () -> Unit, onRegister: () -> Unit, ): LoginComponent 
} 
```

Through the implementation, we can pass all the required dependencies to ensure that each component has everything it needs to function correctly. This approach allows us to inject specific dependencies, such as repositories, context, and callback functions, directly into the components. By doing so, we maintain a clean separation of concerns and promote reusability across different parts of the application.

`composeApp/commonMain/factory/RealComponentFactory.kt`

```kotlin
internal class RealComponentFactory( 
    private val mainContext: CoroutineContext, 
    private val remoteRepository: RemoteRepository, 
) : ComponentFactory { 
    override fun createRootComponent(componentContext: ComponentContext, ): RootComponent { 
        return RealRootComponent( 
            coroutineContext = mainContext, 
            componentContext = componentContext, 
            componentFactory = this, 
        ) 
    } 
 
    override fun createRegisterComponent(componentContext: ComponentContext): RegisterComponent { 
        return RealRegisterComponent( 
            componentContext = componentContext, 
            coroutineContext = mainContext, 
            loginRepository = remoteRepository.loginRepository(), 
        ) 
    } 
 
    override fun createLoginComponent( 
        componentContext: ComponentContext, 
        onLogin: () -> Unit, 
        onRegister: () -> Unit, 
    ): LoginComponent { 
        return RealLoginComponent( 
            coroutineContext = mainContext, 
            componentContext = componentContext, 
            loginRepository = remoteRepository.loginRepository(), 
            onLogin = onLogin, 
            onRegister = onRegister, 
        ) 
    } 
} 
```

Then in our **ID** object, we can replace the direct `RootComponent` call with the newly created factory. We also need to modify the platform call, and finally, the internals of the `RealRootComponent` to use the factory to create its sub-components. 

`composeApp/commonMain/di/DI.kt`

```kotlin
object DI { 
    private val tokenStorage: TokenStorage = RealTokenStorage() 
    private val httpClientFactory: HttpClientFactory = HttpClientFactory(tokenStorage) 
    private val remoteRepository: RemoteRepository = RealRemoteRepository(httpClientFactory.create(), tokenStorage) 
 
    fun rootComponent( 
        componentContext: ComponentContext, 
        mainContext: CoroutineContext 
    ): RootComponent { 
        return RealComponentFactory( 
            mainContext = mainContext, 
            remoteRepository = remoteRepository, 
        ).createRootComponent( 
            componentContext = componentContext, 
        ) 
    } 
} 
```

`composeApp/androidMain/gameshop/MainActivity.kt`

```kotlin
DI.rootComponent(componentContext = it, mainContext = MainScope().coroutineContext) 
```

`composeApp/commonMain/features/RealRootComponent.kt`

```kotlin
internal class RealRootComponent( 
    componentContext: ComponentContext, 
    coroutineContext: CoroutineContext, 
    private val componentFactory: ComponentFactory, 
) : BaseComponent(componentContext, coroutineContext), RootComponent { 
   ... 
   private fun childFactory( 
    config: Config, 
    componentContext: ComponentContext, 
) = when (config) { 
    Config.Login -> { 
        RootComponent.Child.LoginChild( 
            componentFactory.createLoginComponent( 
                componentContext = componentContext, 
                onLogin = { 
                    navigation.pushNew(Config.Home) 
                }, 
                onRegister = { 
                    navigation.pushNew(Config.Register) 
                }, 
            ), 
        ) 
    } 
 
    Config.Register -> { 
        RootComponent.Child.RegisterChild( 
            componentFactory.createRegisterComponent( 
                componentContext = componentContext, 
            ), 
        ) 
    } 
} 
```

Since web application navigation is quite different from mobile – we can reach certain screens by passing a proper link – we need to ensure that this is handled. Thankfully, **Decompose** provides a tool to do it. The `WebHistoryController` is a connection between `childStack` and the `WebHistory` interface. It holds the web paths and can change the navigation according to the current address. We can also introduce a sealed class called `DeepLink` which will produce the current web path.

`composeApp/commonMain/deepLink/DeepLink.kt`

```kotlin
sealed interface DeepLink { 
    data object None : DeepLink 
    class Web(val path: String) : DeepLink 
} 
```

`composeApp/commonMain/features/RealRootComponent.kt`

```kotlin
@OptIn(ExperimentalDecomposeApi::class) 
internal class RealRootComponent( 
    componentContext: ComponentContext, 
    coroutineContext: CoroutineContext, 
    private val deepLink: DeepLink = DeepLink.None, 
    private val webHistoryController: WebHistoryController? = null, 
    private val componentFactory: ComponentFactory, 
) : BaseComponent(componentContext, coroutineContext), RootComponent { ... } 
```

The `webHistoryController` needs to be attached to the stack and navigation. We need to pass the navigation, stack, and serializer. Then we need to find a way to change the web application path based on the current configuration. The `getPath` lambda provides the current configuration and requires a `String` in return. Similarly, the proper configuration should be returned for a given path and this is a role for the `getConfiguration` lambda, which takes a `String` and returns `Configuration`. 

`composeApp/commonMain/features/RealRootComponent.kt`

```kotlin
init { 
    webHistoryController?.attach( 
        navigator = navigation, 
        stack = stack, 
        serializer = Config.serializer(), 
        getPath = ::getPathForConfig, 
        getConfiguration = ::getConfigForPath, 
    ) 
} 
```

`composeApp/commonMain/features/RealRootComponent.kt`

```kotlin
private fun getPathForConfig(config: Config): String = 
    when (config) { 
        Config.Login -> "/login" 
        Config.Register -> "/register" 
    } 
```

`composeApp/commonMain/features/RealRootComponent.kt`

```kotlin
private fun getConfigForPath(path: String): Config = 
    when (path.removePrefix("/")) { 
        “login” -> Config.Login 
        “register” -> Config.Register 
        else -> Config.Login 
    } 
```

The `childStack` function also needs to be modified. The `initialStack` might be constructed differently, based on the passed path. We will take the `webHistoryController` paths, iterate through them, and try to find a proper config for a given address. If we can’t find anything, we should initialize the default stack. For mobile apps (when there is no DeepLink), we are returning the `Login` configuration, but for the Web application with the provided address, we should try to resolve it.

`composeApp/commonMain/features/RealRootComponent.kt`

```kotlin
private val stack = 
    childStack( 
        key = "RootComponent", 
        source = navigation, 
        serializer = Config.serializer(), 
        handleBackButton = true, 
        initialStack = { 
            getInitialStack( 
                webHistoryPaths = webHistoryController?.historyPaths, 
                deepLink = deepLink, 
            ) 
        }, 
        childFactory = ::childFactory, 
    ) 
```

`composeApp/commonMain/features/RealRootComponent.kt`

```kotlin
private fun getInitialStack( 
    webHistoryPaths: List<String>?, 
    deepLink: DeepLink, 
): List<Config> = 
    webHistoryPaths 
        ?.takeUnless(List<*>::isEmpty) 
        ?.map(::getConfigForPath) 
        ?: getInitialStack(deepLink) 
 
private fun getInitialStack(deepLink: DeepLink): List<Config> = 
    when (deepLink) { 
        is DeepLink.None -> listOf(Config.Login) 
        is DeepLink.Web -> listOf(getConfigForPath(deepLink.path)) 
    } 
```

Since we added two new parameters to the `RootComponent` constructor we need to update the **factory** and the **platforms.** We used default parameters in `RootComponent` so after adjusting the factory only the **Web** application entry point will be changed.

`composeApp/iosMain/gameshop/main.kt`

```kotlin
fun MainViewController() = ComposeUIViewController { 
val root = 
    DI.rootComponent( 
        componentContext = DefaultComponentContext(lifecycle = LifecycleRegistry()), 
        deepLink = DeepLink.Web(path = window.location.pathname), 
        webHistoryController = DefaultWebHistoryController(), 
        mainContext = MainScope().coroutineContext, 
    ) 
 
    App(component = root, modifier = Modifier.fillMaxSize()) 
} 
```

That’s all in the when it comes to app architecture. We’ve got the presentation layer ready, with some abstraction on top of it, a component factory, and a configured **DI**. Adding new functionalities should be straightforward, every new screen needs its component and should be added to the navigation.

We still missing one critical part – the **UI** layer. In the next blogpost, we will focus on providing **UI** models to the platforms and handling user interactions. We will create a simple login form and a home screen with a games list.  

> In this article, we delve into the presentation layer of a Kotlin multiplatform application using the Decompose library. We explore how to structure code with lifecycle-aware components, implement seamless navigation, and maintain a clean architecture. The guide covers setting up dependencies, creating components like `RootComponent`, managing navigation, and integrating with Kotlin's coroutine framework for async tasks. Additionally, we discuss the use of Dependency Injection for modularity and handling web-specific navigation. This foundational setup prepares our application for effective data presentation across Android, iOS, and web platforms.
