diff --git a/platforms/android/AGENTS.md b/platforms/android/AGENTS.md index d3d9eb76..3cbd7a6c 100644 --- a/platforms/android/AGENTS.md +++ b/platforms/android/AGENTS.md @@ -21,12 +21,12 @@ The sample is a separate Gradle composite (`samples/MobileBuyIntegration/setting ## Key components -- **`ShopifyCheckoutKit.kt`** — the public singleton. Entry point for all consumer interactions (configure, preload, present). +- **`ShopifyCheckoutKit.kt`** — the public singleton. Entry point for all consumer interactions (configure, present). - **`CheckoutDialog.kt`** — the dialog that hosts the WebView, including the progress indicator and checkout error coordination. -- **`CheckoutWebView.kt`** — primary WebView. Holds the URL-keyed cache with a **5-minute preload TTL**; instruments page loads; routes bridge messages. +- **`CheckoutWebView.kt`** — primary WebView. Instruments page loads; routes bridge messages. - **`BaseWebView.kt`** — abstract base class. Any new WebView variant must extend this so shared configuration (JS interface name, user agent suffix, client handling) is consistent. - **`CheckoutBridge.kt`** — the JS ↔ native bridge. `SCHEMA_VERSION` is a cross-boundary contract with the web checkout team; bumping it requires coordination with them. -- **`Configuration.kt`** — runtime config container (color scheme, preload enable, log level). Any config change clears the WebView cache. +- **`Configuration.kt`** — runtime config container (color scheme, log level). - **`CheckoutListener.kt`** + **`DefaultCheckoutListener`** — consumer-implemented lifecycle interface (failure, cancellation, permission prompts, file chooser). Changes here are consumer API changes. - **`CheckoutPresentation.kt`** — Kotlin-first builder for per-presentation callbacks (`onFail`, `onCancel`, browser/system hooks, ECP `connect(...)`). Builds a `DefaultCheckoutListener` internally. @@ -95,5 +95,4 @@ Publishing goes through GitHub Releases → the repo-root `.github/workflows/and - **Library Kotlin version pin.** Consumer compatibility floor; any migration is a deliberate major-version decision. - **`minSdk` / JVM target.** Same story. - **`CheckoutBridge.SCHEMA_VERSION`.** Cross-team contract with the web checkout — changing it without coordination breaks the bridge. -- **Preload TTL (5 minutes).** Performance-sensitive; has been tuned. Don't tweak without a reason. - **`-Xexplicit-api=strict`.** Removing this would let implicit public declarations ship; keeping it is a consumer-protection invariant. diff --git a/platforms/android/lib/api/lib.api b/platforms/android/lib/api/lib.api index 3db2dc29..00901ae6 100644 --- a/platforms/android/lib/api/lib.api +++ b/platforms/android/lib/api/lib.api @@ -1089,21 +1089,18 @@ public final class com/shopify/checkoutkit/ColorsBuilder { public final class com/shopify/checkoutkit/Configuration { public fun ()V public final fun component1 ()Lcom/shopify/checkoutkit/ColorScheme; - public final fun component2 ()Lcom/shopify/checkoutkit/Preloading; - public final fun component3 ()Lcom/shopify/checkoutkit/Platform; - public final fun component4 ()Lcom/shopify/checkoutkit/LogLevel; - public final fun copy (Lcom/shopify/checkoutkit/ColorScheme;Lcom/shopify/checkoutkit/Preloading;Lcom/shopify/checkoutkit/Platform;Lcom/shopify/checkoutkit/LogLevel;)Lcom/shopify/checkoutkit/Configuration; - public static synthetic fun copy$default (Lcom/shopify/checkoutkit/Configuration;Lcom/shopify/checkoutkit/ColorScheme;Lcom/shopify/checkoutkit/Preloading;Lcom/shopify/checkoutkit/Platform;Lcom/shopify/checkoutkit/LogLevel;ILjava/lang/Object;)Lcom/shopify/checkoutkit/Configuration; + public final fun component2 ()Lcom/shopify/checkoutkit/Platform; + public final fun component3 ()Lcom/shopify/checkoutkit/LogLevel; + public final fun copy (Lcom/shopify/checkoutkit/ColorScheme;Lcom/shopify/checkoutkit/Platform;Lcom/shopify/checkoutkit/LogLevel;)Lcom/shopify/checkoutkit/Configuration; + public static synthetic fun copy$default (Lcom/shopify/checkoutkit/Configuration;Lcom/shopify/checkoutkit/ColorScheme;Lcom/shopify/checkoutkit/Platform;Lcom/shopify/checkoutkit/LogLevel;ILjava/lang/Object;)Lcom/shopify/checkoutkit/Configuration; public fun equals (Ljava/lang/Object;)Z public final fun getColorScheme ()Lcom/shopify/checkoutkit/ColorScheme; public final fun getLogLevel ()Lcom/shopify/checkoutkit/LogLevel; public final fun getPlatform ()Lcom/shopify/checkoutkit/Platform; - public final fun getPreloading ()Lcom/shopify/checkoutkit/Preloading; public fun hashCode ()I public final fun setColorScheme (Lcom/shopify/checkoutkit/ColorScheme;)V public final fun setLogLevel (Lcom/shopify/checkoutkit/LogLevel;)V public final fun setPlatform (Lcom/shopify/checkoutkit/Platform;)V - public final fun setPreloading (Lcom/shopify/checkoutkit/Preloading;)V public fun toString ()Ljava/lang/String; } @@ -3661,19 +3658,6 @@ public final class com/shopify/checkoutkit/PostalAddress$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class com/shopify/checkoutkit/Preloading { - public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Z - public final fun copy (Z)Lcom/shopify/checkoutkit/Preloading; - public static synthetic fun copy$default (Lcom/shopify/checkoutkit/Preloading;ZILjava/lang/Object;)Lcom/shopify/checkoutkit/Preloading; - public fun equals (Ljava/lang/Object;)Z - public final fun getEnabled ()Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - public final class com/shopify/checkoutkit/PurpleSelectedPaymentInstrument { public static final field Companion Lcom/shopify/checkoutkit/PurpleSelectedPaymentInstrument$Companion; public fun (Lcom/shopify/checkoutkit/BillingAddressClass;Lcom/shopify/checkoutkit/CredentialClass;Lkotlinx/serialization/json/JsonObject;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V @@ -3893,7 +3877,6 @@ public final class com/shopify/checkoutkit/ShopifyCheckoutKit { public static final field version Ljava/lang/String; public static final fun configure (Lcom/shopify/checkoutkit/ConfigurationUpdater;)V public static final fun getConfiguration ()Lcom/shopify/checkoutkit/Configuration; - public static final fun invalidate ()V public static final fun present (Ljava/lang/String;Landroidx/activity/ComponentActivity;Lcom/shopify/checkoutkit/DefaultCheckoutListener;)Lcom/shopify/checkoutkit/CheckoutKitDialog; public static final fun present (Ljava/lang/String;Landroidx/activity/ComponentActivity;Lcom/shopify/checkoutkit/DefaultCheckoutListener;Lcom/shopify/checkoutkit/CheckoutCommunicationClient;)Lcom/shopify/checkoutkit/CheckoutKitDialog; public static final synthetic fun present (Ljava/lang/String;Landroidx/activity/ComponentActivity;Lkotlin/jvm/functions/Function1;)Lcom/shopify/checkoutkit/CheckoutKitDialog; diff --git a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/BaseWebView.kt b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/BaseWebView.kt index 173c93ac..678c7434 100644 --- a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/BaseWebView.kt +++ b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/BaseWebView.kt @@ -234,8 +234,6 @@ internal fun BaseWebView.removeFromParent() { val parent = this.parent if (parent is ViewGroup) { log.d("BaseWebView", "Existing parent found for WebView, removing.") - // Ensure view is not destroyed when removing from parent - CheckoutWebViewContainer.retainCacheEntry = RetainCacheEntry.YES parent.removeView(this) } } diff --git a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutDialog.kt b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutDialog.kt index f77b6143..d772e804 100644 --- a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutDialog.kt +++ b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutDialog.kt @@ -81,11 +81,10 @@ internal class CheckoutDialog( // properly into the fields. To be investigated further. window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) - log.d(LOG_TAG, "Finding or creating new WebView.") - val checkoutWebView = CheckoutWebView.cacheableCheckoutView( - checkoutUrl, - context, - ) + log.d(LOG_TAG, "Creating new WebView.") + val checkoutWebView = CheckoutWebView(context as Context).apply { + loadCheckout(checkoutUrl) + } checkoutWebView.onResume() log.d(LOG_TAG, "Setting listener on WebView.") @@ -116,7 +115,6 @@ internal class CheckoutDialog( onBackPressedDispatcher.addCallback(backNavigationCallback) setOnCancelListener { log.d(LOG_TAG, "Cancel listener invoked, invoking onCheckoutCanceled.") - CheckoutWebViewContainer.retainCacheEntry = RetainCacheEntry.IF_NOT_STALE checkoutListener.onCheckoutCanceled() } @@ -198,8 +196,7 @@ internal class CheckoutDialog( } internal fun closeCheckoutDialogWithError(exception: CheckoutException) { - log.d(LOG_TAG, "Closing dialog with error, marking cache entry stale, calling onCheckoutFailed.") - CheckoutWebView.markCacheEntryStale() + log.d(LOG_TAG, "Closing dialog with error, calling onCheckoutFailed.") checkoutListener.onCheckoutFailed(exception) dismiss() } diff --git a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutWebView.kt b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutWebView.kt index 4b640242..a5af429e 100644 --- a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutWebView.kt +++ b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutWebView.kt @@ -29,17 +29,11 @@ import android.os.Looper import android.util.AttributeSet import android.webkit.WebResourceRequest import android.webkit.WebView -import androidx.activity.ComponentActivity import com.shopify.checkoutkit.ShopifyCheckoutKit.log -import java.util.concurrent.CountDownLatch -import kotlin.math.abs -import kotlin.time.Duration.Companion.minutes internal class CheckoutWebView(context: Context, attributeSet: AttributeSet? = null) : BaseWebView(context, attributeSet) { - var isPreload = false - private val checkoutBridge = CheckoutBridge(CheckoutWebViewListener(NoopCheckoutListener())) private val embeddedCheckoutProtocol = EmbeddedCheckoutProtocol(this) private var loadComplete = false @@ -81,13 +75,11 @@ internal class CheckoutWebView(context: Context, attributeSet: AttributeSet? = n removeJavascriptInterface(EmbeddedCheckoutProtocol.INTERFACE_NAME) } - fun loadCheckout(url: String, isPreload: Boolean) { - log.d(LOG_TAG, "Loading checkout with url $url. IsPreload: $isPreload.") - this.isPreload = isPreload + fun loadCheckout(url: String) { + log.d(LOG_TAG, "Loading checkout with url $url.") Handler(Looper.getMainLooper()).post { val ecpUrl = url.appendEcpParams(specVersion = CheckoutProtocol.specVersion) - val headers = if (isPreload) mutableMapOf("Shopify-Purpose" to "prefetch") else mutableMapOf() - loadUrl(ecpUrl, headers) + loadUrl(ecpUrl) } } @@ -126,112 +118,5 @@ internal class CheckoutWebView(context: Context, attributeSet: AttributeSet? = n companion object { private const val LOG_TAG = "CheckoutWebView" private const val JAVASCRIPT_INTERFACE_NAME = "android" - - internal var cacheEntry: CheckoutWebViewCacheEntry? = null - internal var cacheClock = CheckoutWebViewCacheClock() - - fun markCacheEntryStale() { - cacheEntry = cacheEntry?.copy(timeout = -1) - } - - fun clearCache(newEntry: CheckoutWebViewCacheEntry? = null) = cacheEntry?.let { - Handler(Looper.getMainLooper()).post { - it.view.destroy() - cacheEntry = newEntry - } - } - - fun cacheableCheckoutView( - url: String, - activity: ComponentActivity, - isPreload: Boolean = false, - ): CheckoutWebView { - var view: CheckoutWebView? = null - val countDownLatch = CountDownLatch(1) - - activity.runOnUiThread { - view = fetchView(url, activity, isPreload) - countDownLatch.countDown() - } - - countDownLatch.await() - - return view!! - } - - private fun fetchView( - url: String, - activity: ComponentActivity, - isPreload: Boolean, - ): CheckoutWebView { - val preloadingEnabled = ShopifyCheckoutKit.configuration.preloading.enabled - log.d( - LOG_TAG, - "Fetch view called for url $url. Is preload: $isPreload. Preloading enabled: $preloadingEnabled." - ) - if (!preloadingEnabled || cacheEntry?.isValid(url) != true) { - log.d(LOG_TAG, "Constructing new CheckoutWebView and calling loadCheckout.") - val view = CheckoutWebView(activity as Context).apply { - loadCheckout(url, isPreload) - if (isPreload) { - // Pauses processing that can be paused safely (e.g. geolocation, animations), but not JavaScript / network requests - // https://developer.android.com/reference/android/webkit/WebView#onPause() - onPause() - } - } - - setCacheEntry( - CheckoutWebViewCacheEntry( - key = url, - view = view, - clock = cacheClock, - timeout = if (isPreload && preloadingEnabled) 5.minutes.inWholeMilliseconds else 0, - ) - ) - - return view - } - - log.d(LOG_TAG, "Returning previously cached view.") - return cacheEntry!!.view - } - - private fun setCacheEntry(cacheEntry: CheckoutWebViewCacheEntry) { - if (this.cacheEntry == null) { - log.d( - LOG_TAG, - "Caching CheckoutWebView with TTL: ${cacheEntry.timeout} (note: a TTL of 0 is equivalent to not caching)." - ) - - this.cacheEntry = cacheEntry - } else { - log.d(LOG_TAG, "Clearing WebView cache and destroying cached view.") - clearCache(cacheEntry) - } - } - } - - internal data class CheckoutWebViewCacheEntry( - val key: String, - val view: CheckoutWebView, - val clock: CheckoutWebViewCacheClock, - val timeout: Long, - ) { - private val timestamp = clock.currentTimeMillis() - - fun isValid(key: String): Boolean { - return key == cacheEntry!!.key && !cacheEntry!!.isStale - } - - internal val isStale: Boolean - get() { - val staleResult = abs(timestamp - clock.currentTimeMillis()) >= timeout - log.d(LOG_TAG, "Checking cache entry staleness. Is stale: $staleResult.") - return staleResult - } - } - - internal class CheckoutWebViewCacheClock { - fun currentTimeMillis(): Long = System.currentTimeMillis() } } diff --git a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutWebViewContainer.kt b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutWebViewContainer.kt deleted file mode 100644 index 6b2ccdac..00000000 --- a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutWebViewContainer.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * MIT License - * - * Copyright 2023-present, Shopify Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.shopify.checkoutkit - -import android.content.Context -import android.util.AttributeSet -import android.view.View -import android.widget.RelativeLayout -import com.shopify.checkoutkit.ShopifyCheckoutKit.log - -internal class CheckoutWebViewContainer @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, - defStyleRes: Int = 0 -) : RelativeLayout(context, attrs, defStyleAttr, defStyleRes) { - - // Clear the cache whenever the WebView is removed from it's container - // We should only clear the cache and destroy the WebView after it's been removed from it's parent - override fun onViewRemoved(child: View?) { - super.onViewRemoved(child) - log.d(LOG_TAG, "onViewRemoved called.") - if (child is CheckoutWebView) { - if (retainCacheEntry == RetainCacheEntry.IF_NOT_STALE && CheckoutWebView.cacheEntry?.isStale == true) { - log.d(LOG_TAG, "Clearing WebView cache.") - CheckoutWebView.clearCache() - } - } - - retainCacheEntry = RetainCacheEntry.IF_NOT_STALE - } - - companion object { - private const val LOG_TAG = "CheckoutWebViewContainer" - internal var retainCacheEntry = RetainCacheEntry.IF_NOT_STALE - } -} - -internal enum class RetainCacheEntry { - /** - * Retain a WebView in the cache if it is not stale - */ - IF_NOT_STALE, - - /** - * Always retain the WebView in the cache, regardless of whether it is stale or not - */ - YES, -} diff --git a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutWebViewListener.kt b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutWebViewListener.kt index 51d8dd80..e1c989a2 100644 --- a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutWebViewListener.kt +++ b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutWebViewListener.kt @@ -39,7 +39,7 @@ import android.webkit.WebView internal class CheckoutWebViewListener( private val listener: CheckoutListener, private val toggleHeader: (Boolean) -> Unit = {}, - private val closeCheckoutDialogWithError: (CheckoutException) -> Unit = { CheckoutWebView.clearCache() }, + private val closeCheckoutDialogWithError: (CheckoutException) -> Unit = {}, private val setProgressBarVisibility: (Int) -> Unit = {}, private val updateProgressBarPercentage: (Int) -> Unit = {}, ) { diff --git a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/Configuration.kt b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/Configuration.kt index 5afb7880..9aa33742 100644 --- a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/Configuration.kt +++ b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/Configuration.kt @@ -25,26 +25,14 @@ package com.shopify.checkoutkit /** * Configuration for Shopify Checkout Kit. * - * Allows: - * - Enabling/disabling preloading, - * - Specifying the colorScheme that should be used for checkout. + * Allows specifying the colorScheme that should be used for checkout. */ public data class Configuration internal constructor( var colorScheme: ColorScheme = ColorScheme.Automatic(), - var preloading: Preloading = Preloading(), var platform: Platform? = null, var logLevel: LogLevel = LogLevel.WARN, ) -/** - * Configuration related to preloading. - * - * Initially allows toggling the preloading feature. - */ -public data class Preloading( - val enabled: Boolean = true -) - public enum class LogLevel { DEBUG, WARN, ERROR } diff --git a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/EmbeddedCheckoutProtocol.kt b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/EmbeddedCheckoutProtocol.kt index 319e018f..0c7bcc7f 100644 --- a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/EmbeddedCheckoutProtocol.kt +++ b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/EmbeddedCheckoutProtocol.kt @@ -119,11 +119,7 @@ internal class EmbeddedCheckoutProtocol( } private fun handleComplete(message: String) { - // Cache invalidation on completion is a kit invariant — independent of whether - // a merchant client is attached. Mark stale before delegating so a completed - // checkout is never reused from cache on the next present(...). - log.d(LOG_TAG, "Handling $METHOD_COMPLETE: marking cache stale and bubbling up.") - CheckoutWebView.markCacheEntryStale() + log.d(LOG_TAG, "Handling $METHOD_COMPLETE: bubbling up.") onMainThread { client?.process(message) } diff --git a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/ShopifyCheckoutKit.kt b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/ShopifyCheckoutKit.kt index 2219ed61..4b8c5391 100644 --- a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/ShopifyCheckoutKit.kt +++ b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/ShopifyCheckoutKit.kt @@ -27,7 +27,7 @@ import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner /** - * Entrypoint to the library, allows configuring, preloading, and presenting Shopify checkouts. + * Entrypoint to the library, allows configuring and presenting Shopify checkouts. */ public object ShopifyCheckoutKit { @@ -57,9 +57,8 @@ public object ShopifyCheckoutKit { /** * Allows configuring ShopifyCheckoutKit. * - * Current configuration options are for enabling and disabling preloading, and for setting the checkout color scheme. * Kotlin example: - * {@code ShopifyCheckoutKit.configure { it.preloading = Preloading(enabled = enabled) }} + * {@code ShopifyCheckoutKit.configure { it.colorScheme = ColorScheme.Dark() }} * * @param setter a function that modifies the configuration object * @see Configuration @@ -67,57 +66,6 @@ public object ShopifyCheckoutKit { @JvmStatic public fun configure(setter: ConfigurationUpdater) { setter.configure(configuration) - CheckoutWebView.clearCache() - } - - /** - * Invalidate WebViews cached due to preload calls - */ - @JvmStatic - public fun invalidate() { - log.d("ShopifyCheckoutKit", "Invalidate called, marking cache entry stale.") - CheckoutWebView.markCacheEntryStale() - } - - /** - * Preloads a Shopify checkout in the background. - * - * Preloading checkout is fully optional, but allows reducing the time taken between calling - * {@link ShopifyCheckoutKit#present(String, ComponentActivity, CheckoutListener)} and having a fully interactive checkout. - * Note: Preload must be called on all cart changes to avoid stale checkouts being presented. - * Preloaded checkouts also have a TTL of 5 minutes, after checkout will be re-loaded on calling present. - * - * @param checkoutUrl The URL of the checkout to be loaded, this can be obtained via the Storefront API - * @param context The context the checkout is being presented from - * - * Public preload support is coming soon. - */ - @JvmStatic - internal fun preload(checkoutUrl: String, context: ComponentActivity) { - log.d("ShopifyCheckoutKit", "Preload called. Preloading enabled ${configuration.preloading.enabled}.") - if (!configuration.preloading.enabled) return - - val cacheEntry = CheckoutWebView.cacheEntry - if (cacheEntry?.view != null && cacheEntry.view.isInViewHierarchy()) { - if (cacheEntry.key != checkoutUrl) { - log.d( - "ShopifyCheckoutKit", - "View already cached and in view hierarchy, but with different url, marking stale." - ) - CheckoutWebView.markCacheEntryStale() - } - - log.d("ShopifyCheckoutKit", "Calling loadCheckout on existing view with url $checkoutUrl.") - cacheEntry.view.loadCheckout(checkoutUrl, false) - } else { - log.d("ShopifyCheckoutKit", "Fetching cacheable WebView.") - CheckoutWebView.markCacheEntryStale() - CheckoutWebView.cacheableCheckoutView( - url = checkoutUrl, - activity = context, - isPreload = true, - ) - } } /** @@ -197,7 +145,3 @@ public fun interface CheckoutKitDialog { */ public fun dismiss() } - -private fun CheckoutWebView.isInViewHierarchy(): Boolean { - return this.parent != null -} diff --git a/platforms/android/lib/src/main/res/layout/dialog_checkout.xml b/platforms/android/lib/src/main/res/layout/dialog_checkout.xml index 75766c1b..8fca29d1 100644 --- a/platforms/android/lib/src/main/res/layout/dialog_checkout.xml +++ b/platforms/android/lib/src/main/res/layout/dialog_checkout.xml @@ -15,7 +15,7 @@ android:theme="@style/Theme.AppCompat.DayNight" android:elevation="4dp" /> - - + diff --git a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutDialogTest.kt b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutDialogTest.kt index 6a6eeef7..6739f703 100644 --- a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutDialogTest.kt +++ b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutDialogTest.kt @@ -60,9 +60,6 @@ class CheckoutDialogTest { @Before fun setUp() { configuration = ShopifyCheckoutKit.configuration - ShopifyCheckoutKit.configure { - it.preloading = Preloading(enabled = false) - } activity = Robolectric.buildActivity(ComponentActivity::class.java).get() processor = noopDefaultCheckoutListener() } @@ -70,7 +67,6 @@ class CheckoutDialogTest { @After fun tearDown() { ShopifyCheckoutKit.configure { - it.preloading = configuration.preloading it.colorScheme = configuration.colorScheme } } @@ -102,7 +98,7 @@ class CheckoutDialogTest { ShopifyCheckoutKit.present("https://shopify.com", activity, processor) val webView: WebView = ShadowDialog.getLatestDialog() - .findViewById(R.id.checkoutKitContainer) + .findViewById(R.id.checkoutKitContainer) .children.firstOrNull { it is WebView } as WebView? ?: fail("No WebVew found in dialog") assertThat(shadowOf(webView).wasOnResumeCalled()).isTrue() @@ -153,26 +149,6 @@ class CheckoutDialogTest { verify(mockListener, never()).onCheckoutFailed(any()) } - @Test - fun `closeCheckoutDialogWithError marks cache entry stale`() { - withPreloadingEnabled { - val mockListener = mock() - ShopifyCheckoutKit.preload("https://shopify.com", activity) - ShopifyCheckoutKit.present("https://shopify.com", activity, mockListener) - - assertThat(CheckoutWebView.cacheEntry).isNotNull() - - val dialog = ShadowDialog.getLatestDialog() - val checkoutDialog = dialog as CheckoutDialog - val error = checkoutException() - - checkoutDialog.closeCheckoutDialogWithError(error) - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - - assertThat(CheckoutWebView.cacheEntry).isNull() - } - } - @Test fun `closeCheckoutDialogWithError invokes onCheckoutFailed and dismisses the dialog`() { val mockListener = mock() diff --git a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/Helpers.kt b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutExceptionAssert.kt similarity index 91% rename from platforms/android/lib/src/test/java/com/shopify/checkoutkit/Helpers.kt rename to platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutExceptionAssert.kt index 23b0278d..1adbb183 100644 --- a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/Helpers.kt +++ b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutExceptionAssert.kt @@ -24,15 +24,6 @@ package com.shopify.checkoutkit import org.assertj.core.api.AbstractAssert -fun withPreloadingEnabled(block: () -> Unit) { - try { - ShopifyCheckoutKit.configure { it.preloading = Preloading(enabled = true) } - block() - } finally { - ShopifyCheckoutKit.configure { it.preloading = Preloading(enabled = false) } - } -} - class CheckoutExceptionAssert(actual: CheckoutException) : AbstractAssert(actual, CheckoutExceptionAssert::class.java) { companion object { diff --git a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutPresentationTest.kt b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutPresentationTest.kt index fa9d3389..52cd4801 100644 --- a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutPresentationTest.kt +++ b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutPresentationTest.kt @@ -54,21 +54,16 @@ class CheckoutPresentationTest { @Before fun setUp() { configuration = ShopifyCheckoutKit.getConfiguration() - ShopifyCheckoutKit.configure { - it.preloading = Preloading(enabled = false) - } activity = Robolectric.buildActivity(ComponentActivity::class.java).get() } @After fun tearDown() { ShopifyCheckoutKit.configure { - it.preloading = configuration.preloading it.colorScheme = configuration.colorScheme it.platform = configuration.platform it.logLevel = configuration.logLevel } - CheckoutWebView.cacheEntry = null } @Test diff --git a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutWebViewCacheTest.kt b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutWebViewCacheTest.kt deleted file mode 100644 index f7bd71f9..00000000 --- a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutWebViewCacheTest.kt +++ /dev/null @@ -1,198 +0,0 @@ -/* - * MIT License - * - * Copyright 2023-present, Shopify Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.shopify.checkoutkit - -import android.os.Looper -import androidx.activity.ComponentActivity -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.assertThatNoException -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.kotlin.mock -import org.mockito.kotlin.whenever -import org.robolectric.Robolectric -import org.robolectric.RobolectricTestRunner -import org.robolectric.Shadows.shadowOf -import kotlin.time.Duration.Companion.minutes - -@RunWith(RobolectricTestRunner::class) -class CheckoutWebViewCacheTest { - - private lateinit var activity: ComponentActivity - private lateinit var listener: CheckoutListener - - @Before - fun setUp() { - CheckoutWebView.clearCache() - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - - activity = Robolectric.buildActivity(ComponentActivity::class.java).get() - listener = listener() - } - - @Test - fun `cacheable checkout view returns a view if preloading enabled and cache is empty`() { - withPreloadingEnabled { - val view = CheckoutWebView.cacheableCheckoutView(URL, activity) - assertThat(view).isNotNull - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - assertThat(shadowOf(view).lastLoadedUrl).startsWith(URL) - } - } - - @Test - fun `cacheableCheckoutView returns the same view if preloading enabled and cache is populated`() { - withPreloadingEnabled { - val viewOne = CheckoutWebView.cacheableCheckoutView(URL, activity, true) - val viewTwo = CheckoutWebView.cacheableCheckoutView(URL, activity, true) - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - - assertThat(viewOne).isEqualTo(viewTwo) - assertThat(shadowOf(viewOne).lastLoadedUrl).startsWith(URL) - assertThat(shadowOf(viewTwo).lastLoadedUrl).startsWith(URL) - } - } - - @Test - fun `calls onPause if preloading so the PageVisibility API reports the correct value`() { - withPreloadingEnabled { - val viewOne = CheckoutWebView.cacheableCheckoutView(URL, activity, true) - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - assertThat(shadowOf(viewOne).wasOnPauseCalled()).isTrue() - } - } - - @Test - fun `does not call onPause if not preloading`() { - withPreloadingEnabled { - val viewOne = CheckoutWebView.cacheableCheckoutView(URL, activity, false) - - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - - assertThat(shadowOf(viewOne).wasOnPauseCalled()).isFalse() - } - } - - @Test - fun `cacheableCheckoutView returns the new view if URL has changed`() { - withPreloadingEnabled { - val newUrl = "https://another.checkout.url" - val viewOne = CheckoutWebView.cacheableCheckoutView(URL, activity, true) - val viewTwo = CheckoutWebView.cacheableCheckoutView(newUrl, activity, true) - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - - assertThat(viewOne).isNotEqualTo(viewTwo) - assertThat(shadowOf(viewOne).lastLoadedUrl).startsWith(URL) - assertThat(shadowOf(viewTwo).lastLoadedUrl).startsWith(newUrl) - assertThat(shadowOf(viewOne).wasDestroyCalled()).isTrue - assertThat(shadowOf(viewTwo).wasDestroyCalled()).isFalse - } - } - - @Test - fun `cacheableCheckoutView returns the a new view for each call if preloading disabled`() { - val viewOne = CheckoutWebView.cacheableCheckoutView(URL, activity, true) - val viewTwo = CheckoutWebView.cacheableCheckoutView(URL, activity, true) - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - - assertThat(viewOne).isNotEqualTo(viewTwo) - assertThat(shadowOf(viewOne).lastLoadedUrl).startsWith(URL) - assertThat(shadowOf(viewTwo).lastLoadedUrl).startsWith(URL) - - assertThat(shadowOf(viewOne).wasDestroyCalled()).isTrue - assertThat(shadowOf(viewTwo).wasDestroyCalled()).isFalse - } - - @Test - fun `clear cache removes the view from the cache and destroys it`() { - withPreloadingEnabled { - val viewOne = CheckoutWebView.cacheableCheckoutView(URL, activity, true) - CheckoutWebView.clearCache() - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - val viewTwo = CheckoutWebView.cacheableCheckoutView(URL, activity, true) - - assertThat(viewOne).isNotEqualTo(viewTwo) - assertThat(shadowOf(viewOne).wasDestroyCalled()).isTrue - assertThat(shadowOf(viewTwo).wasDestroyCalled()).isFalse - } - } - - @Test - fun `clear cache does nothing if cache empty`() { - withPreloadingEnabled { - assertThatNoException().isThrownBy { - CheckoutWebView.clearCache() - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - } - } - } - - @Test - fun `markCacheEntryStale makes the cache entry stale but does not clear the cache or destroy the view`() { - withPreloadingEnabled { - val viewOne = CheckoutWebView.cacheableCheckoutView(URL, activity, true) - CheckoutWebView.markCacheEntryStale() - - assertThat(CheckoutWebView).isNotNull() - assertThat(CheckoutWebView.cacheEntry!!.isValid(URL)).isFalse() - - val viewTwo = CheckoutWebView.cacheableCheckoutView(URL, activity, true) - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - - assertThat(viewOne).isNotEqualTo(viewTwo) - assertThat(shadowOf(viewOne).lastLoadedUrl).startsWith(URL) - assertThat(shadowOf(viewTwo).lastLoadedUrl).startsWith(URL) - } - } - - @Test - fun `web view cache should have a ttl of 5 minutes`() { - withPreloadingEnabled { - val now = System.currentTimeMillis() - val mockCacheClock = mock() - whenever(mockCacheClock.currentTimeMillis()) - .thenReturn(now) - .thenReturn(now.plus(2.minutes.inWholeMilliseconds)) - .thenReturn(now.plus(5.minutes.inWholeMilliseconds)) - - CheckoutWebView.cacheClock = mockCacheClock - - val viewOne = CheckoutWebView.cacheableCheckoutView(URL, activity, true) - val viewTwo = CheckoutWebView.cacheableCheckoutView(URL, activity, true) - val viewThree = CheckoutWebView.cacheableCheckoutView(URL, activity, true) - - // only 2 minutes have passed, cache entry still valid - assertThat(viewOne).isEqualTo(viewTwo) - - // 5 minutes have passed, cache entry now invalid - assertThat(viewTwo).isNotEqualTo(viewThree) - } - } - - private fun listener(): CheckoutListener = NoopCheckoutListener() - - companion object { - private const val URL = "https://a.checkout.testurl" - } -} diff --git a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutWebViewClientTest.kt b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutWebViewClientTest.kt index 72a06494..007c1379 100644 --- a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutWebViewClientTest.kt +++ b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutWebViewClientTest.kt @@ -50,7 +50,6 @@ import org.robolectric.Shadows.shadowOf import org.robolectric.annotation.Config import org.robolectric.shadows.ShadowLooper import java.net.HttpURLConnection -import kotlin.time.Duration.Companion.minutes @RunWith(RobolectricTestRunner::class) class CheckoutWebViewClientTest { @@ -149,12 +148,11 @@ class CheckoutWebViewClientTest { } @Test - fun `should call event processor and clear cache on web resource load error for main frame`() { + fun `should call event processor on web resource load error for main frame`() { val mockRequest = mockWebRequest(Uri.parse("https://checkout-sdk.myshopify.com"), true) val mockResponse = mockWebResourceError() val view = viewWithProcessor(activity) - CheckoutWebView.cacheEntry = view.toCacheEntry(mockRequest.url.toString()) val webViewClient = view.CheckoutWebViewClient() webViewClient.onReceivedError(view, mockRequest, mockResponse) @@ -379,7 +377,6 @@ class CheckoutWebViewClientTest { private fun triggerOnReceivedHttpError(mockRequest: WebResourceRequest, checkoutExpiredResponse: WebResourceResponse) { val view = viewWithProcessor(activity) - CheckoutWebView.cacheEntry = view.toCacheEntry(mockRequest.url.toString()) val webViewClient = view.CheckoutWebViewClient() webViewClient.onReceivedHttpError(view, mockRequest, checkoutExpiredResponse) @@ -433,13 +430,4 @@ class CheckoutWebViewClientTest { whenever(mock.responseHeaders).thenReturn(headers) return mock } - - private fun CheckoutWebView.toCacheEntry(key: String): CheckoutWebView.CheckoutWebViewCacheEntry { - return CheckoutWebView.CheckoutWebViewCacheEntry( - key = key, - view = this, - clock = CheckoutWebView.CheckoutWebViewCacheClock(), - timeout = 5.minutes.inWholeMilliseconds, - ) - } } diff --git a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutWebViewContainerTest.kt b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutWebViewContainerTest.kt deleted file mode 100644 index e83ed08f..00000000 --- a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutWebViewContainerTest.kt +++ /dev/null @@ -1,178 +0,0 @@ -/* - * MIT License - * - * Copyright 2023-present, Shopify Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.shopify.checkoutkit - -import android.os.Looper -import androidx.activity.ComponentActivity -import org.assertj.core.api.Assertions.assertThat -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.kotlin.mock -import org.mockito.kotlin.whenever -import org.robolectric.Robolectric -import org.robolectric.RobolectricTestRunner -import org.robolectric.Shadows.shadowOf - -@RunWith(RobolectricTestRunner::class) -class CheckoutWebViewContainerTest { - - private val mockCacheClock = mock() - - @Before - fun setUp() { - CheckoutWebView.cacheClock = mockCacheClock - } - - @After - fun tearDown() { - CheckoutWebView.clearCache() - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - } - - // cache entries are essentially immediately stale if preloading is disabled - @Test - fun `should destroy CheckoutWebView when retainCacheEntry is IF_NOT_STALE and preloading is disabled`() { - Robolectric.buildActivity(ComponentActivity::class.java).use { activityController -> - val activity = activityController.get() - - val container = CheckoutWebViewContainer(activity) - val checkoutWebView = CheckoutWebView.cacheableCheckoutView("https://shopify.dev", activity) - val shadow = shadowOf(checkoutWebView) - - container.addView(checkoutWebView) - assertThat(shadow.wasDestroyCalled()).isFalse() - - CheckoutWebViewContainer.retainCacheEntry = RetainCacheEntry.IF_NOT_STALE - container.removeView(checkoutWebView) - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - - assertThat(shadow.wasDestroyCalled()).isTrue() - } - } - - @Test - fun `should destroy CheckoutWebView when retainCacheEntry is IF_NOT_STALE and entry is stale`() { - Robolectric.buildActivity(ComponentActivity::class.java).use { activityController -> - withPreloadingEnabled { - whenever(mockCacheClock.currentTimeMillis()).thenReturn(System.currentTimeMillis()) - - val activity = activityController.get() - - val container = CheckoutWebViewContainer(activity) - val checkoutWebView = CheckoutWebView.cacheableCheckoutView("https://shopify.dev", activity, true) - val shadow = shadowOf(checkoutWebView) - - container.addView(checkoutWebView) - assertThat(shadow.wasDestroyCalled()).isFalse() - - CheckoutWebViewContainer.retainCacheEntry = RetainCacheEntry.IF_NOT_STALE - makeCacheEntryStale() - - container.removeView(checkoutWebView) - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - - assertThat(shadow.wasDestroyCalled()).isTrue() - } - } - } - - @Test - fun `should not destroy non-stale CheckoutWebView when retainCacheEntry == IF_NOT_STALE and entry is not stale`() { - Robolectric.buildActivity(ComponentActivity::class.java).use { activityController -> - withPreloadingEnabled { - val activity = activityController.get() - - val container = CheckoutWebViewContainer(activity) - val checkoutWebView = CheckoutWebView.cacheableCheckoutView("https://shopify.dev", activity, true) - val shadow = shadowOf(checkoutWebView) - - container.addView(checkoutWebView) - assertThat(shadow.wasDestroyCalled()).isFalse() - - CheckoutWebViewContainer.retainCacheEntry = RetainCacheEntry.IF_NOT_STALE - container.removeView(checkoutWebView) - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - - assertThat(shadow.wasDestroyCalled()).isFalse() - } - } - } - - @Test - fun `should not destroy CheckoutWebView when retainCacheEntry == YES and entry is stale`() { - Robolectric.buildActivity(ComponentActivity::class.java).use { activityController -> - withPreloadingEnabled { - whenever(mockCacheClock.currentTimeMillis()).thenReturn(System.currentTimeMillis()) - - val activity = activityController.get() - - val container = CheckoutWebViewContainer(activity) - val checkoutWebView = CheckoutWebView.cacheableCheckoutView("https://shopify.dev", activity, true) - val shadow = shadowOf(checkoutWebView) - - container.addView(checkoutWebView) - assertThat(shadow.wasDestroyCalled()).isFalse() - - CheckoutWebViewContainer.retainCacheEntry = RetainCacheEntry.YES - makeCacheEntryStale() - - container.removeView(checkoutWebView) - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - assertThat(shadow.wasDestroyCalled()).isFalse() - assertThat(CheckoutWebViewContainer.retainCacheEntry).isEqualTo(RetainCacheEntry.IF_NOT_STALE) - } - } - } - - @Test - fun `should not destroy CheckoutWebView when retainCacheEntry == YES and entry is not stale`() { - Robolectric.buildActivity(ComponentActivity::class.java).use { activityController -> - withPreloadingEnabled { - whenever(mockCacheClock.currentTimeMillis()).thenReturn(System.currentTimeMillis()) - - val activity = activityController.get() - - val container = CheckoutWebViewContainer(activity) - val checkoutWebView = CheckoutWebView.cacheableCheckoutView("https://shopify.dev", activity, true) - val shadow = shadowOf(checkoutWebView) - - container.addView(checkoutWebView) - assertThat(shadow.wasDestroyCalled()).isFalse() - - CheckoutWebViewContainer.retainCacheEntry = RetainCacheEntry.YES - - container.removeView(checkoutWebView) - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - assertThat(shadow.wasDestroyCalled()).isFalse() - assertThat(CheckoutWebViewContainer.retainCacheEntry).isEqualTo(RetainCacheEntry.IF_NOT_STALE) - } - } - } - - private fun makeCacheEntryStale() { - val initialTime = mockCacheClock.currentTimeMillis() - whenever(mockCacheClock.currentTimeMillis()).thenReturn(initialTime.plus(60 * 10 * 1000)) - } -} diff --git a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutWebViewTest.kt b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutWebViewTest.kt index c2b05f7c..cb901079 100644 --- a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutWebViewTest.kt +++ b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutWebViewTest.kt @@ -26,11 +26,11 @@ import android.graphics.Color import android.net.Uri import android.os.Looper import android.view.View.VISIBLE -import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.webkit.GeolocationPermissions import android.webkit.PermissionRequest import android.webkit.ValueCallback import android.webkit.WebChromeClient.FileChooserParams +import android.widget.FrameLayout import androidx.activity.ComponentActivity import org.assertj.core.api.Assertions.assertThat import org.junit.After @@ -52,9 +52,6 @@ class CheckoutWebViewTest { @Before fun setUp() { - CheckoutWebView.clearCache() - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - activity = Robolectric.buildActivity(ComponentActivity::class.java).get() } @@ -63,23 +60,13 @@ class CheckoutWebViewTest { ShopifyCheckoutKit.configuration.platform = null } - private fun loadedUrl(platform: Platform? = null): String { - ShopifyCheckoutKit.configuration.platform = platform - val view = CheckoutWebView(activity) - view.loadCheckout("https://checkout.shopify.com/cart/123", false) - ShadowLooper.shadowMainLooper().runToEndOfTasks() - return shadowOf(view).lastLoadedUrl!! - } - @Test fun `configures web view on initialization`() { - val view = CheckoutWebView.cacheableCheckoutView(URL, activity) + val view = CheckoutWebView(activity) assertThat(view.visibility).isEqualTo(VISIBLE) assertThat(view.settings.javaScriptEnabled).isTrue assertThat(view.settings.domStorageEnabled).isTrue - assertThat(view.layoutParams.height).isEqualTo(MATCH_PARENT) - assertThat(view.layoutParams.width).isEqualTo(MATCH_PARENT) assertThat(view.id).isNotNull assertThat(shadowOf(view).webViewClient.javaClass).isEqualTo(CheckoutWebView.CheckoutWebViewClient::class.java) assertThat(shadowOf(view).backgroundColor).isEqualTo(Color.TRANSPARENT) @@ -89,7 +76,7 @@ class CheckoutWebViewTest { @Test fun `user agent suffix contains ShopifyCheckoutKit version and android platform`() { - val view = CheckoutWebView.cacheableCheckoutView(URL, activity) + val view = CheckoutWebView(activity) assertThat(view.settings.userAgentString).contains("ShopifyCheckoutKit/") assertThat(view.settings.userAgentString).contains("(Android;") @@ -98,7 +85,7 @@ class CheckoutWebViewTest { @Test fun `user agent suffix appends platform identifier and version when set`() { ShopifyCheckoutKit.configuration.platform = Platform.ReactNative("0.80.0") - val view = CheckoutWebView.cacheableCheckoutView(URL, activity) + val view = CheckoutWebView(activity) val kotlinVersion = KotlinVersion.CURRENT.let { "${it.major}.${it.minor}" } assertThat(view.settings.userAgentString) @@ -110,40 +97,16 @@ class CheckoutWebViewTest { @Test fun `user agent suffix omits version when platform version is null`() { ShopifyCheckoutKit.configuration.platform = Platform.ReactNative() - val view = CheckoutWebView.cacheableCheckoutView(URL, activity) + val view = CheckoutWebView(activity) val kotlinVersion = KotlinVersion.CURRENT.let { "${it.major}.${it.minor}" } assertThat(view.settings.userAgentString) .endsWith("ShopifyCheckoutKit/${ShopifyCheckoutKit.version} (Android; Kotlin $kotlinVersion) ReactNative") } - @Test - fun `sends prefetch header for preloads`() { - withPreloadingEnabled { - val isPreload = true - val view = CheckoutWebView.cacheableCheckoutView(URL, activity, isPreload) - - val shadow = shadowOf(view) - ShadowLooper.shadowMainLooper().runToEndOfTasks() - - assertThat(shadow.lastAdditionalHttpHeaders.getOrDefault("Shopify-Purpose", "")).isEqualTo("prefetch") - } - } - - @Test - fun `does not send prefetch header for preloads`() { - val isPreload = false - val view = CheckoutWebView.cacheableCheckoutView(URL, activity, isPreload) - - val shadow = shadowOf(view) - ShadowLooper.shadowMainLooper().runToEndOfTasks() - - assertThat(shadow.lastAdditionalHttpHeaders.getOrDefault("Shopify-Purpose", "")).isEqualTo("") - } - @Test fun `attaches javascript interface onAttachedToWindow`() { - val view = CheckoutWebView.cacheableCheckoutView(URL, activity) + val view = CheckoutWebView(activity) val shadow = shadowOf(view) shadow.callOnAttachedToWindow() @@ -154,7 +117,7 @@ class CheckoutWebViewTest { @Test fun `removes javascript interface onDetachedFromWindow`() { - val view = CheckoutWebView.cacheableCheckoutView(URL, activity) + val view = CheckoutWebView(activity) val shadow = shadowOf(view) shadow.callOnDetachedFromWindow() @@ -164,7 +127,7 @@ class CheckoutWebViewTest { @Test fun `calls update progress when new progress is reported by WebChromeClient`() { - val view = CheckoutWebView.cacheableCheckoutView(URL, activity) + val view = CheckoutWebView(activity) val webViewListener = mock() view.setListener(webViewListener) @@ -179,7 +142,7 @@ class CheckoutWebViewTest { @Test fun `calls processors onPermissionRequest when resource permission requested`() { - val view = CheckoutWebView.cacheableCheckoutView(URL, activity) + val view = CheckoutWebView(activity) val webViewListener = mock() view.setListener(webViewListener) @@ -195,7 +158,7 @@ class CheckoutWebViewTest { @Test fun `calls processors onShowFileChooser when called on webChromeClient`() { - val view = CheckoutWebView.cacheableCheckoutView(URL, activity) + val view = CheckoutWebView(activity) val webViewListener = mock() view.setListener(webViewListener) @@ -210,7 +173,7 @@ class CheckoutWebViewTest { @Test fun `calls processors onGeolocationPermissionsShowPrompt when called on webChromeClient`() { - val view = CheckoutWebView.cacheableCheckoutView(URL, activity) + val view = CheckoutWebView(activity) val webViewListener = mock() view.setListener(webViewListener) @@ -226,37 +189,33 @@ class CheckoutWebViewTest { @Test fun `removeFromParent() should remove parent if a parent exists but not destroy WebView`() { - withPreloadingEnabled { - Robolectric.buildActivity(ComponentActivity::class.java).use { activityController -> - val ctx = activityController.get() - val webView = CheckoutWebView.cacheableCheckoutView("https://shopify.dev", ctx, true) - val container = CheckoutWebViewContainer(ctx) - container.addView(webView) - assertThat(webView.parent).isNotNull() - - webView.removeFromParent() - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - - val shadow = shadowOf(webView) - assertThat(shadow.wasDestroyCalled()).isFalse() - assertThat(webView.parent).isNull() - } + Robolectric.buildActivity(ComponentActivity::class.java).use { activityController -> + val ctx = activityController.get() + val webView = CheckoutWebView(ctx) + val container = FrameLayout(ctx) + container.addView(webView) + assertThat(webView.parent).isNotNull() + + webView.removeFromParent() + shadowOf(Looper.getMainLooper()).runToEndOfTasks() + + val shadow = shadowOf(webView) + assertThat(shadow.wasDestroyCalled()).isFalse() + assertThat(webView.parent).isNull() } } @Test fun `removeFromParent() should do nothing if no parent exists`() { - withPreloadingEnabled { - Robolectric.buildActivity(ComponentActivity::class.java).use { activityController -> - val ctx = activityController.get() - val webView = CheckoutWebView.cacheableCheckoutView("https://shopify.dev", ctx, true) - webView.removeFromParent() - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - - val shadow = shadowOf(webView) - assertThat(shadow.wasDestroyCalled()).isFalse() - assertThat(webView.parent).isNull() - } + Robolectric.buildActivity(ComponentActivity::class.java).use { activityController -> + val ctx = activityController.get() + val webView = CheckoutWebView(ctx) + webView.removeFromParent() + shadowOf(Looper.getMainLooper()).runToEndOfTasks() + + val shadow = shadowOf(webView) + assertThat(shadow.wasDestroyCalled()).isFalse() + assertThat(webView.parent).isNull() } } @@ -265,7 +224,7 @@ class CheckoutWebViewTest { @Test fun `loadCheckout appends ec_version to URL when absent`() { val view = CheckoutWebView(activity) - view.loadCheckout("https://checkout.shopify.com/cart/123", false) + view.loadCheckout("https://checkout.shopify.com/cart/123") ShadowLooper.shadowMainLooper().runToEndOfTasks() assertThat(shadowOf(view).lastLoadedUrl).contains("ec_version=${CheckoutProtocol.specVersion}") @@ -274,7 +233,7 @@ class CheckoutWebViewTest { @Test fun `loadCheckout preserves existing query params alongside ec_version`() { val view = CheckoutWebView(activity) - view.loadCheckout("https://checkout.shopify.com/cart/123?foo=bar", false) + view.loadCheckout("https://checkout.shopify.com/cart/123?foo=bar") ShadowLooper.shadowMainLooper().runToEndOfTasks() val loadedUrl = shadowOf(view).lastLoadedUrl @@ -286,7 +245,7 @@ class CheckoutWebViewTest { fun `loadCheckout does not duplicate ec_version when already present`() { val view = CheckoutWebView(activity) val urlWithVersion = "https://checkout.shopify.com/cart/123?ec_version=2026-01-23" - view.loadCheckout(urlWithVersion, false) + view.loadCheckout(urlWithVersion) ShadowLooper.shadowMainLooper().runToEndOfTasks() val loadedUrl = shadowOf(view).lastLoadedUrl!! @@ -301,7 +260,7 @@ class CheckoutWebViewTest { @Test fun `loadCheckout appends ec_delegate=window_open to URL`() { val view = CheckoutWebView(activity) - view.loadCheckout("https://checkout.shopify.com/cart/123", false) + view.loadCheckout("https://checkout.shopify.com/cart/123") ShadowLooper.shadowMainLooper().runToEndOfTasks() assertThat(shadowOf(view).lastLoadedUrl).contains("ec_delegate=window.open") @@ -310,7 +269,7 @@ class CheckoutWebViewTest { @Test fun `loadCheckout does not duplicate ec_delegate when already present`() { val view = CheckoutWebView(activity) - view.loadCheckout("https://checkout.shopify.com/cart/123?ec_delegate=window.open", false) + view.loadCheckout("https://checkout.shopify.com/cart/123?ec_delegate=window.open") ShadowLooper.shadowMainLooper().runToEndOfTasks() val loadedUrl = shadowOf(view).lastLoadedUrl!! @@ -319,8 +278,4 @@ class CheckoutWebViewTest { } // endregion - - companion object { - private const val URL = "https://a.checkout.testurl" - } } diff --git a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/ConfigurationTest.kt b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/ConfigurationTest.kt index 03217b06..547f387c 100644 --- a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/ConfigurationTest.kt +++ b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/ConfigurationTest.kt @@ -40,7 +40,6 @@ class ConfigurationTest { fun tearDown() { ShopifyCheckoutKit.configure { it.colorScheme = initialConfiguration.colorScheme - it.preloading = initialConfiguration.preloading } } @@ -79,22 +78,4 @@ class ConfigurationTest { assertThat(ShopifyCheckoutKit.getConfiguration().colorScheme).isEqualTo(ColorScheme.Automatic()) } - - @Test - fun `can set preloading via configure function - enabled`() { - ShopifyCheckoutKit.configure { - it.preloading = Preloading(enabled = true) - } - - assertThat(ShopifyCheckoutKit.getConfiguration().preloading.enabled).isTrue - } - - @Test - fun `can set preloading via configure function - disabled`() { - ShopifyCheckoutKit.configure { - it.preloading = Preloading(enabled = false) - } - - assertThat(ShopifyCheckoutKit.getConfiguration().preloading.enabled).isFalse - } } diff --git a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/EmbeddedCheckoutProtocolTest.kt b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/EmbeddedCheckoutProtocolTest.kt index 0edeaf77..8337b2c2 100644 --- a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/EmbeddedCheckoutProtocolTest.kt +++ b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/EmbeddedCheckoutProtocolTest.kt @@ -54,9 +54,6 @@ class EmbeddedCheckoutProtocolTest { @Before fun setUp() { - CheckoutWebView.clearCache() - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - activity = Robolectric.buildActivity(ComponentActivity::class.java).setup().get() // Mirror real-Android behavior: startActivity throws ActivityNotFoundException when // no activity resolves the intent. Robolectric defaults to silently recording the @@ -399,23 +396,6 @@ class EmbeddedCheckoutProtocolTest { verify(client).process(rawMessage) } - @Test - fun `ec complete marks the preloaded cache entry stale`() { - ShopifyCheckoutKit.configure { it.preloading = Preloading(enabled = true) } - try { - CheckoutWebView.cacheableCheckoutView("https://shopify.com/checkout", activity, isPreload = true) - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - assertThat(CheckoutWebView.cacheEntry!!.isValid("https://shopify.com/checkout")).isTrue() - - ecp.postMessage("""{"jsonrpc":"2.0","method":"ec.complete","params":{"checkout":{}}}""") - shadowOf(Looper.getMainLooper()).runToEndOfTasks() - - assertThat(CheckoutWebView.cacheEntry!!.isValid("https://shopify.com/checkout")).isFalse() - } finally { - ShopifyCheckoutKit.configure { it.preloading = Preloading(enabled = false) } - } - } - // endregion // region client delegation — requests diff --git a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/InteropTest.java b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/InteropTest.java index ffebff7d..8d91a079 100644 --- a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/InteropTest.java +++ b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/InteropTest.java @@ -135,7 +135,6 @@ public void setUp() { public void tearDown() { ShopifyCheckoutKit.configure(config -> { config.setColorScheme(initialConfiguration.getColorScheme()); - config.setPreloading(initialConfiguration.getPreloading()); }); } @@ -200,14 +199,12 @@ public void canAccessFieldsOnCheckoutCompletedEvent() { @Test public void canConfigureCheckoutKit() { ShopifyCheckoutKit.configure(configuration -> { - configuration.setPreloading(new Preloading(false)); configuration.setColorScheme(new ColorScheme.Dark()); }); Configuration configuration = ShopifyCheckoutKit.getConfiguration(); assertThat(configuration.getColorScheme().getId()).isEqualTo("dark"); - assertThat(configuration.getPreloading().getEnabled()).isEqualTo(false); } @Test diff --git a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/ShopifyCheckoutKitTest.kt b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/ShopifyCheckoutKitTest.kt index cffa011a..55ffede7 100644 --- a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/ShopifyCheckoutKitTest.kt +++ b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/ShopifyCheckoutKitTest.kt @@ -24,176 +24,27 @@ package com.shopify.checkoutkit import androidx.activity.ComponentActivity import org.assertj.core.api.Assertions.assertThat -import org.junit.After -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner -import org.robolectric.Shadows.shadowOf -import org.robolectric.shadows.ShadowLooper @RunWith(RobolectricTestRunner::class) class ShopifyCheckoutKitTest { - @Before - fun setUp() { - ShopifyCheckoutKit.configure { - it.preloading = Preloading(enabled = false) - } - } - - @After - fun tearDown() { - CheckoutWebView.cacheEntry = null - } - - @Test - fun `preload is a noop if preloading is not enabled`() { - Robolectric.buildActivity(ComponentActivity::class.java).use { activityController -> - val url = "https://shopify.dev" - ShopifyCheckoutKit.preload(url, activityController.get()) - ShadowLooper.shadowMainLooper().runToEndOfTasks() - - assertThat(CheckoutWebView.cacheEntry).isNull() - } - } - - @Test - fun `preload caches a WebView and loads the URL if cache is currently empty`() { - Robolectric.buildActivity(ComponentActivity::class.java).use { activityController -> - withPreloadingEnabled { - val url = "https://shopify.dev" - ShopifyCheckoutKit.preload(url, activityController.get()) - ShadowLooper.shadowMainLooper().runToEndOfTasks() - - assertThat(CheckoutWebView.cacheEntry).isNotNull() - val entry = CheckoutWebView.cacheEntry!! - - assertThat(entry.isStale).isFalse() - assertThat(entry.view).isInstanceOf(CheckoutWebView::class.java) - } - } - } - - @Test - fun `invalidate marks cache entry as stale meaning it will not be used when presenting`() { - Robolectric.buildActivity(ComponentActivity::class.java).use { activityController -> - withPreloadingEnabled { - val url = "https://shopify.dev" - ShopifyCheckoutKit.preload(url, activityController.get()) - ShadowLooper.shadowMainLooper().runToEndOfTasks() - - ShopifyCheckoutKit.invalidate() - ShadowLooper.shadowMainLooper().runToEndOfTasks() - - assertThat(CheckoutWebView.cacheEntry).isNotNull() - val entry = CheckoutWebView.cacheEntry!! - - assertThat(entry.isStale).isTrue() - } - } - } - @Test - fun `preload caches a new WebView, loads the URL, and destroys old view if cache is not empty`() { + fun `present returns null when activity is finishing`() { Robolectric.buildActivity(ComponentActivity::class.java).use { activityController -> - withPreloadingEnabled { - val url = "https://shopify.dev" - ShopifyCheckoutKit.preload(url, activityController.get()) - ShadowLooper.shadowMainLooper().runToEndOfTasks() - val originalEntry = CheckoutWebView.cacheEntry - - ShopifyCheckoutKit.preload(url, activityController.get()) - ShadowLooper.shadowMainLooper().runToEndOfTasks() - - assertThat(CheckoutWebView.cacheEntry).isNotNull() - val entry = CheckoutWebView.cacheEntry!! - - assertThat(entry.isStale).isFalse() - assertThat(entry.view).isInstanceOf(CheckoutWebView::class.java) - assertThat(entry.view).isNotEqualTo(originalEntry) - assertThat(shadowOf(originalEntry?.view).wasDestroyCalled()).isTrue() - } - } - } - - @Test - fun `preload while presented loads url in the existing view`() { - Robolectric.buildActivity(ComponentActivity::class.java).use { activityController -> - withPreloadingEnabled { - val activity = activityController.get() - - // first preload caches the view - ShopifyCheckoutKit.preload("https://one.com", activityController.get()) - ShadowLooper.shadowMainLooper().runToEndOfTasks() - - val originalEntry = CheckoutWebView.cacheEntry - assertThat(originalEntry!!.key).isEqualTo("https://one.com") - assertThat(originalEntry.isStale).isFalse() - - // present loads the cached view - ShopifyCheckoutKit.present( - "https://one.com", - activity, - noopDefaultCheckoutListener() - ) - ShadowLooper.shadowMainLooper().runToEndOfTasks() - - val secondEntry = CheckoutWebView.cacheEntry - assertThat(secondEntry!!.key).isEqualTo("https://one.com") - assertThat(secondEntry.isStale).isFalse() - - // preload after present loads URL in the cached view - ShopifyCheckoutKit.preload("https://one.com", activityController.get()) - ShadowLooper.shadowMainLooper().runToEndOfTasks() - - val thirdEntry = CheckoutWebView.cacheEntry - assertThat(thirdEntry!!.key).isEqualTo("https://one.com") - assertThat(thirdEntry.isStale).isFalse() - - assertThat(shadowOf(thirdEntry.view).lastLoadedUrl).startsWith("https://one.com") - } - } - } - - @Test - fun `preload after presented loads the url and marks cache stale if url doesn't match cache key`() { - Robolectric.buildActivity(ComponentActivity::class.java).use { activityController -> - withPreloadingEnabled { - val activity = activityController.get() - - // first preload caches the view - ShopifyCheckoutKit.preload("https://one.com", activityController.get()) - ShadowLooper.shadowMainLooper().runToEndOfTasks() - - val originalEntry = CheckoutWebView.cacheEntry - assertThat(originalEntry?.key).isEqualTo("https://one.com") - assertThat(originalEntry?.isStale).isFalse() - - // present loads the cached view - ShopifyCheckoutKit.present( - "https://one.com", - activity, - noopDefaultCheckoutListener() - ) - ShadowLooper.shadowMainLooper().runToEndOfTasks() - - val secondEntry = CheckoutWebView.cacheEntry - assertThat(secondEntry?.key).isEqualTo("https://one.com") - assertThat(secondEntry?.isStale).isFalse() - - // preload after present loads URL in the cached view - ShopifyCheckoutKit.preload("https://two.com", activityController.get()) - ShadowLooper.shadowMainLooper().runToEndOfTasks() + val activity = activityController.get() + activity.finish() - // as the URL no longer matches the cache key, mark the cache entry as stale - val thirdEntry = CheckoutWebView.cacheEntry - assertThat(thirdEntry?.key).isEqualTo("https://one.com") - assertThat(thirdEntry?.isStale).isTrue() + val dialog = ShopifyCheckoutKit.present( + "https://shopify.dev", + activity, + noopDefaultCheckoutListener() + ) - assertThat(shadowOf(thirdEntry?.view).lastLoadedUrl).startsWith("https://two.com") - } + assertThat(dialog).isNull() } } } diff --git a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/MobileBuyIntegration.kt b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/MobileBuyIntegration.kt index a9bfb4b3..63b5ee30 100644 --- a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/MobileBuyIntegration.kt +++ b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/MobileBuyIntegration.kt @@ -54,7 +54,6 @@ class MobileBuyIntegration : Application() { ShopifyCheckoutKit.configure { it.logLevel = LogLevel.DEBUG it.colorScheme = settings.colorScheme.withCustomCloseIcon() - it.preloading = settings.preloading } } } diff --git a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/cart/CartView.kt b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/cart/CartView.kt index 6c445671..221d5d0e 100644 --- a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/cart/CartView.kt +++ b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/cart/CartView.kt @@ -44,7 +44,6 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -84,10 +83,6 @@ fun CartView( val activity = LocalActivity.current as ComponentActivity var mutableQuantity by remember { mutableStateOf>(mutableMapOf()) } - LaunchedEffect(key1 = true) { - cartViewModel.preloadCheckout(activity) - } - if (loading) { ProgressIndicator() } diff --git a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/cart/CartViewModel.kt b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/cart/CartViewModel.kt index 4911d52d..3cb69415 100644 --- a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/cart/CartViewModel.kt +++ b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/cart/CartViewModel.kt @@ -97,8 +97,6 @@ class CartViewModel( try { val cart = cartRepository.modifyCartLine(state.cartID, lineItemId, quantity) Timber.i("Cart modification complete") - Timber.i("Invalidating previous preloads, so checkout reflects modified cart state") - ShopifyCheckoutKit.invalidate() _cartState.value = if (cart.cartTotals.totalQuantity == 0) CartState.Empty else cart _loadingState.value = false } catch (e: Exception) { @@ -145,18 +143,6 @@ class CartViewModel( } } - fun preloadCheckout( - activity: ComponentActivity, - ) { - // val state = _cartState.value - // if (state is CartState.Cart) { - // Timber.i("Preloading checkout with url ${state.checkoutUrl}") - // ShopifyCheckoutKit.preload(state.checkoutUrl, activity) - // } else { - // Timber.i("Skipping checkout preload, cart is empty") - // } - } - fun continueShopping(navController: NavController) { Timber.i("Continue shopping clicked, navigating to products") navController.navigate(Screen.Products.route) diff --git a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/ColorSchemeSection.kt b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/ColorSchemeSection.kt index a3ca51e4..1cc6db80 100644 --- a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/ColorSchemeSection.kt +++ b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/ColorSchemeSection.kt @@ -42,7 +42,6 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import com.shopify.checkout_kit_mobile_buy_integration_sample.R import com.shopify.checkout_kit_mobile_buy_integration_sample.common.components.BodyMedium -import com.shopify.checkout_kit_mobile_buy_integration_sample.common.components.BodySmall import com.shopify.checkout_kit_mobile_buy_integration_sample.common.components.Header3 import com.shopify.checkout_kit_mobile_buy_integration_sample.common.ui.theme.verticalPadding import com.shopify.checkoutkit.Color @@ -108,12 +107,6 @@ fun ColorSchemeSection( modifier = optionModifier, ) } - - Column(Modifier.padding(vertical = 8.dp)) { - BodySmall( - stringResource(R.string.preloading_note), - ) - } } } diff --git a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/PreferencesManager.kt b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/PreferencesManager.kt index a036def7..3567c7b9 100644 --- a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/PreferencesManager.kt +++ b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/PreferencesManager.kt @@ -30,7 +30,6 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import com.shopify.checkoutkit.ColorScheme -import com.shopify.checkoutkit.Preloading import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.serialization.json.Json @@ -44,12 +43,10 @@ class PreferencesManager(private val context: Context) { val colorScheme = decoder.decodeFromString( preferences[COLOR_SCHEME] ?: DEFAULT_COLOR_SCHEME ) - val preloading = preferences[PRELOADING] ?: true val buyerIdentityDemoEnabled = preferences[BUYER_IDENTITY] ?: false UserPreferences( colorScheme = colorScheme, - preloading = Preloading(preloading), buyerIdentityDemoEnabled = buyerIdentityDemoEnabled ) } @@ -57,7 +54,6 @@ class PreferencesManager(private val context: Context) { suspend fun setColorScheme(colorScheme: ColorScheme) = saveData(COLOR_SCHEME, Json.encodeToString(ColorScheme.serializer(), colorScheme)) - suspend fun setPreloadingEnabled(enabled: Boolean) = saveData(PRELOADING, enabled) suspend fun setBuyerIdentityDemoEnabled(enabled: Boolean) = saveData(BUYER_IDENTITY, enabled) private suspend fun saveData(key: Preferences.Key, value: T) = context.dataStore.edit { @@ -66,7 +62,6 @@ class PreferencesManager(private val context: Context) { companion object { private val COLOR_SCHEME = stringPreferencesKey("colorScheme") - private val PRELOADING = booleanPreferencesKey("preloading") private val BUYER_IDENTITY = booleanPreferencesKey("buyerIdentity") private val DEFAULT_COLOR_SCHEME = Json.encodeToString( @@ -78,6 +73,5 @@ class PreferencesManager(private val context: Context) { data class UserPreferences( val colorScheme: ColorScheme, - val preloading: Preloading, val buyerIdentityDemoEnabled: Boolean, ) diff --git a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/PreloadingSwitch.kt b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/PreloadingSwitch.kt deleted file mode 100644 index db5a06cd..00000000 --- a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/PreloadingSwitch.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * MIT License - * - * Copyright 2023-present, Shopify Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.shopify.checkout_kit_mobile_buy_integration_sample.settings - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.material3.Switch -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import com.shopify.checkout_kit_mobile_buy_integration_sample.common.components.BodyMedium - -@Composable -fun PreloadingSwitch( - checked: Boolean, - onCheckedChange: (Boolean) -> Unit, - modifier: Modifier, -) { - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = modifier - ) { - BodyMedium("Preload checkout") - Switch( - checked = checked, - onCheckedChange = onCheckedChange - ) - } -} diff --git a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/SettingsView.kt b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/SettingsView.kt index 839b5049..f720c71b 100644 --- a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/SettingsView.kt +++ b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/SettingsView.kt @@ -110,14 +110,6 @@ fun SettingsView( } } - PreloadingSwitch( - checked = uiState.settings.preloading.enabled, - onCheckedChange = settingsViewModel::setPreloadingEnabled, - modifier = Modifier - .background(color = MaterialTheme.colorScheme.background) - .fillMaxWidth() - ) - BuyerIdentityDemoSwitch( checked = uiState.settings.buyerIdentityDemoEnabled, onCheckedChange = settingsViewModel::setBuyerIdentityDemoEnabled, diff --git a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/SettingsViewModel.kt b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/SettingsViewModel.kt index 16566f41..40ec1c78 100644 --- a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/SettingsViewModel.kt +++ b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/SettingsViewModel.kt @@ -30,7 +30,6 @@ import com.shopify.checkout_kit_mobile_buy_integration_sample.settings.authentic import com.shopify.checkout_kit_mobile_buy_integration_sample.settings.data.Settings import com.shopify.checkout_kit_mobile_buy_integration_sample.settings.data.SettingsRepository import com.shopify.checkoutkit.ColorScheme -import com.shopify.checkoutkit.Preloading import com.shopify.checkoutkit.ShopifyCheckoutKit import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -70,13 +69,6 @@ class SettingsViewModel( settingsRepository.setColorScheme(colorScheme) } - fun setPreloadingEnabled(enabled: Boolean) = viewModelScope.launch { - ShopifyCheckoutKit.configure { - it.preloading = Preloading(enabled = enabled) - } - settingsRepository.setPreloadingEnabled(enabled) - } - fun setBuyerIdentityDemoEnabled(enabled: Boolean) = viewModelScope.launch { settingsRepository.setBuyerIdentityDemoEnabled(enabled) } diff --git a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/data/Settings.kt b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/data/Settings.kt index d58e783a..d7f15885 100644 --- a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/data/Settings.kt +++ b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/data/Settings.kt @@ -23,10 +23,8 @@ package com.shopify.checkout_kit_mobile_buy_integration_sample.settings.data import com.shopify.checkoutkit.ColorScheme -import com.shopify.checkoutkit.Preloading data class Settings( - val preloading: Preloading, val colorScheme: ColorScheme, val buyerIdentityDemoEnabled: Boolean ) diff --git a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/data/SettingsRepository.kt b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/data/SettingsRepository.kt index a02057a5..84f0c5b0 100644 --- a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/data/SettingsRepository.kt +++ b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/settings/data/SettingsRepository.kt @@ -38,7 +38,6 @@ class SettingsRepository( return preferencesManager.userPreferencesFlow.map { preferences -> Settings( colorScheme = preferences.colorScheme, - preloading = preferences.preloading, buyerIdentityDemoEnabled = preferences.buyerIdentityDemoEnabled, ) } @@ -51,13 +50,6 @@ class SettingsRepository( preferencesManager.setColorScheme(colorScheme) } - /** - * Update the [preloading](https://github.com/Shopify/checkout-kit-android/?tab=readme-ov-file#preloading) setting - */ - suspend fun setPreloadingEnabled(enabled: Boolean) { - preferencesManager.setPreloadingEnabled(enabled) - } - /** * Update the buyerIdentity setting, which sets some pre-known customer details * in Cart buyerIdentityInput, prefilling checkout diff --git a/platforms/android/samples/MobileBuyIntegration/app/src/main/res/values/strings.xml b/platforms/android/samples/MobileBuyIntegration/app/src/main/res/values/strings.xml index 06124929..145329f3 100644 --- a/platforms/android/samples/MobileBuyIntegration/app/src/main/res/values/strings.xml +++ b/platforms/android/samples/MobileBuyIntegration/app/src/main/res/values/strings.xml @@ -22,10 +22,6 @@ Light Web - - NOTE: If preloading is enabled, color scheme changes may not be applied unless the cart is preloaded again. - - Sample app version Checkout Kit version diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java b/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java index 19bd7704..fce22fd1 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java @@ -24,10 +24,7 @@ of this software and associated documentation files (the "Software"), to deal package com.shopify.reactnative.checkoutkit; import android.app.Activity; -import android.content.Context; import androidx.activity.ComponentActivity; -import androidx.annotation.NonNull; -import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.Arguments; @@ -99,21 +96,10 @@ public void dismiss() { } } - @ReactMethod - public void preload(String checkoutURL) { - // Public native preload support is not exposed by the local Android SDK. - } - - @ReactMethod - public void invalidateCache() { - ShopifyCheckoutKit.invalidate(); - } - @ReactMethod(isBlockingSynchronousMethod = true) public WritableMap getConfig() { WritableMap resultConfig = Arguments.createMap(); - resultConfig.putBoolean("preloading", checkoutConfig.getPreloading().getEnabled()); resultConfig.putString("colorScheme", colorSchemeToString(checkoutConfig.getColorScheme())); resultConfig.putString("logLevel", logLevelToString(checkoutConfig.getLogLevel())); @@ -122,13 +108,7 @@ public WritableMap getConfig() { @ReactMethod public void setConfig(ReadableMap config) { - Context context = getReactApplicationContext(); - ShopifyCheckoutKit.configure(configuration -> { - if (config.hasKey("preloading")) { - configuration.setPreloading(new Preloading(config.getBoolean("preloading"))); - } - if (config.hasKey("logLevel")) { LogLevel logLevel = getLogLevel(config.getString("logLevel")); configuration.setLogLevel(logLevel); diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/specs/NativeShopifyCheckoutKit.ts b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/specs/NativeShopifyCheckoutKit.ts index d217aa57..b85bd8ba 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/specs/NativeShopifyCheckoutKit.ts +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/specs/NativeShopifyCheckoutKit.ts @@ -54,7 +54,6 @@ type ColorsSpec = { }; type ConfigurationSpec = { - preloading?: boolean; title?: string; colorScheme?: string; logLevel?: string; @@ -62,7 +61,6 @@ type ConfigurationSpec = { }; type ConfigurationResultSpec = { - preloading: boolean; colorScheme: string; logLevel: string; title?: string; @@ -73,9 +71,7 @@ type ConfigurationResultSpec = { export interface Spec extends TurboModule { present(checkoutUrl: string): void; - preload(checkoutUrl: string): void; dismiss(): void; - invalidateCache(): void; setConfig(configuration: ConfigurationSpec): void; getConfig(): ConfigurationResultSpec; configureAcceleratedCheckouts( diff --git a/platforms/react-native/sample/android/app/src/test/java/com/shopify/checkoutkitreactnative/ShopifyCheckoutKitModuleTest.java b/platforms/react-native/sample/android/app/src/test/java/com/shopify/checkoutkitreactnative/ShopifyCheckoutKitModuleTest.java index f4c4b03f..eee9eb01 100644 --- a/platforms/react-native/sample/android/app/src/test/java/com/shopify/checkoutkitreactnative/ShopifyCheckoutKitModuleTest.java +++ b/platforms/react-native/sample/android/app/src/test/java/com/shopify/checkoutkitreactnative/ShopifyCheckoutKitModuleTest.java @@ -15,7 +15,6 @@ import com.shopify.checkoutkit.ConfigurationException; import com.shopify.checkoutkit.HttpException; import com.shopify.checkoutkit.ShopifyCheckoutKit; -import com.shopify.checkoutkit.Preloading; import com.shopify.checkoutkit.ColorScheme; import com.shopify.checkoutkit.LogLevel; import com.shopify.reactnative.checkoutkit.ShopifyCheckoutKitModule; @@ -57,7 +56,6 @@ public class ShopifyCheckoutKitModuleTest { private ShopifyCheckoutKitModule shopifyCheckoutKitModule; // Store initial configuration to restore after each test - private Preloading initialPreloading; private ColorScheme initialColorScheme; private LogLevel initialLogLevel; @@ -87,7 +85,6 @@ public void setup() { shopifyCheckoutKitModule = new ShopifyCheckoutKitModule(mockReactContext); // Capture initial configuration state to restore after each test - initialPreloading = ShopifyCheckoutKitModule.checkoutConfig.getPreloading(); initialColorScheme = ShopifyCheckoutKitModule.checkoutConfig.getColorScheme(); initialLogLevel = ShopifyCheckoutKitModule.checkoutConfig.getLogLevel(); } @@ -101,7 +98,6 @@ public void tearDown() { // Reset configuration to initial state after each test ShopifyCheckoutKit.configure(configuration -> { - configuration.setPreloading(initialPreloading); configuration.setColorScheme(initialColorScheme); configuration.setLogLevel(initialLogLevel); ShopifyCheckoutKitModule.checkoutConfig = configuration; @@ -128,13 +124,6 @@ public void testCanPresentCheckout() { } } - @Test - public void testCanPreloadCheckout() { - String checkoutUrl = "https://shopify.com"; - - shopifyCheckoutKitModule.preload(checkoutUrl); - } - /** * Module name and version */ @@ -159,24 +148,10 @@ public void testConstants() { @Test public void testHasCorrectDefaultConfiguration() { // Test that the module starts with sensible defaults - assertThat(ShopifyCheckoutKitModule.checkoutConfig.getPreloading().getEnabled()) - .isTrue(); - assertThat(ShopifyCheckoutKitModule.checkoutConfig.getColorScheme().getId()) .isEqualTo("automatic"); } - @Test - public void testCanDisablePreloading() { - JavaOnlyMap config = new JavaOnlyMap(); - config.putBoolean("preloading", false); - - shopifyCheckoutKitModule.setConfig(config); - - assertThat(ShopifyCheckoutKitModule.checkoutConfig.getPreloading().getEnabled()) - .isFalse(); - } - @Test public void testCanSetDarkColorScheme() { JavaOnlyMap config = new JavaOnlyMap(); @@ -390,7 +365,6 @@ public void testLogLevelHandlesUppercaseError() { @Test public void testSetConfigWithoutLogLevelDefaultsToError() { JavaOnlyMap config = new JavaOnlyMap(); - config.putBoolean("preloading", true); shopifyCheckoutKitModule.setConfig(config); @@ -524,14 +498,11 @@ public void testCanProcessHttpErrors() { public void testCompleteConfigurationAndEventFlow() { // Set up configuration JavaOnlyMap config = new JavaOnlyMap(); - config.putBoolean("preloading", true); config.putString("colorScheme", "dark"); shopifyCheckoutKitModule.setConfig(config); // Verify configuration was applied - assertThat(ShopifyCheckoutKitModule.checkoutConfig.getPreloading().getEnabled()) - .isTrue(); assertThat(ShopifyCheckoutKitModule.checkoutConfig.getColorScheme().getId()) .isEqualTo("dark"); }