Skip to content

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

Merged
merged 15 commits into from
Aug 18, 2025

Conversation

reidbaker
Copy link
Contributor

@reidbaker reidbaker commented Aug 14, 2025

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

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement].
  • I signed the [CLA].
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is [test-exempt].
  • I followed the [breaking change policy] and added [Data Driven Fixes] where supported.
  • All existing and new tests are passing.

@reidbaker reidbaker requested a review from a team as a code owner August 14, 2025 19:16
@reidbaker reidbaker marked this pull request as draft August 14, 2025 19:16
@github-actions github-actions bot added platform-android Android applications specifically tool Affects the "flutter" command-line tool. See also t: labels. labels Aug 14, 2025
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 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.

@reidbaker reidbaker force-pushed the i166550-copy-task-part-1 branch from ce9dba9 to 29bb1bf Compare August 15, 2025 16:05
@reidbaker reidbaker requested review from gmackall and jesswrd August 15, 2025 17:21
@reidbaker reidbaker marked this pull request as ready for review August 15, 2025 17:33
@reidbaker
Copy link
Contributor Author

@gemini-code-assist[bot] please review this PR

@reidbaker
Copy link
Contributor Author

/gemini review

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 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 TODOs for efficiency, ensuring deterministic build outputs, and adhering to Kotlin style conventions.

Comment on lines +165 to +179
schemes.forEach { scheme ->
hosts.forEach { host ->
paths.forEach { path ->
appLinkSettings.deeplinks.add(
Deeplink(
scheme,
host,
path,
intentFilterCheck
)
)
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

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(
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

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

Suggested change
public fun createAppLinkSettingsFile(
fun createAppLinkSettingsFile(

Style Guide References

Footnotes

  1. The provided style guide specifies that Kotlin code should follow the Android Kotlin Style Guide, which recommends omitting the public modifier as it is the default visibility. (link)

Comment on lines +112 to +116
actionItems.forEach { action ->
if (action.attribute(MANIFEST_NAME_KEY) == "android.intent.action.VIEW") {
intentFilterCheck.hasActionView = true
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

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"
                }

Comment on lines +121 to +131
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
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

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" }

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 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.

Comment on lines +164 to +178
// 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
)
)
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

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.

Suggested change
// 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
)
)
}
}
}

Copy link
Member

@gmackall gmackall left a 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

  1. like a fragile area of the code
  2. I don't know how well it is tested.

@reidbaker
Copy link
Contributor Author

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

  1. like a fragile area of the code
  2. I don't know how well it is tested.

I authored integration tests here

during the intial conversion. Those tests use object based xml that is then written to a string so we dont have to worry about authoring valid xml.

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.

@reidbaker reidbaker requested a review from mboetger August 18, 2025 19:11
@gmackall
Copy link
Member

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

  1. like a fragile area of the code
  2. I don't know how well it is tested.

I authored integration tests here

during the intial conversion. Those tests use object based xml that is then written to a string so we dont have to worry about authoring valid xml.
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 👍

@reidbaker reidbaker added the autosubmit Merge PR when tree becomes green via auto submit App label Aug 18, 2025
@auto-submit auto-submit bot added this pull request to the merge queue Aug 18, 2025
Merged via the queue into flutter:master with commit 1e1908d Aug 18, 2025
144 of 145 checks passed
@flutter-dashboard flutter-dashboard bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Aug 18, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 19, 2025
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Aug 19, 2025
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
reidbaker added a commit to reidbaker/flutter-work that referenced this pull request Aug 19, 2025
WillBLogical pushed a commit to WillBLogical/packages that referenced this pull request Aug 20, 2025
…#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
gmackall pushed a commit to gmackall/flutter that referenced this pull request Aug 21, 2025
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.
SydneyBao pushed a commit to SydneyBao/flutter that referenced this pull request Aug 22, 2025
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.
SydneyBao pushed a commit to SydneyBao/flutter that referenced this pull request Aug 22, 2025
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
platform-android Android applications specifically tool Affects the "flutter" command-line tool. See also t: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants