raamcosta / compose-destinations

Annotation processing library for type-safe Jetpack Compose navigation with no boilerplate.
https://composedestinations.rafaelcosta.xyz
Apache License 2.0
3.14k stars 129 forks source link

Multi-module setup throws compilation error that start route does not exist. #618

Closed mainrs closed 2 months ago

mainrs commented 2 months ago
// MainGraph.kt
@NavHostGraph
annotation class MainGraph {

    @ExternalModuleDestinations<ContactdetailsModuleDestinations>
    @ExternalModuleDestinations<ContactlistModuleDestinations>
    companion object Includes
}

// ContactDetailsScreen.kt (feature module 1)
@Composable
@Destination<ExternalModuleGraph>
fun ContactDetailsScreen() {}

// ContactListScreen (feature module 2)
@Composable
@Destination<ExternalModuleGraph>(start = true)
fun ContactListScreen() {}

// Main.kt
@Composable
fun App() {
    DestinationsNavHost(
                            modifier = Modifier.fillMaxSize(),
                            navGraph = NavGraphs.root,
                            startRoute = ContactListScreenDestination,
                        )
}

The error I get is:

ksp] com.ramcosta.composedestinations.codegen.commons.IllegalDestinationsSetup: NavGraph 'MainGraph' doesn't have any start route. Use corresponding annotation with `start = true` in the Destination or nested NavGraph you want to be the start of this graph!
    at com.ramcosta.composedestinations.codegen.commons.RawNavGraphTreeBuilderKt.calculateStartRouteNavArgsTree(RawNavGraphTreeBuilder.kt:236)
    at com.ramcosta.composedestinations.codegen.commons.RawNavGraphTreeBuilderKt.calculateNavArgsAndValidate(RawNavGraphTreeBuilder.kt:150)
    at com.ramcosta.composedestinations.codegen.commons.RawNavGraphTreeBuilderKt.makeGraphTree(RawNavGraphTreeBuilder.kt:102)
    at com.ramcosta.composedestinations.codegen.commons.RawNavGraphTreeBuilderKt.makeNavGraphTrees(RawNavGraphTreeBuilder.kt:81)
    at com.ramcosta.composedestinations.codegen.writers.ModuleOutputWriter.write(ModuleOutputWriter.kt:28)
    at com.ramcosta.composedestinations.codegen.CodeGenerator.generate(CodeGenerator.kt:54)
    at com.ramcosta.composedestinations.ksp.processors.Processor.process(Processor.kt:85)
    at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$8$1.invoke(KotlinSymbolProcessingExtension.kt:306)
    at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$8$1.invoke(KotlinSymbolProcessingExtension.kt:304)
    at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:410)
    at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:304)
    at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:112)
    at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:77)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:256)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:247)
    at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:115)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:247)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:87)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:43)
    at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:165)
    at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:50)
    at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:104)
    at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:48)
    at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
    at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1523)
    at jdk.internal.reflect.GeneratedMethodAccessor93.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
    at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
    at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
    at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:840)
raamcosta commented 2 months ago

Hi @mainrs

You need to define a start for your graph at the graph level. When using ExternalModuleGraph, the start will be ignored. I will improve this so that I throw an helpful error in the future.

In any case, to do that, do:

@NavHostGraph
annotation class MainGraph {

    @ExternalModuleDestinations<ContactdetailsModuleDestinations>
    @ExternalModuleDestinations<ContactlistModuleDestinations>(
        [
            OverrideDestination(
                destination = ContactListScreenDestination::class,
                with = ExternalDestination(start = true),
            )
        ]
    )
    companion object Includes
}

You don't want feature modules to define themselves as the start, we just want to expose graphs and let the importing module decide what to us as the start of the graph.

raamcosta commented 2 months ago

Let me know if this helps!

mainrs commented 2 months ago

Ahhhh, that's how it works! Thank you very much!