Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deeplinks and BuildConfig, code not generated #178

Open
JustJerem opened this issue Jul 6, 2022 · 13 comments
Open

Deeplinks and BuildConfig, code not generated #178

JustJerem opened this issue Jul 6, 2022 · 13 comments
Labels
enhancement New feature or request feedback needed Extra attention is needed

Comments

@JustJerem
Copy link

JustJerem commented Jul 6, 2022

Hello Rafael,
First of all, I would like to thank you for this great library. A real pleasure to use every day ! 馃憦

My issue is that I trying to use DeepLinks with a changing configuration, thanks to BuildConfig.
The application compiles, but it crashes when launching on the phone with this error:

java.lang.IllegalStateException: The NavDeepLink must have an uri, action, and/or mimeType.

And indeed, looking in the generated code, nothing appears in the Deeplinks variable.

override val deepLinks get() = listOf(
	navDeepLink {
	}
)

This works perfectly if I hardcode the URL in the right field, but when I pass it in the BuildConfig, it doesn't generate. And I need it because our application has 4 environments.

Current state :

DashboardScreen.kt

const val DEEPLINK_DASHBOARD_URI = "https://website.com/dashboard"
@Destination(deepLinks = [
    DeepLink(action = DEEPLINK_DASHBOARD_URI)
])
@Composable
fun DashboardScreen(
    viewModel: DashboardViewModel = hiltViewModel(),
    navigator: DestinationsNavigator,
) {
...
}

What I try to achieve :

build.gradle

buildConfigField "String", "DEEPLINK_DASHBOARD_URI", "\"https://website.com/dashboard\""

DashboardScreen.kt

@Destination(deepLinks = [
    DeepLink(action = BuildConfig.DEEPLINK_DASHBOARD_URI)
])
@Composable
fun DashboardScreen(
    viewModel: DashboardViewModel = hiltViewModel(),
    navigator: DestinationsNavigator,
) {
...
}

Is there a solution ?
Sorry if I missed something in the documentation.

@raamcosta raamcosta added the enhancement New feature or request label Jul 6, 2022
@raamcosta
Copy link
Owner

raamcosta commented Jul 6, 2022

Hi 馃憢

Thank you for the kind words, glad you like it!

I believe this can be due to ksp task running before the build config class is created when you build the app. But I see how good this could be, I鈥檒l leave this open as a future enhancement and I鈥檒l try to check if I can solve it somehow 馃

In the meantime, one thing you can do is, right before calling DestinationsNavHost, you copy your root NavGraph (since it is a data class) and reach to the Destination you want to add the deep link, then call this (line 112 - withDeepLink).

https://github.com/raamcosta/compose-destinations/blob/main/compose-destinations/src/main/java/com/ramcosta/composedestinations/dynamic/DynamicDestinationSpec.kt

This will create a copy of the generated Destination with the passes in deep link. And this way you can set deep links at runtime.
Remember you have to change the NavGraph you pass to DestinationsNavHost, and replace the destination with this copy of itself in the destinations list that the NavGraph contains. If you want I can show you an example later on.

This method was not intended for this specific use case but I believe it should work, but please let me know 馃檪

@MichielDroid
Copy link

MichielDroid commented Aug 19, 2022

Hiya!
@raamcosta Can you show us an example please? I don't quite understand what you mean.

I have an app with multiple build flavors, and the scheme for the deeplinks is based on the build.FLAVOR.

I wrote this code based on your answer. Works for a little bit, until I open a notification with a deeplink. Then it throws the following exception:

 java.lang.IllegalArgumentException: Registering multiple navigation graphs with same route ('newRoot') is not allowed.
        const val GENERAL_ALERT_DEEPLINK_URL = "${BuildConfig.FLAVOR}://generalAlert"
        const val ALERT_DEEPLINK_URL = "${BuildConfig.FLAVOR}://alert/{alertId}"
        private var navGraph: NavGraph? = null


 internal fun regenerateRootNavGraph(): NavGraph {
        if (navGraph == null) {
            val newRoot = NavGraphs.root.copy(route = "newRoot")

            val destinations = newRoot.destinations.toMutableList()
            destinations.remove(SingleAlertDetailScreenDestination)
            destinations.remove(GeneralAlertDetailsDestination)

            SingleAlertDetailScreenDestination.withDeepLink {
                uriPattern = ALERT_DEEPLINK_URL
            }.routedIn(newRoot)

            GeneralAlertDetailsDestination.withDeepLink {
                uriPattern = GENERAL_ALERT_DEEPLINK_URL
            }.routedIn(newRoot)

            navGraph = newRoot.copy(destinations = destinations)
        }
        return navGraph!!
    }

@cvb941
Copy link

cvb941 commented Sep 5, 2022

Hi, you can try adding the code below to your build.gradle, it should make the BuildConfig class available to KSP.

ksp {
    allowSourcesFromOtherPlugins = true
}

@ryanholden8
Copy link

Hi, you can try adding the code below to your build.gradle, it should make the BuildConfig class available to KSP.

ksp {
    allowSourcesFromOtherPlugins = true
}

I get this build error when using this option:

Circular dependency between the following tasks:
:app:kaptDebugKotlin
\--- :app:kaptGenerateStubsDebugKotlin
     \--- :app:kspDebugKotlin
          \--- :app:kaptDebugKotlin (*)

@cvb941 - Where you able to get it working? If so, mind sharing a few snippets of the code?

@cvb941
Copy link

cvb941 commented Sep 6, 2022

Sorry, I just enabled that option and it worked, nothing special. I am not using kapt though.

@MichielDroid
Copy link

@ryanholden8 Did you find a solution or maybe a workaround? I think my project requires kapt for Hilt, and ksp for destinations. I also need to set ksp. allowSourcesFromOtherPlugins to true because we have build flavors and we need deeplinks based on build flavors.

I feel like there must be a way to have ksp_gradleTask.dependsOn(BuildConfig_gradleTask), but I'm not very familiar with ksp and gradle.

@ryanholden8
Copy link

@MiieL - We did not, we duplicated the references. Not ideal but not much of a choice.

@javierpe
Copy link

javierpe commented May 22, 2023

@raamcosta I have many issues with this. Did you solve it?

How can I help you to solve it to send Pull Request?

@ninovanhooff
Copy link

ninovanhooff commented Sep 1, 2023

The way I solved this was to register a single pattern as documented, using an app scheme, for example:

Screen @destination annotation

deepLinks = [
        DeepLink(
            uriPattern = "myapp://groups/join?token={token}",
        ),
    ]

Then, in the MainActivity, I used traditional url- handling to transform environment (prod, dev, acc) specific https urls in to the registered myapp:// pattern and feed that into the navigator

MainActivity kotlin:


 override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
          val navController = rememberAnimatedNavController()
          DeeplinkHandler(navController)
          
         AppNavigation(
            navController = navController,
            navGraph = NavGraphs.root,
            startRoute = viewModel.startRoute,
          )

    }

    deeplinkManager.onNewIntent(intent) // important!
}

 override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        this.intent = intent
        deeplinkManager.onNewIntent(intent)
    }

DeeplinkManager

@Singleton
class DeeplinkManager @Inject constructor(
    private val deeplinkParser: DeeplinkParser,
) {

    private val _deeplinkFlow = MutableStateFlow(Uri.EMPTY)
    val deeplinkFlow: Flow<Uri> = _deeplinkFlow

    fun onNewIntent(intent: Intent?) {
        Napier.d {
            "onNewIntent received with data ${intent?.data} and extras ${
                intent?.extras?.keySet()?.toList()
            }"
        }
        if (intent == null) return

        deeplinkParser.parseIntent(intent)?.let {
            _deeplinkFlow.value = it
        }
    }

    fun onDeeplinkHandled() {
        _deeplinkFlow.value = Uri.EMPTY
    }
}

DeeplinkParser: A class transforming https://<dev | acc | prod>.mycompany.com/join?token=ABCDEF to myapp://join/token={token}

DeeplinkHandler

@Composable
    private fun DeeplinkHandler(navController: NavHostController) {
        val deeplink by deeplinkManager.deeplinkFlow.collectAsStateWithLifecycle(
            initialValue = Uri.EMPTY
        )

        if (deeplink != Uri.EMPTY) {
            Napier.d { "Navigating to deeplink $deeplink" }
            navController.navigate(deeplink)
            deeplinkManager.onDeeplinkHandled()
        }
    }

@amaranthius
Copy link

A bit late to the party, but just wanted to share a workaround that worked for me. Might be helpful to some folks out there.

I've basically used this suggestion:

ksp {
    allowSourcesFromOtherPlugins = true
}

but with this extra step:

tasks.withType<com.google.devtools.ksp.gradle.KspTaskJvm> {
    mustRunAfter("generateDebugBuildConfig")
}

This little block makes sure that BuildConfig would've been generated by the time KSP starts to do its thing. Bear in mind that, depending on your particular case, there might be more tasks that need to be added to mustRunAfter.

Of course, having an elegant out-of-the-box solution would be way better, but this is not too ugly for a workaround. Hope. this helps!

@pm-nchain
Copy link

@raamcosta is it possible to improve error message in this situation? Error like following is kind of misleading.

 java.lang.IllegalArgumentException: Deep link null can't be used to open destination Destination(0xa4735ba4) route=verify_email_username/{email}/{code}.
 Following required arguments are missing: [email, code]
 	at androidx.navigation.NavDestination.addDeepLink(NavDestination.kt:355)
 	at androidx.navigation.compose.NavGraphBuilderKt.composable(NavGraphBuilder.kt:104)
 	at androidx.navigation.compose.NavGraphBuilderKt.composable$default(NavGraphBuilder.kt:78)
 	at com.ramcosta.composedestinations.spec.DestinationStyleKt.addActivityDestination(DestinationStyle.kt:158)
 	at com.ramcosta.composedestinations.DefaultNavHostEngine.composable(DefaultNavHostEngine.kt:123)
 	at com.ramcosta.composedestinations.DestinationsNavHostKt.addNavGraphDestinations(DestinationsNavHost.kt:115)
 	at com.ramcosta.composedestinations.DestinationsNavHostKt.access$addNavGraphDestinations(DestinationsNavHost.kt:1)
 	at com.ramcosta.composedestinations.DestinationsNavHostKt$addNestedNavGraphs$1$1$1.invoke(DestinationsNavHost.kt:142)
 	at com.ramcosta.composedestinations.DestinationsNavHostKt$addNestedNavGraphs$1$1$1.invoke(DestinationsNavHost.kt:141)

@raamcosta raamcosta added the feedback needed Extra attention is needed label Mar 29, 2024
@raamcosta
Copy link
Owner

Does anyone know if this is still the case?

@amaranthius
Copy link

amaranthius commented Apr 3, 2024

Does anyone know if this is still the case?

@raamcosta Is it supposed to be working? Haven't tried since I've put in place the workaround I've mentioned earlier

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request feedback needed Extra attention is needed
Projects
None yet
Development

No branches or pull requests

9 participants