1

I'm encountering an issue with Jetpack Compose deeplinks where they're consistently redirecting to the ResetPasswordRoute destination, despite having different uriPattern configurations. Here's an overview of my setup:

Manifest Configuration:

<activity
    android:name=".activity.MainActivity"
    android:exported="true"
    android:theme="@style/Theme.ServiceSpotter">

    <!-- Intent filter for email verification -->
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" android:host="handypro.pro" />
    </intent-filter>

    <!-- Intent filter for password reset -->
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" android:host="staging.handypro.pro" />
    </intent-filter>
</activity>

Navigation Setup:

@Composable
fun RootNavigation(
    isLoggedIn: Boolean,
    isWalkthroughCompleted: Boolean,
    navController: NavHostController,
    isSystemInDarkTheme: Boolean
) {
    NavHost(
        navController = navController,
        startDestination = SplashScreenDestination.route
    ) {
        splashNavGraph(
            isLoggedIn,
            isWalkthroughCompleted,
            onNavigateToHome = {
                navController.navigate(MainScreenDestination.destination) {
                    popUpTo(SplashScreenDestination.route) {
                        inclusive = true
                    }
                }
            },
            onNavigateToLogin = {
                navController.navigate(AuthDestination.route) {
                    popUpTo(SplashScreenDestination.route) {
                        inclusive = true
                    }
                }
            },
            onNavigateToWalkthrough = {
                navController.navigate(OnBoardingDestination.route) {
                    popUpTo(SplashScreenDestination.route) {
                        inclusive = true
                    }
                }
            }
        )

        walkthroughNavGraph(
            onSignInClicked = {
                navController.navigate(AuthDestination.route)
            },
            onGoogleSignInClicked = {},
            onGetStartedClicked = {
                navController.navigate(RegisterDestination.route)
            }
        )

        authNavGraph(
            onNavigateToHome = {
                navController.navigate(MainScreenDestination.destination) {
                    popUpTo(SplashScreenDestination.route) {
                        inclusive = true
                    }
                }
            },
            onNavigateToOtp = {},
            onGoogleSignInClicked = {},
            onBackClicked = {
                navController.popBackStack()
            },
            onRegisterBackClicked = {
                navController.popBackStack()
            },
            onCreateAccountClicked = {
                navController.navigate(RegisterDestination.route)
            },
            onForgotPasswordClicked = {
                navController.navigate(ForgotPasswordDestination.route)
            },
            isSystemInDarkMode = isSystemInDarkTheme,
        )


        mainScreenNavGraph(isSystemInDarkTheme)
    }
}

Auth Nav Graph

fun NavGraphBuilder.authNavGraph(
    onNavigateToHome: () -> Unit = {},
    onForgotPasswordClicked: () -> Unit = {},
    onGoogleSignInClicked: () -> Unit = {},
    onCreateAccountClicked: () -> Unit = {},
    onBackClicked: () -> Unit = {},
    onRegisterBackClicked: () -> Unit = {},
    onNavigateToOtp: () -> Unit = {},
    isSystemInDarkMode: Boolean = false,
) {
    navigation(
        startDestination = LoginDestination.destination,
        route = AuthDestination.route
    ) {
        composable(route = LoginDestination.destination,
            enterTransition = { slideIn },
            exitTransition = { slideOut }) {
            LoginRoute(
                onNavigateHome = onNavigateToHome,
                onForgotPasswordClicked = onForgotPasswordClicked,
                onGoogleSignInClicked = onGoogleSignInClicked,
                onCreateAccountClicked = onCreateAccountClicked,
                onBackClicked = onBackClicked
            )
        }

        composable(
            route = RegisterDestination.route,
            enterTransition = { slideIn },
            exitTransition = { slideOut }
        ) {
            RegisterRoute(
                onNavigateToOtp = onNavigateToOtp,
                onBackClick = onRegisterBackClicked
            )
        }

        composable(
            route = ForgotPasswordDestination.route,
            enterTransition = { slideIn },
            exitTransition = { slideOut }
        ) {
            ForgotPasswordRoute(
                isSystemInDarkMode = isSystemInDarkMode,
                onBackClick = onBackClicked,
            )
        }




        composable(
            route = VerificationDestination.route,
            deepLinks = listOf(
                navDeepLink {
                    uriPattern = BuildConfig.BASE_URL + "email/verify/{id}?expires={expires}&hash={hash}&signature={signature}"
                    action = Intent.ACTION_VIEW
                }
            ),

            arguments = listOf(
                navArgument("id") {
                    type = StringType
                    defaultValue = ""
                },
                navArgument("expires") {
                    type = StringType
                    defaultValue = ""
                },
                navArgument("hash") {
                    type = StringType
                    defaultValue = ""
                },
                navArgument("signature") {
                    type = StringType
                    defaultValue = ""
                }
            ),
            enterTransition = { slideIn },
            exitTransition = { slideOut },
        ) { backStackEntry ->
            val context = LocalContext.current
            val intent = remember(backStackEntry) {
                context.findActivity()?.intent
            }
            val uri = intent?.data

            val id = uri?.pathSegments?.get(3) ?: ""
            val expires = uri?.getQueryParameter("expires") ?: ""
            val hash = uri?.getQueryParameter("hash") ?: ""
            val signature = uri?.getQueryParameter("signature") ?: ""
            VerificationRoute(
                isSystemInDarkMode = isSystemInDarkMode,
                id = id,
                expires = expires,
                hash = hash,
                signature = signature
            )
        }


        composable(
            route = ResetPasswordDestination.route,
            deepLinks = listOf(
                navDeepLink {
                    uriPattern = BuildConfig.BASE_URL + "password/email?token={token}&email={email}"
                    action = Intent.ACTION_DEFAULT

                }
            ),
            arguments = listOf(
                navArgument("token") {
                    type = StringType
                    defaultValue = ""
                },
                navArgument("email") {
                    type = StringType
                    defaultValue = ""
                }
            ),
            enterTransition = { slideIn },
            exitTransition = { slideOut }
        ) { backStackEntry ->
            val context = LocalContext.current
            val intent = remember(backStackEntry) {
                context.findActivity()?.intent
            }
            val uri = intent?.data

            val token = uri?.getQueryParameter("token") ?: ""
            val email = uri?.getQueryParameter("email") ?: ""
            ResetPasswordRoute(
                isSystemInDarkMode = false,
                onBackClick = onBackClicked,
                token = token,
                email = email
            )
        }
    }

}

fun Context.findActivity(): Activity? = when (this) {
    is Activity -> this
    is ContextWrapper -> baseContext.findActivity()
    else -> null
}

MainActivity Setup

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    private val viewModel by viewModels<MainVewModel>()
    val notificationLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) {

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (
            isAtLeastApi(Build.VERSION_CODES.TIRAMISU) &&
            hasPermission(Manifest.permission.POST_NOTIFICATIONS).not()
        ) {
            notificationLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
        }
        setContent {
            val isLoggedIn by viewModel.isLoggedIn.collectAsStateWithLifecycle()
            val isWalkthroughCompleted by viewModel.walkthroughCompleted.collectAsStateWithLifecycle()
            ServiceSpotterTheme {
                val navController = rememberNavController()
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    RootNavigation(
                        isLoggedIn = isLoggedIn,
                        navController = navController,
                        isWalkthroughCompleted = isWalkthroughCompleted,
                        isSystemInDarkTheme = isSystemInDarkTheme()
                    )
                }
            }
        }
    }
}

Issue Description:

Despite having different uriPattern configurations for email verification and password reset, Jetpack Compose deeplinks consistently redirect to the ResetPasswordRoute destination, regardless of the URL clicked. The BASE_URL in BuildConfig is "https://staging.handypro.pro/api/", but deeplinks from both https://handypro.pro and https://staging.handypro.pro domains redirect to the same destination.

Expected Behavior:

I expect deeplinks to correctly navigate to the appropriate destinations based on their uriPattern configurations, directing to either email verification or password reset routes accordingly.

Additional Context:

  1. I've verified that the deeplink URLs match the defined uriPattern configurations.
  2. The domain `handypro.pro` and `staging.handypro.pro` have been verified in the server via `assetlinks.json`
  3. The issue persists across different devices and environments.
  4. Any insights or suggestions on debugging steps or potential solutions would be greatly appreciated.
  5. In addition I tested the links both using emulator and physical devices and also used android studio App Link Assistant to test but all result to same behavior

Here are the screenshots for the screens

[![ResetPasswordRoute Screenshot][1]][1] [![Verify Email Link][2]][2] [![Reset Password Link][3]][3]

ResetPasswordRoute Verify Email Link Reset Password Link
Image 1 Image 2 Image 3

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.