Skip to content

[Android] Expose view - #446

Closed
kiftio wants to merge 1 commit into
dk/web-message-listenerfrom
dk/expose-android-checkout-view
Closed

[Android] Expose view#446
kiftio wants to merge 1 commit into
dk/web-message-listenerfrom
dk/expose-android-checkout-view

Conversation

@kiftio

@kiftio kiftio commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

What changes are you making?

Introduces CheckoutView, 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.

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

CheckoutBottomSheet now hosts a CheckoutView 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 is moved out of the constructor so a failed feature check can cleanly destroy the partially constructed WebView. 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 to demonstrate the embedded flow using ModalBottomSheet + AndroidView, and CartViewModel.checkoutConfiguration is extracted so the same callback block can be shared between the imperative and embedded paths.

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 CheckoutView.
  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 CheckoutViewTest and updated InteropTest cover construction, preload consumption, lifecycle destruction, back navigation, cancel deduplication, and Java interop.

Before you merge

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

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 13, 2026

kiftio commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@kiftio
kiftio changed the base branch from main to graphite-base/446 July 13, 2026 19:55
@kiftio
kiftio force-pushed the dk/expose-android-checkout-view branch from e0a0dbe to 20c3336 Compare July 13, 2026 19:55
@kiftio
kiftio force-pushed the graphite-base/446 branch from 4f70c63 to a7833d1 Compare July 13, 2026 19:55
@kiftio
kiftio changed the base branch from graphite-base/446 to dk/web-message-listener July 13, 2026 19:55
@kiftio kiftio changed the title use web message listner when available [Android] Expose view Jul 13, 2026
@kiftio kiftio mentioned this pull request Jul 13, 2026
11 tasks
@kiftio
kiftio force-pushed the dk/expose-android-checkout-view branch from 20c3336 to 8a96e85 Compare July 13, 2026 22:07
* 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")

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.

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

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

CheckoutView instead requires runtime state immediately:

CheckoutView(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.CheckoutView ... />

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

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.

1 participant