Skip to content

Conversation

@claraf3
Copy link
Collaborator

@claraf3 claraf3 commented Dec 1, 2025

The recipe spans across two modules in order to simulate a real-world scenario where App A deeplinks into App B.

App A - com.example.nav3recipes.deeplink.advanced module
App B - advanceddeeplinkapp module

The goal of this recipe is to show how to:

  1. Create a synthetic backStack
    A synthetic backStack is created when the deep linked app is started as the root Activity of a new Task. This backStack is required in order to support proper Back & Up buttons.

  2. Support Back / Up buttons
    Back button brings user back to the previous screen, while the Up button brings user to the current screen's hierarchical parent. This requires synthetic backStack and Task stack management. See README for more info.

This PR contains three commits

  1. Adding advanceddeeplinkapp module which is the app to deep link into
  2. Pulling out common screens & resources into a :common module to be shared by basic and advanced deep link recipes
  3. Adding the com.example.nav3recipes.deeplink.advanced module

Second app to show case deeplinking with new stack and up button
Contains common code shared between app and appdeeplink for deeplink recipes
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @claraf3, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a comprehensive advanced deep linking recipe, showcasing how to handle inter-app navigation, manage Android task stacks, and construct synthetic backstacks for consistent Back and Up button functionality. The changes also involve significant architectural improvements by extracting shared UI and data logic into a new common module, making the codebase more modular and maintainable.

Highlights

  • Advanced Deep Link Recipe Implementation: Introduced a new advanced deep link recipe that simulates a real-world scenario of 'App A' deep linking into 'App B', demonstrating complex navigation patterns.
  • Synthetic BackStack Management: Implemented logic to create and manage a synthetic backStack, crucial for supporting proper Back and Up button behavior when a deep-linked app is launched as the root Activity of a new Task.
  • Refactoring and Modularity: Refactored common UI components, data models, and utility functions into a new :common module, enhancing code reusability and improving overall project structure.
  • New Application Module for Deep Link Target: Created a dedicated Android application module (:appdeeplink) to serve as 'App B', the target for advanced deep links, complete with its own manifest, navigation keys, and utility classes.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request successfully implements an advanced deep linking recipe, effectively demonstrating how to manage task stacks and create a synthetic back stack. The separation of concerns into app, appdeeplink, and common modules is a great approach that mimics a real-world scenario. The code is well-structured and the new README.md files provide excellent documentation for this complex topic. I've identified a few areas for improvement, including a potential crash due to unsafe URI parsing, some leftover debug code, and minor code refinements. My detailed comments and suggestions aim to enhance the robustness and clarity of the implementation.

@claraf3 claraf3 force-pushed the cf/advanced-recipe branch 2 times, most recently from 9f9e755 to 88c51d1 Compare December 1, 2025 17:30
The recipe spans across two modules in order to simulate a real-world scenario where `App A` deeplinks into `App B`.

The goal of this recipe is to show how to:

1. Create a synthetic backStack

A synthetic backStack is created when the deeplinked app is started as the root Activity of a new Task. This backStack is required
in order to support proper Back & Up buttons.

2. Support Back / Up buttons

Back button brings user back to the prevoius screen, while the Up button brings user to the current screen's hierarhical parent. This requires
synthetic backStack and Task stack management. See README for more info.
@claraf3 claraf3 force-pushed the cf/advanced-recipe branch from 88c51d1 to eb5de52 Compare December 1, 2025 17:33
Copy link
Collaborator

@jbw0033 jbw0033 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make this not touch the basic sample code at all? It is okay to duplicate things here if needed.

I think the most important thing for these samples is that it is clear what code is required for advanced deep linking separate from helper functions for just navigating and what code is required for advanced deep linking vs the basic sample.

We can worry about clean up later.

@claraf3
Copy link
Collaborator Author

claraf3 commented Dec 1, 2025

Can you make this not touch the basic sample code at all? It is okay to duplicate things here if needed.

I think the most important thing for these samples is that it is clear what code is required for advanced deep linking separate from helper functions for just navigating and what code is required for advanced deep linking vs the basic sample.

If anything, extracting the Screens out into a separate :common module should help separate what is deep link related and what is just for looks. The extracted screens are just UI pieces unrelated to deep link.

With that, the remaining code in deeplink.basic and deeplink.advanced and deeplink.app are core as in they contain

  1. The CreateAdvancedDeepLinkActivity to create deep links with
  2. The util package for deep link specific helpers
  3. The MainActivity that you deeplink into
  4. NavRecipeKey - the NavKeys

Is any part confusing / too blended?

@claraf3 claraf3 mentioned this pull request Dec 2, 2025
Guide that covers basic principles of deep linking and demonstrates how to apply them in Navigation 3
@claraf3 claraf3 force-pushed the cf/advanced-recipe branch from 8f2eaea to e5cfc90 Compare December 2, 2025 05:19
@claraf3
Copy link
Collaborator Author

claraf3 commented Dec 2, 2025

Added the deeplink-guide.md into this PR

@claraf3
Copy link
Collaborator Author

claraf3 commented Dec 2, 2025

Spoke with Jeremy offline. Rename some classes and packages to clarify that the second app is for advanced deep link recipe.

@claraf3 claraf3 requested a review from jbw0033 December 2, 2025 22:40
Rename second app to advanceddeeplinkapp and reuse the same deeplink.advanced package
@claraf3 claraf3 force-pushed the cf/advanced-recipe branch from 35f3b4b to b2ef58c Compare December 3, 2025 01:23
@claraf3 claraf3 merged commit 861030d into android:main Dec 3, 2025
2 checks passed
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If at all possible, I would like to avoid creating separate modules in the recipes repo. Currently each recipe has its own activity - is it not possible to demonstrate the deep linking concepts by having separate activities for each deep link app?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've explored how to represent different Task stacks without a second app module. Separate activities is not enough - we need to show the difference between an app's Activity in another App's Task, and the an app's Activity in its own Task. We do need a second app for this :(

Copy link
Collaborator Author

@claraf3 claraf3 Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you deep link to another Activity within the same module, the Activity would still be started in the original Task even if you flagged it with Intent.FLAG_ACTIVITY_NEW_TASK

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK fair enough, thanks for clarifying.

@@ -0,0 +1,175 @@
package com.example.nav3recipes.deeplink.advanced.util
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use of util anywhere is usually a code smell - everything and anything can be a utility and it usually becomes a dumping ground for miscellaneous things.

I would prefer splitting this file to BackStackHelpers.kt, UriHelpers.kt etc. Then moving these files into the package above and removing util package.

* Up button (see [navigateUp] for more on this).
*/

internal interface NavRecipeKey: NavKey {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be better named RecipeNavKey as it's a recipe-specific NavKey.

val screenTitle: String
}

internal interface NavDeepLinkRecipeKey: NavRecipeKey {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better as DeepLinkRecipeNavKey

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming there are no specific resources for the advanceddeeplinkapp, all these resources can be obtained from the app module (although better if we don't have separate modules at all).

Copy link
Collaborator Author

@claraf3 claraf3 Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a different launcher for second app on purpose so that you can tell which app the Activity is started on,

i.e. on old Task you'd see App A's launcher icon, on new Task you'd see App B's launcher icon.

import com.example.nav3recipes.deeplink.basic.ui.STRING_LITERAL_USERS
import kotlinx.serialization.Serializable

internal interface NavRecipeKey: NavKey {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RecipeNavKey


@Composable
internal fun EntryScreen(text: String, content: @Composable () -> Unit = { }) {
public fun EntryScreen(text: String, content: @Composable () -> Unit = { }) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: functions are public by default in kotlin so public is redundant unless you've enabled explicit API mode (which we haven't)


@Composable
internal fun EntryScreen(text: String, content: @Composable () -> Unit = { }) {
public fun EntryScreen(text: String, content: @Composable () -> Unit = { }) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a composable that does almost exactly this already in the app's content/Content.kt file called ContentBase. See the other recipes for usage.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should move the content package into the :common module.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Might as well since :common is there now.

implementation(libs.androidx.adaptive.layout)
implementation(platform(libs.androidx.compose.bom))

implementation(libs.kotlinx.serialization.core)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are all these dependencies needed for the common module?

internal fun FriendsList(users: List<User>) {
public fun FriendsList(
users: List<User>,
onClick: ((user: User) -> Unit)? = null
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
onClick: ((user: User) -> Unit)? = null
onUserClick: ((user: User) -> Unit)? = null

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants