feat: add Partial Custom Tabs support for Android#846
Conversation
| (customTabsOptionsMap["allowedBrowsers"] as? List<*>)?.filterIsInstance<String>().orEmpty() | ||
| } else { | ||
| (args["allowedBrowsers"] as? List<*>)?.filterIsInstance<String>().orEmpty() | ||
| } |
There was a problem hiding this comment.
Should we handle the edge scenario where user can send both customTabsOptions and allowedBrowsers ?
There was a problem hiding this comment.
Yes, this is already handled! When both are provided, customTabsOptions.allowedBrowsers wins and the top-level one is ignored. I've now extracted this into a shared utility with a KDoc comment that makes the precedence explicit. There's also a test specifically for this case (uses allowedBrowsers from customTabsOptions when both are provided).
| } | ||
|
|
||
| if (customTabsOptionsMap != null) { | ||
| val initialHeight = customTabsOptionsMap["initialHeight"] as? Int |
There was a problem hiding this comment.
Pbly extract all these checks to a Util method to be reused for both login and logout ?
There was a problem hiding this comment.
Created buildCustomTabsOptions() in the utils package. Both handlers now just do:
buildCustomTabsOptions(args)?.let { builder.withCustomTabsOptions(it) }
Much cleaner and keeps them in sync.
| ) | ||
|
|
||
| runRequestHandler(args) { _, builder -> | ||
| verify(builder).withCustomTabsOptions(any()) |
There was a problem hiding this comment.
This is validating any property being passed. Not the exact property as mentioned in the title
There was a problem hiding this comment.
Fair point. The issue is that CustomTabsOptions has all private fields with no public getters, so we can't directly assert on the built object from the handler level. I've added a dedicated CustomTabsOptionsBuilderTest that tests our utility function and uses reflection to verify the exact values (initialHeight=700, cornerRadius=16, etc.). The handler tests now just verify the routing — that withCustomTabsOptions gets called or not based on the input.
| ) | ||
|
|
||
| runRequestHandler(args) { _, builder -> | ||
| verify(builder).withCustomTabsOptions(any()) |
There was a problem hiding this comment.
Addressed — same approach as above. Value-level assertions live in CustomTabsOptionsBuilderTest.
| ) | ||
|
|
||
| runRequestHandler(args) { _, builder -> | ||
| verify(builder).withCustomTabsOptions(any()) |
There was a problem hiding this comment.
Validation is vague here ?
There was a problem hiding this comment.
Fixed in the new utility test. For example, the precedence test asserts that the packages list is exactly ["com.android.chrome"] (from customTabsOptions) and not ["org.mozilla.firefox"] (from top-level). Same for the fallback test — it asserts the exact list passed via top-level allowedBrowsers.
| ) | ||
|
|
||
| runRequestHandler(args) { _, builder -> | ||
| verify(builder).withCustomTabsOptions(any()) |
There was a problem hiding this comment.
Fixed in the new utility test. For example, the precedence test asserts that the packages list is exactly ["com.android.chrome"] (from customTabsOptions) and not ["org.mozilla.firefox"] (from top-level). Same for the fallback test — it asserts the exact list passed via top-level allowedBrowsers.
| (customTabsOptionsMap["allowedBrowsers"] as? List<*>)?.filterIsInstance<String>().orEmpty() | ||
| } else { | ||
| (args["allowedBrowsers"] as? List<*>)?.filterIsInstance<String>().orEmpty() | ||
| } |
There was a problem hiding this comment.
There is still a edge-case where if user passes customTabsOptionsMap !=null but doesn't contain allowed browser list and args["allowedBrowsers"] is passed with a valid list. In this scenario, we would completely skip extracting the allowedBrowsers. Not a hard requirement . Skip if you think this won't happen or chance are very low
📋 Changes
Adds support for Partial Custom Tabs in auth0-flutter, allowing the authentication page to be displayed as a bottom sheet or side sheet on Android instead of a full-screen browser tab.
New types:
Modified methods:
Deprecated:
Native (Kotlin):
Backward compatibility: Fully non-breaking. The new parameter defaults to null. The deprecated allowedBrowsers continues to work as a fallback when customTabsOptions is absent.
Usage:
await auth0.webAuthentication().login( customTabsOptions: const CustomTabsOptions( initialHeight: 700, toolbarCornerRadius: 16, backgroundInteractionEnabled: true, ), );📎 References
SDK-8720
🎯 Testing
Unit tests added:
Not tested: