xxfast / Decompose-Router

A Compose-multiplatform navigation library that leverage Decompose to create an API inspired by Conductor
https://xxfast.github.io/Decompose-Router/
221 stars 9 forks source link

Export RouterContext for iOS #87

Closed rzolin closed 7 months ago

rzolin commented 7 months ago

I am coming from iOS side of things and a bit lost here. The documentation and the closed issue says that I need to export the RouterContext to use it in AppDelegate. At the moment I am trying to build your stripped sample app for iOS, it builds for Android, but iOS spits out the error:

Execution failed for task ':app:linkDebugFrameworkUikitSimulatorArm64'.
> Following dependencies exported in the debugFramework binary are not specified as API-dependencies of a corresponding source set:

  Files: [/Users/rzolin/.gradle/caches/modules-2/files-2.1/io.github.xxfast/decompose-router-iossimulatorarm64/0.6.0/3523672dbaf4e4c3155282f25bac0b3dc7ae7f6a/decompose-router.klib]
  Files: [/Users/rzolin/.gradle/caches/modules-2/files-2.1/io.github.xxfast/decompose-router-iossimulatorarm64/0.6.0/3523672dbaf4e4c3155282f25bac0b3dc7ae7f6a/decompose-router.klib]

  Please add them in the API-dependencies and rerun the build.

I do export RouterContext as following:

  listOf(
    iosX64("uikitX64"),
    iosArm64("uikitArm64"),
    iosSimulatorArm64("uikitSimulatorArm64"),
  ).forEach {
    it.binaries{
      framework {
        baseName = "app"

        // Only need this if you wish to add your own AppDelegate in swift
        export(libs.decompose.router)
      }

      executable {
        entryPoint = "io.github.xxfast.decompose.router.app.main"
        freeCompilerArgs += listOf(
          "-linker-option", "-framework", "-linker-option", "Metal",
          "-linker-option", "-framework", "-linker-option", "CoreText",
          "-linker-option", "-framework", "-linker-option", "CoreGraphics"
        )
      }
    }
  }

decompose-router = "0.6.0"
decompose-router = { module = "io.github.xxfast:decompose-router", version.ref = "decompose-router" }

Still not sure what I am missing or doing wrong. Please advise

xxfast commented 7 months ago

Hmm.. by sample are you referring to this app?

Can you verify if you included router as an API dependency like this?

https://github.com/xxfast/Decompose-Router/blob/69d8bb7cce70db7b8b154dec46fe8a0a1a37aa8a/app/build.gradle.kts#L61-L65

rzolin commented 7 months ago

Yes, that app, which is included in the GitHub repo.

The line api(project(":decompose-router")) was originally there (and it works on iOS that way), but since I am linking the project to the repo as this:

decompose-router = "0.6.0"
decompose-router = { module = "io.github.xxfast:decompose-router", version.ref = "decompose-router" }

  sourceSets {
    val commonMain by getting {
      dependencies {
        // Only need to add this as api if you wish to add your own AppDelegate in swift
        implementation(libs.decompose.router)

the inclusion of the directories decompose-router and decompose-router-wear are no longer needed. And it is true for Android, though not for iOS.

rzolin commented 7 months ago

Any updates? I would love to export to iOS without including the code in the directories decompose-router and decompose-router-wear

xxfast commented 7 months ago

Hi. Sorry I'm struggling a little to understand a little context around your usage.

Is there a reason why you are including your dependency as implementation instead of api?

I would love to export to iOS without including the code in the directories decompose-router and decompose-router-wear

Exporting allows your iOS project to reference RouterContext and its lifecycle triggeres (resume, stop and destroy) from your AppDelegate (as outlined here).

However - alternatively, If you don't want to export decompose-router for whatever reason, you can still export only the bare essentials by creating your own exports. To do that

  1. Remove export() and api() declarations from the build script
  2. Add your own export file like
    // iosMain/MyCustomExport.kt
    fun defaultRouterContext(): RouterContext = defaultRouterContext()
    private val RouterContext.lifecycleRegistry: LifecycleRegistry get() = this.lifecycle as LifecycleRegistry
    fun RouterContext.destroy() = lifecycleRegistry.destroy()
    fun RouterContext.resume() = lifecycleRegistry.resume()
    fun RouterContext.stop() = lifecycleRegistry.stop()
    fun RouterContext.pause() = lifecycleRegistry.pause()
  3. Update your delegate on swift

    var defaultRouterContext: Decompose_routerRouterContext { delegate.holder.defaultRouterContext }
    
      var body: some Scene {
        WindowGroup {
          HomeView(routerContext: defaultRouterContext)
        }
        .onChange(of: scenePhase) { newPhase in
            switch newPhase {
            case .background: defaultRouterContext.stop()
            case .inactive: defaultRouterContext.pause()
            case .active: defaultRouterContext.resume()
            @unknown default: break
            }
        }
      }

Arguably this is more setup, and it would be easier to export decompose-router as an API dependency.

rzolin commented 7 months ago

Thanks for clarification. I am new to Android/Gradle.

Is there a reason why you are including your dependency as implementation instead of api? I think it's just my lack of understanding Gradle. Does implementation mean that the code should be present, and api that the code is included in a library/module? I was following the pattern how other libs are linked, like implementation("libs.decompose").

The general idea is to use the AppDelegate in iOS, as your sample app does. I will probably expand it with other things like sessions and etc. But when I removed the included directories "decompose-router" and "decompose-router-wear" (and reference them it stopped building for iOS.

Anyway, it looks like if I replace implementation("decompose-router") with api("decompose-router") it builds. Just having some issues with launching the simulator. But it's another issue.

xxfast commented 7 months ago

Does implementation mean that the code should be present, and api that the code is included in a library/module? I was following the pattern how other libs are linked, like implementation("libs.decompose").

Basically, api will make my library be transitively exposed to your shared module. You can learn more about the api/implementation difference here

I'll update the docs here to include instructions on exporting for future reference

Thanks