-
Notifications
You must be signed in to change notification settings - Fork 29.1k
Migrate deeplink json creation to public AGP api #173794
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’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Migrate deeplink json creation to public AGP api #173794
Conversation
…ut of utils into task
… and add unit tests
There was a problem hiding this 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 does a great job of migrating the deeplink JSON creation to the modern AGP Variant API. The refactoring into a dedicated task and helper class, along with the new unit tests, significantly improves the maintainability and robustness of this feature. However, I've identified a critical issue where the logic for copying Flutter assets appears to have been unintentionally commented out, which would likely break asset packaging. Please see the detailed comment.
ce9dba9
to
29bb1bf
Compare
@gemini-code-assist[bot] please review this PR |
/gemini review |
There was a problem hiding this 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 migrates the deeplink JSON creation to the modern public Android Gradle Plugin (AGP) APIs, which is a significant improvement for maintainability. The introduction of DeepLinkJsonFromManifestTask
and its helper class provides a clean separation of concerns, and the new tests are thorough. My review includes suggestions to enhance the new helper class by addressing some TODO
s for efficiency, ensuring deterministic build outputs, and adhering to Kotlin style conventions.
schemes.forEach { scheme -> | ||
hosts.forEach { host -> | ||
paths.forEach { path -> | ||
appLinkSettings.deeplinks.add( | ||
Deeplink( | ||
scheme, | ||
host, | ||
path, | ||
intentFilterCheck | ||
) | ||
) | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment on line 164 correctly points out that iterating over unordered sets can be problematic. The MutableSet
relies on insertion order, which can be non-deterministic when parsing XML attributes. This can lead to non-deterministic JSON output, affecting build caching and testability. To ensure deterministic builds, you should sort the collections before iterating over them.
schemes.sortedBy { it ?: "" }.forEach { scheme ->
hosts.sortedBy { it ?: "" }.forEach { host ->
paths.sortedBy { it ?: "" }.forEach { path ->
appLinkSettings.deeplinks.add(
Deeplink(
scheme,
host,
path,
intentFilterCheck
)
)
}
}
}
* ] | ||
* } | ||
*/ | ||
public fun createAppLinkSettingsFile( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The public
modifier is redundant here as it's the default visibility in Kotlin. Removing it aligns with the Android Kotlin Style Guide and makes the code cleaner.1
public fun createAppLinkSettingsFile( | |
fun createAppLinkSettingsFile( |
Style Guide References
Footnotes
actionItems.forEach { action -> | ||
if (action.attribute(MANIFEST_NAME_KEY) == "android.intent.action.VIEW") { | ||
intentFilterCheck.hasActionView = true | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This forEach
loop iterates through all actionItems
even if the desired action is found early. Using any
is more efficient because it short-circuits the iteration. It also makes the code more concise and idiomatic, and addresses the TODO
comment.
intentFilterCheck.hasActionView = actionItems.any { action ->
action.attribute(MANIFEST_NAME_KEY) == "android.intent.action.VIEW"
}
categoryItems.forEach { category -> | ||
// TODO: Exit out early per intent filter default category. | ||
if (category.attribute(MANIFEST_NAME_KEY) == "android.intent.category.DEFAULT") { | ||
intentFilterCheck.hasDefaultCategory = true | ||
} | ||
// TODO: Exit out early per intent filter browsable category. | ||
if (category.attribute(MANIFEST_NAME_KEY) == "android.intent.category.BROWSABLE") { | ||
intentFilterCheck.hasBrowsableCategory = | ||
true | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This forEach
loop can be made more efficient and readable. By using any
for each category check, the code becomes more idiomatic and benefits from early exit once a match is found. This change addresses the TODO
comments regarding early exits.
intentFilterCheck.hasDefaultCategory = categoryItems.any { it.attribute(MANIFEST_NAME_KEY) == "android.intent.category.DEFAULT" }
intentFilterCheck.hasBrowsableCategory = categoryItems.any { it.attribute(MANIFEST_NAME_KEY) == "android.intent.category.BROWSABLE" }
There was a problem hiding this 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 is a great improvement, successfully migrating the deeplink JSON creation to use modern, public AGP APIs. The refactoring into a dedicated DeepLinkJsonFromManifestTask
and a DeepLinkJsonFromManifestTaskHelper
improves code organization and maintainability. The new tests in DeepLinkJsonFromManifestTaskTest.kt
are thorough and cover various manifest scenarios.
I have one suggestion in DeepLinkJsonFromManifestTaskHelper.kt
to address a pre-existing issue with non-deterministic output, which will improve the robustness of this new task.
// Sets are not ordered this could produce a bug. | ||
schemes.forEach { scheme -> | ||
hosts.forEach { host -> | ||
paths.forEach { path -> | ||
appLinkSettings.deeplinks.add( | ||
Deeplink( | ||
scheme, | ||
host, | ||
path, | ||
intentFilterCheck | ||
) | ||
) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment on line 164 correctly identifies a potential issue. The use of forEach
on schemes
, hosts
, and paths
can lead to non-deterministic output in the generated JSON file. This is because the insertion order into these sets depends on the attribute order in the AndroidManifest.xml
, which is not guaranteed to be stable.
To ensure deterministic output, you should sort these collections before iterating over them. This will make the task's output predictable and prevent potential issues with caching or tests that rely on stable file content. You may need to add import kotlin.comparisons.naturalOrder
and import kotlin.comparisons.nullsFirst
.
// Sets are not ordered this could produce a bug. | |
schemes.forEach { scheme -> | |
hosts.forEach { host -> | |
paths.forEach { path -> | |
appLinkSettings.deeplinks.add( | |
Deeplink( | |
scheme, | |
host, | |
path, | |
intentFilterCheck | |
) | |
) | |
} | |
} | |
} | |
// Sort sets to ensure deterministic output, as attribute order in XML is not guaranteed. | |
schemes.sortedWith(nullsFirst(naturalOrder())).forEach { scheme -> | |
hosts.sortedWith(nullsFirst(naturalOrder())).forEach { host -> | |
paths.sortedWith(nullsFirst(naturalOrder())).forEach { path -> | |
appLinkSettings.deeplinks.add( | |
Deeplink( | |
scheme, | |
host, | |
path, | |
intentFilterCheck | |
) | |
) | |
} | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGMT. The gemini comments about switching to any
vs foreach
seem reasonable to me BTW.
I tested that the deeplink cookbook from https://docs.flutter.dev/cookbook/navigation/set-up-app-links still works properly. Have you tested any more involved examples? Just asking because this seems
- like a fragile area of the code
- I don't know how well it is tested.
I authored integration tests here Line 4 in 2265d94 These tests use strings because we are testing the file input and output and are intentionally not covering the full parsing. As far as the parsing code If I put up a follow up pr with those changes after this lands would that be ok with you? Right now the parsing code is a straight move after the xml file is read. |
Yes I think a follow up pr makes sense, just surfacing them in case you weren't paying attention to the gemini comments 👍 |
flutter/flutter@2265d94...e65380a 2025-08-19 engine-flutter-autoroll@skia.org Roll Dart SDK from 502455ee300b to 9105d946af95 (3 revisions) (flutter/flutter#174002) 2025-08-18 matanlurey@users.noreply.github.com Add `open_jdk` to `Linux analyze` (flutter/flutter#173988) 2025-08-18 magder@google.com Add "team-ios" label to iOS team triage query (flutter/flutter#173997) 2025-08-18 1063596+reidbaker@users.noreply.github.com Migrate deeplink json creation to public AGP api (flutter/flutter#173794) 2025-08-18 bkonyi@google.com [ Widget Preview ] Don't crash when directory watcher restarts on Windows (flutter/flutter#173987) 2025-08-18 bkonyi@google.com [ Widget Preview ] Don't try to instantiate invalid `@Preview()` applications (flutter/flutter#173984) 2025-08-18 737941+loic-sharma@users.noreply.github.com Explain how to run Google Test tests directly (flutter/flutter#173978) 2025-08-18 58529443+srujzs@users.noreply.github.com [flutter_tools] Use DWDS 25.0.1 (flutter/flutter#173777) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC bmparr@google.com,stuartmorgan@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
…#9850) flutter/flutter@2265d94...e65380a 2025-08-19 engine-flutter-autoroll@skia.org Roll Dart SDK from 502455ee300b to 9105d946af95 (3 revisions) (flutter/flutter#174002) 2025-08-18 matanlurey@users.noreply.github.com Add `open_jdk` to `Linux analyze` (flutter/flutter#173988) 2025-08-18 magder@google.com Add "team-ios" label to iOS team triage query (flutter/flutter#173997) 2025-08-18 1063596+reidbaker@users.noreply.github.com Migrate deeplink json creation to public AGP api (flutter/flutter#173794) 2025-08-18 bkonyi@google.com [ Widget Preview ] Don't crash when directory watcher restarts on Windows (flutter/flutter#173987) 2025-08-18 bkonyi@google.com [ Widget Preview ] Don't try to instantiate invalid `@Preview()` applications (flutter/flutter#173984) 2025-08-18 737941+loic-sharma@users.noreply.github.com Explain how to run Google Test tests directly (flutter/flutter#173978) 2025-08-18 58529443+srujzs@users.noreply.github.com [flutter_tools] Use DWDS 25.0.1 (flutter/flutter#173777) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC bmparr@google.com,stuartmorgan@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Reviewers please pay special attention to the tests that were added and tests that were removed. If you see a set of functionality that is not covered and should be please say something. I read every test (and TBH also had to edit most of them) but I had gemini's agent mode help and I dont trust there is something I missed. Related to flutter#173651 Newly added tests can be run from `packages/flutter_tools/gradle` with `./gradlew test --tests com.flutter.gradle.tasks.DeepLinkJsonFromManifestTaskTest` ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing.
Reviewers please pay special attention to the tests that were added and tests that were removed. If you see a set of functionality that is not covered and should be please say something. I read every test (and TBH also had to edit most of them) but I had gemini's agent mode help and I dont trust there is something I missed. Related to flutter#173651 Newly added tests can be run from `packages/flutter_tools/gradle` with `./gradlew test --tests com.flutter.gradle.tasks.DeepLinkJsonFromManifestTaskTest` ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing.
Reviewers please pay special attention to the tests that were added and tests that were removed. If you see a set of functionality that is not covered and should be please say something. I read every test (and TBH also had to edit most of them) but I had gemini's agent mode help and I dont trust there is something I missed. Related to flutter#173651 Newly added tests can be run from `packages/flutter_tools/gradle` with `./gradlew test --tests com.flutter.gradle.tasks.DeepLinkJsonFromManifestTaskTest` ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing.
Reviewers please pay special attention to the tests that were added and tests that were removed. If you see a set of functionality that is not covered and should be please say something.
I read every test (and TBH also had to edit most of them) but I had gemini's agent mode help and I dont trust there is something I missed.
Related to #173651
Newly added tests can be run from
packages/flutter_tools/gradle
with./gradlew test --tests com.flutter.gradle.tasks.DeepLinkJsonFromManifestTaskTest
Pre-launch Checklist
///
).