flutter-url-launcher-engineering
Skill8 starsChanged 49 days agoMITView on GitHub
Design, implement, review, debug, secure, test, migrate, and optimize production Flutter external URI launching with url_launcher. Use this skill whenever a Flutter task involves launchUrl, LaunchMode, websites, mailto/tel/sms, maps, app stores, custom schemes, Universal/App Links, Android package visibility, iOS LSApplicationQueriesSchemes, web popup/user-gesture behavior, desktop file URIs, URI validation/encoding, OAuth or payment handoff boundaries, launch failures, deprecated launch/canLaunch APIs, or deciding whether url_launcher is the right integration mechanism.
In this repository70 other files it gives its agents.
- camerawesome-flutterSkill
- dart-args-production-cliSkill
- dart-flutter-equatable-engineeringSkill
- dart-flutter-ffi-systems-engineeringSkill
- dart-metaSkill
- document-camera-frameSkill
- document-scan-flutterSkill
- figma-to-flutter-expertSkill
- flutter-animations-production-engineeringSkill
- flutter-app-hardening-reverse-engineeringSkill
- flutter-app-icon-engineeringSkill
- flutter-application-security-engineeringSkill
- flutter-app-links-deep-link-engineeringSkill
- flutter-cachingSkill
- flutter-concurrency-memory-performanceSkill
- flutter-confetti-engineeringSkill
- flutter-dart-package-engineeringSkill
- flutter-design-system-from-design-mdSkill
- flutter-dio-cache-interceptor-engineeringSkill
- flutter-dio-engineeringSkill
- flutter-doc-scannerSkill
- flutter-drift-persistence-engineeringSkill
- flutter-expert-tipsSkill
- flutter-firebase-analyticsSkill
- flutter-firebase-core-engineeringSkill
- flutter-firebase-messagingSkill
- flutter-get-it-injectable-dependency-injectionSkill
- flutter-go-router-engineeringSkill
- flutter-hive-persistence-engineeringSkill
- flutter-image-performanceSkill
- flutter-image-picker-production-engineeringSkill
- flutter-lints-static-analysis-engineeringSkill
- flutter-local-notifications-production-engineeringSkill
- flutter-memory-leak-engineeringSkill
- flutter-mobile-developmentSkill
- flutter-motion-engineeringSkill
- flutter-multi-environmentSkill
- flutter-native-splash-engineeringSkill
- flutter-package-plugin-selectionSkill
- flutter-path-provider-production-storageSkill
- flutter-performance-jank-optimizationSkill
- flutter-permission-handler-engineeringSkill
- flutter-pinputSkill
- flutter-production-build-releaseSkill
- flutter-production-engineeringSkill
- flutter-production-loggingSkill
- flutter-pro-image-editor-engineeringSkill
- flutter-secure-storage-security-engineeringSkill
- flutter-sentry-engineeringSkill
- flutter-share-plus-engineeringSkill
- flutter-skeletonizer-engineeringSkill
- flutter-squadron-concurrency-engineeringSkill
- flutter-supabase-engineeringSkill
- flutter-svg-engineeringSkill
- flutter-ui-performance-engineeringSkill
- flutter-video-player-production-engineeringSkill
- flutter-webview-production-engineeringSkill
- freezed-production-engineeringSkill
- html-to-flutterSkill
- hydrated-blocSkill
- The first 60 shown.
The file
Show its rules and text
We split this skill into the rules it gives an agent
57 rules. About 0 are common, 29 are distinctive, and 28 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈29
Distinctive · estimate
This author's own choices. Worth a look.
28
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 10
Treat url_launcher as a platform handoff API, not a browser framework or proof that an external business action completed.
- Very specific: line 26
Use launchUrl(Uri, ...); do not introduce deprecated string-based launch/canLaunch APIs.
- Very specific: line 27
Use Uri.parse only for known complete trusted constants.
- Distinctive: line 30
Treat API, CMS, chat, QR, push, deep-link, remote-config, and user-provided destinations as untrusted.
- Distinctive: line 32
Prefer HTTPS for ordinary web navigation when policy requires trusted web transport.
- Distinctive: line 33
Do not put credentials, session secrets, private keys, or server secrets into externally launched URLs unless a reviewed protocol …
- Distinctive: line 34
Do not log complete sensitive URIs, query strings, or fragments.
- Very specific: line 37
Prefer attempting launchUrl and handling failure when capability detection is not needed for pre-launch UI.
- Very specific: line 38
Treat canLaunchUrl as a platform-sensitive query that may return false because visibility/querying is restricted.
- Very specific: line 39
Use supportsLaunchMode only when automatic fallback would violate product requirements.
- Very specific: line 40
Add Android <queries> and iOS LSApplicationQueriesSchemes only when the app genuinely needs capability querying.
- Very specific: line 42
Never interpret launchUrl == true as proof of email delivery, call completion, SMS send, OAuth authentication, payment, purchase, …
- Distinctive: line 44
Use verified HTTPS/App Link/Universal Link mechanisms when target identity matters.
- Very specific: line 45
Keep internal Flutter navigation in the router/Navigator, not url_launcher.
- Distinctive: line 46
Use a dedicated WebView/browser integration when the app needs rich embedded browsing controls.
- Distinctive: line 47
Keep external navigation as a presentation/platform side effect unless a deliberate application abstraction adds security, fallback, …
- Distinctive: line 49
Test Android, iOS, web, and desktop behavior independently where supported.
- Distinctive: line 53
Read only what the task needs:
- Distinctive: line 94
Before refactoring, write one sentence describing the intended user-visible action, for example:
- Distinctive: line 101
Use it when the operation is naturally:
- Distinctive: line 115
Do not use it for:
- Distinctive: line 159
Do not concatenate dynamic path/query text into a URL string.
- Distinctive: line 187
Use this decision table:
- Very specific: line 196
do not claim platformDefault means a fixed surface.
- Distinctive: line 197
do not use in-app modes for non-HTTP(S).
- Very specific: line 198
if automatic fallback is acceptable, skip supportsLaunchMode.
- Distinctive: line 199
if exact surface is mandatory, verify support and define fallback.
- Very specific: line 208
Use canLaunchUrl only when advance knowledge changes UI or flow.
- Very specific: line 215
Do not interpret it as authoritative installation/security proof.
- Very specific: line 216
Avoid repeated canLaunchUrl → launchUrl pairs when the preflight adds no product value.
- Very specific: line 220
If querying schemes on Android 11+, add narrowly scoped <queries> entries.
- Distinctive: line 221
Do not broaden package visibility unnecessarily.
- Very specific: line 223
If canLaunchUrl must query nonstandard schemes, add the required scheme names to LSApplicationQueriesSchemes.
- Distinctive: line 224
Do not confuse query permission with launch authorization or unique app identity.
- Very specific: line 228
use structured Uri construction for dynamic data.
- Distinctive: line 233
do not assume every mail client handles optional fields identically.
- Very specific: line 238
do not add direct-call permissions merely for a tel: handoff.
- Very specific: line 240
use recipient-only sms:<recipient> as the portable baseline.
- Very specific: line 241
do not promise cross-platform message-body prefill through url_launcher.
- Distinctive: line 252
Do not treat custom-scheme ownership as verified identity.
- Distinctive: line 258
Do not assume generic HTTPS targets a specific app or browser.
- Distinctive: line 261
Do not extrapolate Android WebView semantics to iOS.
- Very specific: line 265
Do not use web canLaunchUrl as installed-app detection.
- Very specific: line 270
Never launch external input directly after Uri.parse/Uri.tryParse without policy checks.
- Distinctive: line 314
Do not swallow a user-requested failure silently.
- Distinctive: line 317
Never write logic equivalent to:
- Very specific: line 336
Create a thin ExternalLinkService/adapter when it adds real value:
- Very specific: line 342
Do not build several forwarding layers that merely mirror launchUrl.
- Very specific: line 343
Keep BuildContext out of long-lived launcher services.
- Very specific: line 352
If UI awaits launch before showing fallback, check context.mounted before using context.
- Very specific: line 391
If canLaunchUrl says false but launch may work, inspect Android package visibility or iOS query-scheme configuration and reconsider whether …
- Distinctive: line 392
If the wrong app opens, remember generic handler selection is OS-owned.
- Distinctive: line 395
Before replacing legacy code, inspect:
- Distinctive: line 412
Do not mechanically translate old flags by name.
- Very specific: line 415
When reviewing or implementing url_launcher, report in this order:
- Distinctive: line 428
Do not approve a launch flow until all applicable answers are known:
- Distinctive: line 445
If any critical answer is unknown, treat the integration as incomplete rather than adding more launcher calls.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Flutter url_launcher Engineering
Mission
Treat url_launcher as a platform handoff API, not a browser framework or proof that an external business action completed.
Use this loop:
identify external action
→ decide whether url_launcher is appropriate
→ classify destination trust
→ construct a safe Uri
→ choose an intentional LaunchMode
→ query capability only if it changes UX
→ launch once
→ handle false/exception with fallback
→ verify downstream business completion separately
→ test platform-specific behaviorNon-negotiable rules
- Use
launchUrl(Uri, ...); do not introduce deprecated string-basedlaunch/canLaunchAPIs. - Use
Uri.parseonly for known complete trusted constants; use structuredUriconstruction for dynamic components. - For dynamic HTTP(S) query values, use
queryParametersrather than string concatenation. - For non-HTTP(S) schemes such as
mailto:, follow the package's documentedquery+Uri.encodeComponentworkaround. - Treat API, CMS, chat, QR, push, deep-link, remote-config, and user-provided destinations as untrusted.
- Allow only intentional schemes and validate exact hosts when the destination is product-controlled.
- Prefer HTTPS for ordinary web navigation when policy requires trusted web transport; reject disallowed HTTP rather than silently rewriting arbitrary URLs.
- Do not put credentials, session secrets, private keys, or server secrets into externally launched URLs unless a reviewed protocol explicitly requires it.
- Do not log complete sensitive URIs, query strings, or fragments.
- Choose
LaunchModefrom required UX semantics. Do not rely on today's observedplatformDefaultbehavior when the surface matters. - Both in-app modes require HTTP(S); do not use them for
tel:,mailto:,sms:, or arbitrary custom schemes. - Prefer attempting
launchUrland handling failure when capability detection is not needed for pre-launch UI. - Treat
canLaunchUrlas a platform-sensitive query that may return false because visibility/querying is restricted. - Use
supportsLaunchModeonly when automatic fallback would violate product requirements. - Add Android
<queries>and iOSLSApplicationQueriesSchemesonly when the app genuinely needs capability querying. - On web, preserve direct user-gesture context for new tab/window launches.
- Never interpret
launchUrl == trueas proof of email delivery, call completion, SMS send, OAuth authentication, payment, purchase, installation, or any other external result. - Custom schemes are routing mechanisms, not verified application identity.
- Use verified HTTPS/App Link/Universal Link mechanisms when target identity matters.
- Keep internal Flutter navigation in the router/Navigator, not
url_launcher. - Use a dedicated WebView/browser integration when the app needs rich embedded browsing controls.
- Keep external navigation as a presentation/platform side effect unless a deliberate application abstraction adds security, fallback, observability, or testing value.
- Prevent harmful duplicate launches when rapid repeated taps could open multiple windows/apps.
- Test Android, iOS, web, and desktop behavior independently where supported.
- Inspect resolved federated platform package versions before making implementation-specific claims.
Reference routing
Read only what the task needs:
| Need | Read |
Decide whether url_launcher fits; URI construction/encoding; launchUrl, canLaunchUrl, modes; HTTP/mail/tel/SMS | references/01-foundations-uri-launch-apis.md |
| Custom schemes, Universal/App Links, maps, stores, Android/iOS/web/desktop behavior | references/02-schemes-platforms.md |
| Destination security, failure UX, architecture, Bloc/DI, performance, observability | references/03-security-failure-architecture.md |
| Unit/widget/platform/web tests, debugging, OAuth/payment boundaries, migrations | references/04-testing-debugging-migration.md |
| Anti-patterns, implementation/review/refactor workflows, checklists, production gate | references/05-antipatterns-workflows-checklists.md |
Step 1 — Inspect the repository before changing launch behavior
Record:
Flutter + Dart versions
url_launcher constraint
pubspec.lock
resolved url_launcher_android / ios / web / desktop packages
current launch/canLaunch APIs
launch modes in use
URI builders and raw string concatenation
Android <queries>
iOS LSApplicationQueriesSchemes
webOnlyWindowName usage
fallback UX
security allowlists
logging/telemetry
tests for platform failuresSearch for:
launchUrl(
canLaunchUrl(
supportsLaunchMode(
LaunchMode.
launch(
canLaunch(
forceWebView
forceSafariVC
webOnlyWindowName
LSApplicationQueriesSchemes
<queries>Before refactoring, write one sentence describing the intended user-visible action, for example:
Open our trusted help center in the user's external browser.Preserve that behavior intentionally.
Step 2 — Decide whether url_launcher is the correct boundary
Use it when the operation is naturally:
URI → operating-system/browser handlerTypical fits:
- external website;
- lightweight in-app HTTP(S) browser surface;
mailto:composition;tel:dialer handoff;- recipient-only
sms:; - maps/store/custom-scheme handoff;
- App/Universal Link launch;
- desktop
file:where documented; - opening an OAuth/payment page as one step of a larger protocol. Do not use it for:
- internal Flutter routing;
- a full embedded browser/WebView architecture;
- complete OAuth/OIDC implementation;
- App/Universal Link association configuration;
- payment verification;
- direct-call semantics;
- universal SMS-body prefill;
- general cross-platform file opening.
Step 3 — Classify destination trust
Use three categories:
A. static application-owned constant
B. dynamic URI built from trusted identifiers
C. externally supplied/untrusted URIFor C, define an explicit policy before launching:
allowed schemes
allowed hosts/subdomains
HTTPS requirement
userinfo policy
port policy
custom-scheme policy
sensitive parameter policy
confirmation policySyntax validity is not authorization.
Step 4 — Build URIs safely
Static trusted constant:
final uri = Uri.parse('https://example.com/legal/privacy');Dynamic web destination:
final uri = Uri(
scheme: 'https',
host: 'example.com',
pathSegments: ['articles', articleId],
queryParameters: {'q': query},
);Do not concatenate dynamic path/query text into a URL string. For non-HTTP(S) query values, use an encoder such as:
String encodeQueryParameters(Map<String, String> values) => values.entries
.map((e) => '${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}')
.join('&');then pass the encoded result through Uri(query: ...).
Step 5 — Validate untrusted destinations
For controlled web links, prefer a validator equivalent to:
URI must be absolute
scheme == https
host non-empty
userinfo empty
host exactly allowlisted, or explicit approved subdomain rule
port allowed if product restricts itNever use:
uri.host.contains('example.com')because it admits lookalikes such as example.com.evil.test.
Reject unexpected schemes such as javascript:, data:, or file: unless a reviewed feature explicitly permits them.
Validate redirect targets too; a trusted redirector host does not make its eventual external target trusted.
Step 6 — Choose LaunchMode by UX semantics
Use this decision table:
| Requirement | Prefer |
| Platform may choose the surface | platformDefault |
| Must leave app/use external handler | externalApplication |
| Lightweight in-app browser-style HTTP(S) surface | inAppBrowserView |
| Package-provided in-app WebView-style HTTP(S) surface | inAppWebView |
| Must avoid browser handling where supported | externalNonBrowserApplication, only after version/platform verification |
| Rules: |
- do not claim
platformDefaultmeans a fixed surface; - do not use in-app modes for non-HTTP(S);
- if automatic fallback is acceptable, skip
supportsLaunchMode; - if exact surface is mandatory, verify support and define fallback.
Step 7 — Prefer launch-with-fallback over unnecessary preflight
Default:
launchUrl(uri)
→ true: handoff initiated
→ false/exception: fallback UXUse canLaunchUrl only when advance knowledge changes UI or flow.
Its false result may mean:
no verifiable handler
OR
handler not query-visible to the appDo not interpret it as authoritative installation/security proof.
Avoid repeated canLaunchUrl → launchUrl pairs when the preflight adds no product value.
Step 8 — Configure capability querying only when needed
Android
If querying schemes on Android 11+, add narrowly scoped <queries> entries. If querying support for inAppBrowserView, add the documented Custom Tabs service query.
Do not broaden package visibility unnecessarily.
iOS
If canLaunchUrl must query nonstandard schemes, add the required scheme names to LSApplicationQueriesSchemes.
Do not confuse query permission with launch authorization or unique app identity.
Step 9 — Handle common schemes correctly
HTTP(S)
- use structured
Uriconstruction for dynamic data; - validate untrusted hosts/schemes;
- choose external vs in-app behavior deliberately.
mailto:
- encode subject/body/cc/bcc using the non-HTTP query workaround;
- do not assume every mail client handles optional fields identically;
- success means composer/handoff, not delivery.
tel:
- preserve leading
+and normalize according to product rules; - launching is a dialer/platform handoff, not proof of a completed call;
- do not add direct-call permissions merely for a
tel:handoff.
sms:
- use recipient-only
sms:<recipient>as the portable baseline; - do not promise cross-platform message-body prefill through
url_launcher.
Step 10 — Design app-to-app and external fallbacks
For a custom app URI, prefer:
attempt documented native/custom URI
→ if unavailable/fails
→ trusted HTTPS destination
→ if that fails
→ user-visible fallback/copy actionDo not treat custom-scheme ownership as verified identity. For security-sensitive handoff, prefer verified HTTPS association mechanisms where available. For generic HTTPS, do not promise that a specific installed app will open; the OS owns resolution.
Step 11 — Respect platform-specific behavior
Android
Reason about Android intent resolution, package visibility, installed handlers, verified links, user defaults, and resolved url_launcher_android behavior.
Do not assume generic HTTPS targets a specific app or browser.
iOS
Reason about LSApplicationQueriesSchemes, custom-scheme collision, Universal Links, simulator mail/phone limitations, and resolved url_launcher_ios behavior.
Do not extrapolate Android WebView semantics to iOS.
Web
New tabs/windows are subject to browser user-activation rules. Keep destination preparation synchronous before the click where possible.
A web true result does not prove a window opened.
Do not use web canLaunchUrl as installed-app detection.
Desktop
Expect default-handler behavior. Use Uri.file only for documented desktop file flows and verify file existence first.
Step 12 — Keep security boundaries explicit
Never launch external input directly after Uri.parse/Uri.tryParse without policy checks.
Avoid sending:
access tokens
refresh tokens
passwords
private keys
server API secrets
sensitive reset/session URLsthrough external destinations unless the protocol explicitly requires it and the threat model accepts the exposure. Telemetry should prefer:
destination category
scheme
safe host category
mode
platform
failure category
fallback usedover raw URI strings.
Step 13 — Model launch failure separately from external outcome
Useful application categories include:
invalid target
disallowed scheme/host
unsupported requested mode
no handler/unavailable app
query visibility/configuration issue
browser popup blocked
platform exception
unexpected failureChoose fallback by action:
mailto unavailable → contact form / copy address
tel unavailable → show/copy number
custom app unavailable → HTTPS fallback
store app unavailable → public HTTPS listing
maps app unavailable → HTTPS map
browser failure → trusted copyable URL/retryDo not swallow a user-requested failure silently.
Step 14 — Do not confuse handoff with business success
Never write logic equivalent to:
launch succeeded
→ mark email sent
→ mark SMS sent
→ mark call completed
→ mark user authenticated
→ mark order paidFor OAuth/OIDC, authoritative completion comes from the validated callback/token flow with required state/PKCE/nonce semantics. For payments, authoritative completion comes from provider/backend status. For store/email/phone/SMS actions, model only what the app can actually know.
Step 15 — Keep architecture proportional
Small app / one trusted link:
button handler → launchUrlis fine.
Create a thin ExternalLinkService/adapter when it adds real value:
- centralized destination policy;
- reusable fallbacks;
- privacy-safe telemetry;
- deterministic tests;
- platform-specific implementations.
Do not build several forwarding layers that merely mirror
launchUrl. KeepBuildContextout of long-lived launcher services; presentation owns dialogs/snackbars/navigation.
Step 16 — State-management integration
Prefer:
business/application state says an external action is needed
→ UI listener/effect performs platform launchover coupling domain entities or state objects to url_launcher types.
If UI awaits launch before showing fallback, check context.mounted before using context.
Prevent duplicate launches with a pending flag only where repeated windows/apps would be undesirable.
Step 17 — Test policy separately from platform integration
Unit-test:
- URI builders;
- reserved-character/Unicode encoding;
- scheme allowlist;
- host allowlist and lookalike rejection;
- HTTPS/userinfo/port rules;
- fallback selection;
- redaction policy. Widget-test with a fake launcher:
- one user action → one launch;
- expected URI/mode;
- duplicate-tap behavior;
- fallback UI;
- accessible labels. Integration-test platform-sensitive behavior:
- Android
<queries>and mode support; - iOS query schemes and real-device mail/phone;
- web user gesture /
_blank/_self/ popup behavior; - desktop default/file handlers.
Step 18 — Debug systematically
If launch fails:
resolved package versions
→ safe URI components
→ correct scheme
→ requested mode
→ HTTP(S) requirement for in-app modes
→ handler availability
→ capability-query configuration
→ web user gesture
→ platform logs/browser console
→ minimal known URI
→ current platform implementation/changelogIf canLaunchUrl says false but launch may work, inspect Android package visibility or iOS query-scheme configuration and reconsider whether preflight is needed.
If the wrong app opens, remember generic handler selection is OS-owned.
Step 19 — Migrate deprecated code intentionally
Before replacing legacy code, inspect:
launch(
canLaunch(
forceWebView
forceSafariVC
universalLinksOnly
closeWebView
raw URL concatenationThen preserve the historical/product intent while moving to:
Uri
launchUrl
LaunchMode
WebViewConfiguration / BrowserConfigurationDo not mechanically translate old flags by name. Re-test platform behavior, fallbacks, capability-query configuration, and web activation.
Review output format
When reviewing or implementing url_launcher, report in this order:
- Intent — what external action the feature actually needs.
- Fit — whether
url_launcheris the right boundary. - URI policy — construction, encoding, trust, allowed schemes/hosts.
- Launch semantics — requested mode and whether fallback is acceptable.
- Platform configuration — Android/iOS/web/desktop requirements.
- Security/privacy — secrets, target validation, logging, custom-scheme trust.
- Failure UX — false/exception and fallback path.
- Business completion — authoritative downstream success signal.
- Tests — deterministic policy tests plus platform integration coverage.
- Migration/version risks — deprecated APIs or federated-platform assumptions.
Production gate
Do not approve a launch flow until all applicable answers are known:
Is this truly an external platform handoff?
Is destination ownership/trust classified?
Is the URI structurally encoded?
Are untrusted schemes/hosts constrained?
Are secrets excluded from URL/logs?
Is LaunchMode intentional?
Is platformDefault acceptable if used?
Is capability querying actually needed?
Are Android/iOS query declarations minimal and correct?
Does web launch preserve user activation?
Is there a useful handler-unavailable fallback?
Can duplicate taps cause harm?
Is downstream success verified independently?
Are platform-sensitive behaviors tested against resolved versions?If any critical answer is unknown, treat the integration as incomplete rather than adding more launcher calls.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

