Skip to content

Quick Start

This guide shows how to quickly integrate Tuucho into a Kotlin Multiplatform project (iOS and Android). Tuucho is a server‑driven rendering engine published on Maven Central.

1 Add dependencies to the shared module

In your shared module (commonMain) you need the Tuucho core library plus its supporting libraries:

 commonMain.dependencies {
    implementation("com.tezov:tuucho.core:0.0.1-alpha25_2.3.0") // for kotlin 2.3.0     
    implementation("com.tezov:tuucho.ui-component.stable:0.0.1-alpha25_2.3.0")
    implementation("io.insert-koin:koin-core:4.2.0-beta2")     
    implementation("io.ktor:ktor-client-core:3.3.2")

    implementation("org.jetbrains.compose.runtime:runtime:1.10.0")
    implementation("org.jetbrains.compose.foundation:foundation:1.10.0")
    implementation("org.jetbrains.compose.ui:ui:1.10.0")
 }

2 Create an AppScreen in commonMain in the shared module

Tuucho exposes a composable engine which loads its configuration from the server and renders the pages. The simplest way to start it is via an AppScreen composable:

@Composable
fun AppScreen(
    applicationModules: List<KoinMass>,
    koinExtension: (KoinApplication.() -> Unit)? = null,
) {
    TuuchoEngineStart(
        koinMassModules = SystemSharedModules.invoke() + applicationModules,
        koinExtension = koinExtension,
        onStartUrl = "lobby/page-login"
    )
}

You can make use of middleware navigation to preload component: - Config details to understand the config file format. - Middleware details to understand navigation middleware.

You can also check the tuucho‑sample in the repository.

3 Supply configuration via Koin in the shared module

Tuucho needs configuration properties, like you server base url, endpoint, database file name. Define a Koin module that provides a SystemCoreDataModules.Config instance. In the shared module you can implement this interface with your own values:

object ConfigModule {

    fun invoke() = module(ModuleContextCore.Main) {
        factory<NetworkRepositoryModule.Config> {
            object : NetworkRepositoryModule.Config {
                override val timeoutMillis = 5000
                override val version = "v1"
                override val baseUrl = "http://localhost:3000/"
                override val healthEndpoint = "health"
                override val resourceEndpoint = "resource"
                override val sendEndpoint = "send"
            }
        }
    }
}

The core module defines the Config interface for NetworkRepositoryModule.

The sample application uses BuildKonfig to generate these values per platform.

4 Android integration

On Android you have to provide the application Context so that Tuucho can access platform services. Define an ApplicationModuleDeclaration that injects the context using Koin:

First you need the to add the dependencies

 dependencies {
    implementation(project(":app:shared"))
    implementation("com.tezov:tuucho.core:0.0.1-alpha25_2.3.0") // for kotlin 2.3.0     
    implementation("io.insert-koin:koin-core:4.2.0-beta2")

    implementation("androidx.activity:activity-compose:1.12.2")
 }

Then you need to supply the context with the koin symbol ApplicationModules.Name.APPLICATION_CONTEXT

object ApplicationModule {

    fun invoke(
        applicationContext: Context
    ) = module(ModuleContextCore.Main) {
        single<Context>(SystemCoreDataModulesAndroid.Name.APPLICATION_CONTEXT) {
            applicationContext
        }
    }

}

Then, in your Activity, set the Compose content to AppScreen and pass in the application context:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .windowInsetsPadding(WindowInsets.systemBars)
            ) {
                AppScreen(
                    applicationModules = listOf(ApplicationModule.invoke(applicationContext)),
                    koinExtension = {
                        koin.get<NavigationFinishPublisher>().onFinish {
                            koin.close()
                            this@MainActivity.finish()
                        }
                    }
                )
            }
        }
    }
}

At runtime Tuucho will start Koin, load the configuration and display the server‑rendered UI.

5 iOS integration

On iOS you need to expose the AppScreen composable through a ComposeUIViewController, then wrap it in a SwiftUI view. In your shared module create a simple function returning a ComposeUIViewController:

fun uiView(
    koinExtension: (KoinApplication.() -> Unit)? = null,
) = ComposeUIViewController {
    AppScreen(
        applicationModules = emptyList(),
        koinExtension = koinExtension
    )
}

val KoinApplication.tuuchoKoinIos get(): KoinIos = koin.get<KoinIos>()

Then in your iOS app, bridge the Compose controller into SwiftUI. The sample uses AppCoordinator, ComposeView and ComposeHostController to do this:

//AppCoordinator.swift
import SwiftUI
import Combine
class AppCoordinator: ObservableObject {

    @Published var shouldRecreateController = false
    private var isKoinClosed = false

    func handleAppAppear() {
        if isKoinClosed {
            shouldRecreateController = true
            isKoinClosed = false
        }
    }

    func handleKoinClosed() {
        isKoinClosed = true
        shouldRecreateController = true
    }
}

//ComposeView.swift
import SwiftUI
import UIKit
struct ComposeView: UIViewControllerRepresentable {
    @ObservedObject var coordinator: AppCoordinator

    func makeUIViewController(context: Context) -> UIViewController {
        let controller = ComposeHostController()
        controller.coordinator = coordinator
        return controller
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        if coordinator.shouldRecreateController {
            guard let controller = uiViewController as? ComposeHostController else { return }
            controller.recreateComposeView()
            DispatchQueue.main.async {
                coordinator.shouldRecreateController = false
            }
        }
    }
}

//ComposeHostController.swift
import UIKit
import SwiftUI
import AppSharedFramework

class ComposeHostController: UIViewController {

    weak var coordinator: AppCoordinator?
    private var composeViewController: UIViewController?
    private var isKoinInitialized = false

    override func viewDidLoad() {
        super.viewDidLoad()
        setupComposeView()
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        composeViewController?.view.frame = view.bounds
    }

    func recreateComposeView() {
        cleanup()
        setupComposeView()
    }

    private func cleanup() {
        composeViewController?.willMove(toParent: nil)
        composeViewController?.view.removeFromSuperview()
        composeViewController?.removeFromParent()
        composeViewController = nil
    }

    private func setupComposeView() {
        guard !isKoinInitialized else { return }
        let vc = KMPKitKt.uiView(koinExtension: { [weak self] koinApplication in
            guard let self = self else { return }
            let publisher = koinApplication.tuuchoKoinIos.get(clazz: NavigationFinishPublisher.self) as! NavigationFinishPublisher
            publisher.onFinish(block: {
                self.coordinator?.handleKoinClosed()
                koinApplication.tuuchoKoinIos.close()
                self.isKoinInitialized = false
                UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
            })
        })
        self.composeViewController = vc
        self.isKoinInitialized = true
        addChild(vc)
        view.addSubview(vc.view)
        vc.view.frame = view.bounds
        vc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        vc.didMove(toParent: self)
    }
}

// iosApp.swift
import SwiftUI

@main
struct iosApp: App {
    @StateObject private var coordinator = AppCoordinator()

    var body: some Scene {
        WindowGroup {
            ComposeView(coordinator: coordinator)
                .onAppear {
                    coordinator.handleAppAppear()
                }
        }
    }

}

Ios require more code mainly to be able to manage the swipe back. you can check all that code inside the sample app

6 Backend

Tuucho renders its UI from a backend server. For a quick test you can run the tuucho‑backend dev repository locally with a version matching your Tuucho client.

The engine is under active development. At the time of writing the UI components are limited to basic primitives (fields, labels, linear layouts, buttons and spacers). The ability to register custom views and provide richer UI will evolve in future releases.

7 Sample Application

Note

The sample application includes a mock server. If you launch it in mock build type, you don't need a backend server.