Skip to content

[Android] Extract and expose ShopifyCheckout view - #456

Merged
kiftio merged 1 commit into
mainfrom
07-14-extract_and_expose_checkoutview
Jul 16, 2026
Merged

[Android] Extract and expose ShopifyCheckout view#456
kiftio merged 1 commit into
mainfrom
07-14-extract_and_expose_checkoutview

Conversation

@kiftio

@kiftio kiftio commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

What changes are you making?

Introduces a ShopifyCheckoutview, a new public FrameLayout subclass that lets host apps embed Shopify checkout inside their own presentation container (e.g. a Jetpack Compose ModalBottomSheet) rather than relying solely on the imperative ShopifyCheckoutKit.present API.

ShopifyCheckout owns the

  • checkout header,
  • close control,
  • loading indicator,
  • WebView, back-navigation handling,
  • protocol connectivity.

The host retains full control over sheet geometry, scrim, drag handle, snap points, and dismissal gestures. The view can be constructed directly for View-system and Java hosts, or created via the ShopifyCheckout.create Kotlin builder that accepts the same CheckoutPresentation DSL used by present.

Key behavioral details:

  • onCancel is invoked by the close button and system back (which navigates WebView history first when possible); the host is responsible for routing sheet-gesture dismissals through the same cancellation path.
  • onFail asks the host to remove its presentation rather than attempting to dismiss an unknown parent.
  • When the underlying WebView is unsupported, onFail is invoked and the view remains inert instead of crashing.
  • destroy() is idempotent and must be called when the view is permanently removed. AndroidView.onRelease is the recommended Compose integration point. The view also destroys itself when its nearest LifecycleOwner is destroyed.
  • Callbacks and the protocol client are fixed at construction time; a new ShopifyCheckout must be created for a new checkout URL.

Checkout chrome (toolbar, progress bar, loading background, close button) is extracted from CheckoutBottomSheet into a new checkout_view_content.xml layout owned by ShopifyCheckout.

CheckoutBottomSheet now hosts a ShopifyCheckout inside a plain FrameLayout container, removing duplicated chrome logic.

CheckoutWebView.checkoutViewFor is refactored to run on the calling thread (must be main) and transport attachment now happens ina. try-catch in the constructor that destorys the partially constructed WebVeiw and rethrows on unsupported-feature failure. CheckoutBottomSheet.start() now returns a Boolean indicating whether presentation succeeded, allowing ShopifyCheckoutKit to clean up its lifecycle observer on failure without relying on a caught exception.

The demo app's cart screen is updated upstack to demonstrate (not in this PR)

How to test

  1. Build and run the demo app (CheckoutKitAndroidDemo).
  2. Add items to the cart and tap Checkout — the cart screen now presents checkout inside a ModalBottomSheet via ShopifyCheckout.
  3. Verify the checkout header, close button, loading indicator, and WebView render correctly inside the sheet.
  4. Tap the close button or swipe to dismiss — confirm onCancel is invoked and the sheet closes.
  5. Press system back while on the first checkout page — confirm the sheet closes. Press back on a page with WebView history — confirm it navigates back before closing.
  6. Confirm the existing ShopifyCheckoutKit.present flow (used elsewhere in the app) continues to work unchanged.
  7. Run the unit tests: ./gradlew :lib:test — new ShopifyCheckoutTest and updated InteropTest cover construction, preload consumption, lifecycle destruction, back navigation, cancel deduplication, and Java interop.

Before you merge

Important

Important

  • I've added tests to support my implementation
  • I have read and agree with the Contribution Guidelines
  • I have read and agree with the Code of Conduct
  • I've updated the relevant platform README (platforms/swift/README.md and/or platforms/android/README.md)

Releasing a new Swift version?
  • I have bumped the version in ShopifyCheckoutKit.podspec
  • I have bumped the version in platforms/swift/Sources/ShopifyCheckoutKit/ShopifyCheckoutKit.swift
  • I have updated the SwiftPM/CocoaPods version snippets in platforms/swift/README.md (major version only)
Releasing a new Embedded Checkout Protocol version?
  • I have bumped embeddedCheckoutProtocolAndroid in platforms/android/gradle/libs.versions.toml
  • I have updated protocol/languages/kotlin/embedded-checkout-protocol/api/embedded-checkout-protocol.api if the public API changed
Releasing a new Android version?
  • I have bumped checkoutKitAndroid in platforms/android/gradle/libs.versions.toml
  • I have updated the Gradle/Maven version snippets in platforms/android/README.md

Tip

Tip

See the Contributing documentation for the full release process per platform.

@github-actions github-actions Bot added the #gsd:50662 Rebase Checkout Kit on UCP label Jul 14, 2026

kiftio commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

@kiftio kiftio changed the title Extract and expose CheckoutView [Android] Extract and expose CheckoutView Jul 14, 2026
* connectivity. Its parent owns presentation state, geometry, and dismissal gestures. Call
* [destroy] when the view is permanently removed so the underlying WebView is released promptly.
*/
@SuppressLint("ViewConstructor")

@kiftio kiftio Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ShopifyCheckout intentionally lacks Android’s standard XML-inflatable constructors:

  • View(context)
  • View(context, attrs)
  • View(context, attrs, defStyleAttr)

ShopifyCheckout instead requires runtime state immediately:

ShopifyCheckout(context, checkoutUrl, listener, protocolClient)

Without the suppression, lint warns because subclasses of View are normally expected to support XML inflation.

The practical consequence is that this will not work:

<com.shopify.checkoutkit.ShopifyCheckout ... />

But direct construction and Compose’s AndroidView(factory = …) work normally. The annotation only silences that specific static-analysis warning; it changes no runtime behaviour.

preloadCache.invalidate()
null
}
check(Looper.myLooper() == Looper.getMainLooper()) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is internal..

The responsibility is on the caller to run call on the main thread.

Rationale - the whole CheckoutView is UI construction. The whole thing should be on the UI thread, not just this internal WebView creation part.

Annotating the constructors for the View with @mainthread

Compose's AndroidView.factory already runs on the UI thread

CheckoutWebView(activity as Context, webMessageTransport).apply {
loadCheckout(url)
}
return cachedView ?: run {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

small refactor to avoid the non-null assert

Comment thread platforms/android/README.md Outdated
}
},
modifier = Modifier.fillMaxSize(),
onRelease = CheckoutView::destroy,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Important and different from swift - consumers will be responsible for calling an explicit destroy() function

@kiftio
kiftio force-pushed the 07-14-extract_and_expose_checkoutview branch 2 times, most recently from 8febad5 to fa38ddb Compare July 14, 2026 09:45
@kiftio
kiftio marked this pull request as ready for review July 14, 2026 09:59
@kiftio
kiftio requested a review from a team as a code owner July 14, 2026 09:59
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown

React Native — Coverage Report

Lines Statements Branches Functions
Coverage: 92%
91.85% (327/356) 87.98% (183/208) 100% (86/86)

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown

Package Size

Platform Artifact Base Head Delta
Android release AAR 226.5 KiB 239.2 KiB +12.7 KiB
Android file breakdown
File Base Head Delta
classes.jar 239.8 KiB 253.2 KiB +13.4 KiB
res/layout/checkout_sheet_content.xml 4.0 KiB 2.0 KiB -2.0 KiB
res/layout/checkout_view_content.xml 2.2 KiB +2.2 KiB
res/values/values.xml 1.2 KiB 1.2 KiB 0 B
R.txt 1.0 KiB 1.1 KiB +73 B
proguard.txt 798 B 798 B 0 B
AndroidManifest.xml 578 B 578 B 0 B
res/drawable/close.xml 431 B 431 B 0 B
res/menu/checkout_menu.xml 354 B 354 B 0 B
META-INF/com/android/build/gradle/aar-metadata.properties 157 B 157 B 0 B

Measured from the PR base SHA and PR head SHA. The file breakdown shows uncompressed sizes within each package artifact, so individual files do not sum to the compressed artifact total. This comment reports package artifact sizes only; it is not a final app binary-size report.

@kiftio
kiftio force-pushed the 07-14-extract_and_expose_checkoutview branch from fa38ddb to 253322d Compare July 14, 2026 11:39
@kiftio kiftio mentioned this pull request Jul 14, 2026
11 tasks
Base automatically changed from dk/web-message-listener to main July 14, 2026 13:16
@kiftio
kiftio force-pushed the 07-14-extract_and_expose_checkoutview branch 2 times, most recently from 2d9ed69 to 54efe2d Compare July 14, 2026 14:44
@kiftio kiftio changed the title [Android] Extract and expose CheckoutView [Android] Extract and expose ShopifyCheckout view Jul 14, 2026
@kiftio
kiftio force-pushed the 07-14-extract_and_expose_checkoutview branch from 54efe2d to 565b063 Compare July 14, 2026 15:07
embeddedCheckoutProtocol.attach()
try {
embeddedCheckoutProtocol.attach()
} catch (error: UnsupportedWebViewException) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

adding this try-catch to catch the error, destroy the view.

We still re-throw the error so consumers get it

* Initialization failures are reported on the main thread after this constructor returns. The
* returned view remains inert when initialization fails.
*/
@MainThread

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@mainthread annotations are basically for Android Lint and IDE support to help folks be aware that they're calling from the wrong thread

@kiftio
kiftio force-pushed the 07-14-extract_and_expose_checkoutview branch from 565b063 to 288e313 Compare July 14, 2026 15:35
@kiftio
kiftio force-pushed the 07-14-extract_and_expose_checkoutview branch from 288e313 to 0ca3772 Compare July 15, 2026 12:28
Assisted-By: devx/7caad83a-1983-48f4-a173-2b35c37e1e4f
@kiftio
kiftio force-pushed the 07-14-extract_and_expose_checkoutview branch from 0ca3772 to f616e04 Compare July 16, 2026 09:32

kiftio commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Merge activity

  • Jul 16, 10:33 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jul 16, 10:33 AM UTC: @kiftio merged this pull request with Graphite.

@kiftio
kiftio merged commit a38dd5a into main Jul 16, 2026
35 checks passed
@kiftio
kiftio deleted the 07-14-extract_and_expose_checkoutview branch July 16, 2026 10:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

#gsd:50662 Rebase Checkout Kit on UCP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants