From 313c278362a88b227fc2cf3be19519a0af343b69 Mon Sep 17 00:00:00 2001 From: Trevor Lambert <78672774+trevor-lambert@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:24:45 -0500 Subject: [PATCH 1/6] feat: align Kotlin version with latest React Native version --- build.gradle.kts | 4 ++-- settings.gradle | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6e199b8..ff5fb41 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,10 @@ plugins { id("org.jetbrains.dokka") version "2.0.0" - id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20" + id("org.jetbrains.kotlin.plugin.serialization") version "2.1.20" } buildscript { - val kotlinVersion = "2.2.20" + val kotlinVersion = "2.1.20" extra.apply { set("kotlinVersion", kotlinVersion) } diff --git a/settings.gradle b/settings.gradle index 360dcf8..72ae5a2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,11 +5,11 @@ pluginManagement { mavenCentral() } plugins { - id 'org.jetbrains.kotlin.plugin.serialization' version "2.2.20" - id 'org.jetbrains.kotlin.plugin.compose' version "2.2.20" + id 'org.jetbrains.kotlin.plugin.serialization' version "2.1.20" + id 'org.jetbrains.kotlin.plugin.compose' version "2.1.20" id 'com.android.application' version '8.13.0' id 'com.android.library' version '8.13.0' - id 'org.jetbrains.kotlin.android' version '2.2.20' + id 'org.jetbrains.kotlin.android' version '2.1.20' } } From d80086fd4c399250c850162a2640e99d69a35a41 Mon Sep 17 00:00:00 2001 From: Trevor Lambert <78672774+trevor-lambert@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:31:43 -0500 Subject: [PATCH 2/6] chore: version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 28dd58f..533ed24 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ionic-portals-android", - "version": "0.13.0", + "version": "0.13.0-rn.1", "description": "Ionic Portals", "homepage": "https://ionic.io/portals", "author": "Ionic Team (https://ionic.io)", From 4346353795d5aa9c5b47d19636611dbc70d152e1 Mon Sep 17 00:00:00 2001 From: OS-ruialves Date: Thu, 5 Mar 2026 10:44:37 +0000 Subject: [PATCH 3/6] feat: add third-party LiveUpdates provider support and upgrade dependencies - Add support for custom LiveUpdates managers via setLiveUpdateManager() - Upgrade Capacitor from 7.x to 8.x - Upgrade Android compile/target SDK from 35 to 36 - Upgrade Kotlin from 1.9.25 to 2.1.0 - Upgrade Android Gradle Plugin from 8.7.3 to 8.13.0 - Upgrade Gradle from 8.9 to 9.3.1 - Upgrade Java compatibility from 17 to 21 - Bump minimum SDK from 23 to 24 - Add live-updates-provider dependency --- IonicPortals/build.gradle.kts | 8 ++- .../main/kotlin/io/ionic/portals/Portal.kt | 53 +++++++++++++++++++ .../kotlin/io/ionic/portals/PortalFragment.kt | 7 ++- TestApp/build.gradle.kts | 15 ++++-- TestAppCompose/build.gradle.kts | 11 +++- build.gradle.kts | 6 ++- 6 files changed, 86 insertions(+), 14 deletions(-) diff --git a/IonicPortals/build.gradle.kts b/IonicPortals/build.gradle.kts index 119a5e6..797a0db 100644 --- a/IonicPortals/build.gradle.kts +++ b/IonicPortals/build.gradle.kts @@ -32,8 +32,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } + kotlinOptions { + jvmTarget = "21" } publishing { singleVariant("release") @@ -51,6 +54,7 @@ dependencies { api("com.capacitorjs:core:[8.0.0,9.0.0)") compileOnly("io.ionic:liveupdates:0.5.5") + compileOnly("io.ionic:live-updates-provider:LOCAL-SNAPSHOT") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") implementation("androidx.core:core-ktx:1.15.0") diff --git a/IonicPortals/src/main/kotlin/io/ionic/portals/Portal.kt b/IonicPortals/src/main/kotlin/io/ionic/portals/Portal.kt index f5f7abe..c93b06b 100644 --- a/IonicPortals/src/main/kotlin/io/ionic/portals/Portal.kt +++ b/IonicPortals/src/main/kotlin/io/ionic/portals/Portal.kt @@ -1,9 +1,17 @@ package io.ionic.portals import android.content.Context +import android.util.Log +import com.getcapacitor.JSObject import com.getcapacitor.Plugin import io.ionic.liveupdates.LiveUpdate import io.ionic.liveupdates.LiveUpdateManager +import io.ionic.liveupdates.data.model.FailResult +import io.ionic.liveupdatesprovider.LiveUpdatesError +import io.ionic.liveupdatesprovider.SyncCallback +import io.ionic.liveupdatesprovider.models.SyncResult +import io.ionic.liveupdatesprovider.LiveUpdatesManager as LiveUpdatesManagerProvider + /** * A class representing a Portal that contains information about the web content to load and any @@ -85,6 +93,11 @@ class Portal(val name: String) { } } + /** + * A LiveUpdate manager, if live updates is being used. + */ + var liveUpdatesManager: LiveUpdatesManagerProvider? = null + /** * Whether to run a live update sync when the portal is added to the manager. */ @@ -309,6 +322,7 @@ class PortalBuilder(val name: String) { private var portalFragmentType: Class = PortalFragment::class.java private var onCreate: (portal: Portal) -> Unit = {} private var liveUpdateConfig: LiveUpdate? = null + private var liveUpdatesManager: LiveUpdatesManagerProvider? = null private var devMode: Boolean = true internal constructor(name: String, onCreate: (portal: Portal) -> Unit) : this(name) { @@ -561,6 +575,44 @@ class PortalBuilder(val name: String) { return this } + /** + * Set a custom [LiveUpdateManager] instance to be used with the Portal. + * + * Example usage (kotlin): + * ```kotlin + * val liveUpdateManager = LiveUpdateManager() + * builder = builder.setLiveUpdateManager(liveUpdateManager) + * ``` + * + * Example usage (java): + * ```java + * LiveUpdateManager liveUpdateManager = new LiveUpdateManager(); + * builder = builder.setLiveUpdateManager(liveUpdateManager); + * ``` + * + * @param liveUpdateManager a custom LiveUpdateManager instance + * @return the instance of the PortalBuilder with the LiveUpdateManager set + */ + @JvmOverloads + fun setLiveUpdateManager(context: Context, liveUpdatesManager: LiveUpdatesManagerProvider, updateOnAppLoad: Boolean = true): PortalBuilder { + this.liveUpdatesManager = liveUpdatesManager + if (updateOnAppLoad) { + liveUpdatesManager.sync( + callback = object : SyncCallback { + override fun onComplete(result: SyncResult) { + Log.d("TestApplication", "Live Update sync complete. Did update: ${result.didUpdate}, latest app dir: ${result.latestAppDirectory}") + } + + override fun onError(error: LiveUpdatesError.SyncFailed) { + Log.e("TestApplication", "Live Update sync failed: ${error.message}") + } + } + + ) + } + return this + } + /** * Set development mode on the Portal which will look for a server URL set by the Portals CLI. * This is set to true by default but can be turned off manually if desired. @@ -598,6 +650,7 @@ class PortalBuilder(val name: String) { portal.initialContext = this.initialContext portal.portalFragmentType = this.portalFragmentType portal.liveUpdateConfig = this.liveUpdateConfig + portal.liveUpdatesManager = this.liveUpdatesManager portal.devMode = this.devMode onCreate(portal) return portal diff --git a/IonicPortals/src/main/kotlin/io/ionic/portals/PortalFragment.kt b/IonicPortals/src/main/kotlin/io/ionic/portals/PortalFragment.kt index 2b73fdb..7e36c97 100644 --- a/IonicPortals/src/main/kotlin/io/ionic/portals/PortalFragment.kt +++ b/IonicPortals/src/main/kotlin/io/ionic/portals/PortalFragment.kt @@ -271,7 +271,7 @@ open class PortalFragment : Fragment { */ fun reload() { if(portal?.liveUpdateConfig != null) { - val latestLiveUpdateFiles = LiveUpdateManager.getLatestAppDirectory(requireContext(), portal?.liveUpdateConfig?.appId!!) + val latestLiveUpdateFiles = portal?.liveUpdatesManager?.latestAppDirectory() ?: LiveUpdateManager.getLatestAppDirectory(requireContext(), portal?.liveUpdateConfig?.appId!!) if (latestLiveUpdateFiles != null) { if (liveUpdateFiles == null || liveUpdateFiles!!.path != latestLiveUpdateFiles.path) { liveUpdateFiles = latestLiveUpdateFiles @@ -285,7 +285,6 @@ open class PortalFragment : Fragment { bridge?.setServerAssetPath(portal?.startDir!!) } } - // Reload the bridge to the existing start url bridge?.reload() } @@ -327,7 +326,7 @@ open class PortalFragment : Fragment { .addWebViewListeners(webViewListeners) if (portal?.liveUpdateConfig != null) { - liveUpdateFiles = LiveUpdateManager.getLatestAppDirectory(requireContext(), portal?.liveUpdateConfig?.appId!!) + liveUpdateFiles = portal?.liveUpdatesManager?.latestAppDirectory() ?: LiveUpdateManager.getLatestAppDirectory(requireContext(), portal?.liveUpdateConfig?.appId!!) bridgeBuilder = if (liveUpdateFiles != null) { if (config == null) { val configFile = File(liveUpdateFiles!!.path + "/capacitor.config.json") @@ -516,4 +515,4 @@ open class PortalFragment : Fragment { } } } -} \ No newline at end of file +} diff --git a/TestApp/build.gradle.kts b/TestApp/build.gradle.kts index 886004a..8a8ba7d 100644 --- a/TestApp/build.gradle.kts +++ b/TestApp/build.gradle.kts @@ -34,14 +34,13 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } } -kotlin { - compilerOptions { - jvmTarget = JvmTarget.JVM_17 + kotlinOptions { + jvmTarget = "21" } } @@ -52,6 +51,8 @@ androidComponents { } dependencies { + implementation("io.ionic:live-updates-provider:LOCAL-SNAPSHOT") + implementation(project(":IonicPortals")) implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.fragment:fragment-ktx:1.6.2") @@ -70,6 +71,7 @@ fun getPortalsKey(): String { val propFile = rootProject.file("local.properties") val properties = Properties() properties.load(FileInputStream(propFile)) +<<<<<<< HEAD val raw = properties.getProperty("portals_key") ?: "" val normalized = if (raw.length >= 2 && raw.first() == '"' && raw.last() == '"') { raw.substring(1, raw.length - 1) @@ -78,4 +80,7 @@ fun getPortalsKey(): String { } val escaped = normalized.replace("\\", "\\\\").replace("\"", "\\\"") return "\"$escaped\"" +======= + return properties.getProperty("portals_key") ?: "" +>>>>>>> e9d0f1b (feat: add third-party LiveUpdates provider support and upgrade dependencies) } diff --git a/TestAppCompose/build.gradle.kts b/TestAppCompose/build.gradle.kts index 58535ca..09859b2 100644 --- a/TestAppCompose/build.gradle.kts +++ b/TestAppCompose/build.gradle.kts @@ -37,8 +37,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } + kotlinOptions { + jvmTarget = "21" } buildFeatures { compose = true @@ -89,6 +92,7 @@ fun getPortalsKey(): String { val propFile = rootProject.file("local.properties") val properties = Properties() properties.load(FileInputStream(propFile)) +<<<<<<< HEAD val raw = properties.getProperty("portals_key") ?: "" val normalized = if (raw.length >= 2 && raw.first() == '"' && raw.last() == '"') { raw.substring(1, raw.length - 1) @@ -97,4 +101,7 @@ fun getPortalsKey(): String { } val escaped = normalized.replace("\\", "\\\\").replace("\"", "\\\"") return "\"$escaped\"" +======= + return properties.getProperty("portals_key") ?: "" +>>>>>>> e9d0f1b (feat: add third-party LiveUpdates provider support and upgrade dependencies) } diff --git a/build.gradle.kts b/build.gradle.kts index ff5fb41..ef90aab 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,8 @@ plugins { } buildscript { - val kotlinVersion = "2.1.20" + //val kotlinVersion = "1.9.25" + val kotlinVersion = "2.1.0" extra.apply { set("kotlinVersion", kotlinVersion) } @@ -22,9 +23,11 @@ buildscript { classpath("io.github.gradle-nexus:publish-plugin:1.1.0") } + classpath("org.jetbrains.dokka:dokka-base:1.7.20") classpath("com.android.tools.build:gradle:8.13.0") classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + classpath("org.jetbrains.kotlin:compose-compiler-gradle-plugin:$kotlinVersion") } } @@ -35,6 +38,7 @@ if (System.getenv("PORTALS_PUBLISH") == "true") { allprojects { repositories { + mavenLocal() google() mavenCentral() } From badb1770d765a9e59ed5950e2cae8ffbad4ee67a Mon Sep 17 00:00:00 2001 From: OS-ruialves Date: Mon, 16 Mar 2026 15:50:04 +0000 Subject: [PATCH 4/6] test(android): add mock provider and manager in test app --- IonicPortals/build.gradle.kts | 6 +- TestApp/build.gradle.kts | 13 +- .../ionic/portals/testapp/TestApplication.kt | 137 +++++++++++++++++- TestAppCompose/build.gradle.kts | 10 +- 4 files changed, 147 insertions(+), 19 deletions(-) diff --git a/IonicPortals/build.gradle.kts b/IonicPortals/build.gradle.kts index 797a0db..70424d2 100644 --- a/IonicPortals/build.gradle.kts +++ b/IonicPortals/build.gradle.kts @@ -32,11 +32,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "21" + jvmTarget = "17" } publishing { singleVariant("release") diff --git a/TestApp/build.gradle.kts b/TestApp/build.gradle.kts index 8a8ba7d..abed446 100644 --- a/TestApp/build.gradle.kts +++ b/TestApp/build.gradle.kts @@ -34,13 +34,14 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } } - kotlinOptions { - jvmTarget = "21" +kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_17 } } @@ -71,7 +72,6 @@ fun getPortalsKey(): String { val propFile = rootProject.file("local.properties") val properties = Properties() properties.load(FileInputStream(propFile)) -<<<<<<< HEAD val raw = properties.getProperty("portals_key") ?: "" val normalized = if (raw.length >= 2 && raw.first() == '"' && raw.last() == '"') { raw.substring(1, raw.length - 1) @@ -80,7 +80,4 @@ fun getPortalsKey(): String { } val escaped = normalized.replace("\\", "\\\\").replace("\"", "\\\"") return "\"$escaped\"" -======= - return properties.getProperty("portals_key") ?: "" ->>>>>>> e9d0f1b (feat: add third-party LiveUpdates provider support and upgrade dependencies) } diff --git a/TestApp/src/main/java/io/ionic/portals/testapp/TestApplication.kt b/TestApp/src/main/java/io/ionic/portals/testapp/TestApplication.kt index a3a206e..b6f894b 100644 --- a/TestApp/src/main/java/io/ionic/portals/testapp/TestApplication.kt +++ b/TestApp/src/main/java/io/ionic/portals/testapp/TestApplication.kt @@ -1,7 +1,101 @@ package io.ionic.portals.testapp import android.app.Application +import android.content.Context +import android.util.Log + +import io.ionic.liveupdatesprovider.LiveUpdatesError.InvalidConfiguration +import io.ionic.liveupdatesprovider.LiveUpdatesError.SyncFailed +import io.ionic.liveupdatesprovider.LiveUpdatesManager +import io.ionic.liveupdatesprovider.LiveUpdatesProvider +import io.ionic.liveupdatesprovider.LiveUpdatesRegistry +import io.ionic.liveupdatesprovider.SyncCallback +import io.ionic.liveupdatesprovider.models.ProviderConfig +import io.ionic.liveupdatesprovider.models.SyncResult import io.ionic.portals.PortalManager +import java.io.File + + +/** + * Mock implementation of LiveUpdatesManager for testing purposes. + * Allows testing config parsing and sync behavior without actual network requests. + */ +internal class MockLiveUpdatesManager( + private val appId: String?, + private val channel: String?, + private val latestAppDir: File?, + private val shouldFail: Boolean, + private val failureDetails: String, + private val didUpdate: Boolean +) : LiveUpdatesManager { + override fun sync(callback: SyncCallback?) { + // Simulate async behavior with a small delay + Thread(Runnable { + try { + Thread.sleep(5000) + } catch (e: InterruptedException) { + e.printStackTrace() + } + if (this.shouldFail) { + val error = SyncFailed(this.failureDetails, null) + if (callback != null) { + callback.onError(error) + } + } else { + val result = SyncResult(this.didUpdate, this.latestAppDir) + if (callback != null) { + callback.onComplete(result) + } + } + }).start() + } + + override fun latestAppDirectory(): File? { + return this.latestAppDir + } +} + + +class MockLiveUpdatesProvider(override val id: String) : LiveUpdatesProvider { + @Throws(InvalidConfiguration::class) + override fun createManager(context: Context, config: ProviderConfig): LiveUpdatesManager { + + val data: MutableMap = config.data as MutableMap + var shouldFail = false + val shouldFailObj = data.get("shouldFail") + if (shouldFailObj is Boolean) { + shouldFail = shouldFailObj + } + + var failureDetails = "Mock sync failed" + val failureDetailsObj = data.get("failureDetails") + if (failureDetailsObj is String) { + failureDetails = failureDetailsObj + } + + var didUpdate = false + val didUpdateObj = data.get("didUpdate") + if (didUpdateObj is Boolean) { + didUpdate = didUpdateObj + } + + // For testing purposes, we can point to a static directory that simulates the latest app version. + // This was gotten from the logs after a successful sync with the real provider + val filePath = + "/data/user/0/io.ionic.portals.ecommercewebapp/files/ionic_apps/3fde24f8/5966bde5-da2e-4b40-8487-2b0fef7c458b" + val latestAppDir = File(filePath) + + return MockLiveUpdatesManager( + data.get("appId") as? String, + data.get("channel") as? String, + latestAppDir, // latestAppDir + shouldFail, + failureDetails, + didUpdate + ) + } +} + class TestApplication: Application() { @@ -9,6 +103,47 @@ class TestApplication: Application() { super.onCreate() PortalManager.register(BuildConfig.PORTALS_KEY) - PortalManager.newPortal("testportal").create() + Log.d("TestApplication", "Registered portal with key: ${BuildConfig.PORTALS_KEY}") + val portalBuilder = PortalManager.newPortal("testportal") + + + + // Register provider + LiveUpdatesRegistry.register(MockLiveUpdatesProvider("mock")) + + // Resolve the provider where you want in the app + val provider = LiveUpdatesRegistry.resolve("mock") + if (provider == null) { + Log.e("TestApplication", "Failed to register MockLiveUpdatesProvider") + } else { + Log.d("TestApplication", "Successfully registered MockLiveUpdatesProvider with ID: ${provider.id}") + } + + // create the 3rd party manager + val manager = provider?.createManager( + this, + ProviderConfig( + mapOf( + "appId" to "testAppId", + "channel" to "testChannel", + "shouldFail" to false, + "failureDetails" to "Simulated sync failure", + "didUpdate" to true, + "endpoint" to "https://cloud.provider.io", + "apiKey" to "" + ) + ) + ) + if (manager == null) { + Log.e("TestApplication", "Failed to create LiveUpdatesManager from MockLiveUpdatesProvider") + } else { + Log.d("TestApplication", "Successfully created LiveUpdatesManager from MockLiveUpdatesProvider") + + + // set the 3rd party manager + portalBuilder.setLiveUpdateManager(this.applicationContext, manager); + } + + val portal = portalBuilder.create() } } \ No newline at end of file diff --git a/TestAppCompose/build.gradle.kts b/TestAppCompose/build.gradle.kts index 09859b2..674cadb 100644 --- a/TestAppCompose/build.gradle.kts +++ b/TestAppCompose/build.gradle.kts @@ -37,11 +37,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "21" + jvmTarget = "17" } buildFeatures { compose = true @@ -92,7 +92,6 @@ fun getPortalsKey(): String { val propFile = rootProject.file("local.properties") val properties = Properties() properties.load(FileInputStream(propFile)) -<<<<<<< HEAD val raw = properties.getProperty("portals_key") ?: "" val normalized = if (raw.length >= 2 && raw.first() == '"' && raw.last() == '"') { raw.substring(1, raw.length - 1) @@ -101,7 +100,4 @@ fun getPortalsKey(): String { } val escaped = normalized.replace("\\", "\\\\").replace("\"", "\\\"") return "\"$escaped\"" -======= - return properties.getProperty("portals_key") ?: "" ->>>>>>> e9d0f1b (feat: add third-party LiveUpdates provider support and upgrade dependencies) } From 9dbdda7fea8288937e29283c3a4226d9ddae5b0e Mon Sep 17 00:00:00 2001 From: OS-ruialves Date: Fri, 20 Mar 2026 16:08:29 +0000 Subject: [PATCH 5/6] chore(android): update portals with live update provider api changes --- .../main/kotlin/io/ionic/portals/Portal.kt | 42 +++++++--- .../kotlin/io/ionic/portals/PortalFragment.kt | 4 +- .../ionic/portals/testapp/TestApplication.kt | 84 +++++++++---------- 3 files changed, 70 insertions(+), 60 deletions(-) diff --git a/IonicPortals/src/main/kotlin/io/ionic/portals/Portal.kt b/IonicPortals/src/main/kotlin/io/ionic/portals/Portal.kt index c93b06b..d64f87e 100644 --- a/IonicPortals/src/main/kotlin/io/ionic/portals/Portal.kt +++ b/IonicPortals/src/main/kotlin/io/ionic/portals/Portal.kt @@ -2,15 +2,13 @@ package io.ionic.portals import android.content.Context import android.util.Log -import com.getcapacitor.JSObject import com.getcapacitor.Plugin import io.ionic.liveupdates.LiveUpdate import io.ionic.liveupdates.LiveUpdateManager -import io.ionic.liveupdates.data.model.FailResult -import io.ionic.liveupdatesprovider.LiveUpdatesError -import io.ionic.liveupdatesprovider.SyncCallback -import io.ionic.liveupdatesprovider.models.SyncResult -import io.ionic.liveupdatesprovider.LiveUpdatesManager as LiveUpdatesManagerProvider +import io.ionic.liveupdateprovider.LiveUpdateError +import io.ionic.liveupdateprovider.SyncCallback +import io.ionic.liveupdateprovider.SyncResult +import io.ionic.liveupdateprovider.LiveUpdateManager as ProviderLiveUpdateManager /** @@ -96,7 +94,7 @@ class Portal(val name: String) { /** * A LiveUpdate manager, if live updates is being used. */ - var liveUpdatesManager: LiveUpdatesManagerProvider? = null + var liveUpdatesManager: ProviderLiveUpdateManager? = null /** * Whether to run a live update sync when the portal is added to the manager. @@ -322,7 +320,7 @@ class PortalBuilder(val name: String) { private var portalFragmentType: Class = PortalFragment::class.java private var onCreate: (portal: Portal) -> Unit = {} private var liveUpdateConfig: LiveUpdate? = null - private var liveUpdatesManager: LiveUpdatesManagerProvider? = null + private var liveUpdatesManager: ProviderLiveUpdateManager? = null private var devMode: Boolean = true internal constructor(name: String, onCreate: (portal: Portal) -> Unit) : this(name) { @@ -569,9 +567,27 @@ class PortalBuilder(val name: String) { LiveUpdateManager.initialize(context) LiveUpdateManager.cleanVersions(context, liveUpdateConfig.appId) LiveUpdateManager.addLiveUpdateInstance(context, liveUpdateConfig) - if (updateOnAppLoad) { + + if (!updateOnAppLoad) return this + + // old way if no manager defined + if (this.liveUpdatesManager == null) { LiveUpdateManager.sync(context, arrayOf(liveUpdateConfig.appId)) + return this } + + this.liveUpdatesManager!!.sync( + callback = object : SyncCallback { + override fun onComplete(result: SyncResult) { + Log.d("PortalBuilder", "Live Update sync complete. Did update: ${result.didUpdate}, latest app dir: ${liveUpdatesManager?.latestAppDirectory}") + } + + override fun onError(error: LiveUpdateError.SyncFailed) { + Log.e("PortalBuilder", "Live Update sync failed: ${error.message}") + } + } + + ) return this } @@ -594,16 +610,16 @@ class PortalBuilder(val name: String) { * @return the instance of the PortalBuilder with the LiveUpdateManager set */ @JvmOverloads - fun setLiveUpdateManager(context: Context, liveUpdatesManager: LiveUpdatesManagerProvider, updateOnAppLoad: Boolean = true): PortalBuilder { + fun setLiveUpdateManager(context: Context, liveUpdatesManager: ProviderLiveUpdateManager, updateOnAppLoad: Boolean = true): PortalBuilder { this.liveUpdatesManager = liveUpdatesManager if (updateOnAppLoad) { - liveUpdatesManager.sync( + this.liveUpdatesManager?.sync( callback = object : SyncCallback { override fun onComplete(result: SyncResult) { - Log.d("TestApplication", "Live Update sync complete. Did update: ${result.didUpdate}, latest app dir: ${result.latestAppDirectory}") + Log.d("TestApplication", "Live Update sync complete. Did update: ${result.didUpdate}, latest app dir: ${liveUpdatesManager.latestAppDirectory}") } - override fun onError(error: LiveUpdatesError.SyncFailed) { + override fun onError(error: LiveUpdateError.SyncFailed) { Log.e("TestApplication", "Live Update sync failed: ${error.message}") } } diff --git a/IonicPortals/src/main/kotlin/io/ionic/portals/PortalFragment.kt b/IonicPortals/src/main/kotlin/io/ionic/portals/PortalFragment.kt index 7e36c97..08d18c7 100644 --- a/IonicPortals/src/main/kotlin/io/ionic/portals/PortalFragment.kt +++ b/IonicPortals/src/main/kotlin/io/ionic/portals/PortalFragment.kt @@ -271,7 +271,7 @@ open class PortalFragment : Fragment { */ fun reload() { if(portal?.liveUpdateConfig != null) { - val latestLiveUpdateFiles = portal?.liveUpdatesManager?.latestAppDirectory() ?: LiveUpdateManager.getLatestAppDirectory(requireContext(), portal?.liveUpdateConfig?.appId!!) + val latestLiveUpdateFiles = portal?.liveUpdatesManager?.latestAppDirectory ?: LiveUpdateManager.getLatestAppDirectory(requireContext(), portal?.liveUpdateConfig?.appId!!) if (latestLiveUpdateFiles != null) { if (liveUpdateFiles == null || liveUpdateFiles!!.path != latestLiveUpdateFiles.path) { liveUpdateFiles = latestLiveUpdateFiles @@ -326,7 +326,7 @@ open class PortalFragment : Fragment { .addWebViewListeners(webViewListeners) if (portal?.liveUpdateConfig != null) { - liveUpdateFiles = portal?.liveUpdatesManager?.latestAppDirectory() ?: LiveUpdateManager.getLatestAppDirectory(requireContext(), portal?.liveUpdateConfig?.appId!!) + liveUpdateFiles = portal?.liveUpdatesManager?.latestAppDirectory ?: LiveUpdateManager.getLatestAppDirectory(requireContext(), portal?.liveUpdateConfig?.appId!!) bridgeBuilder = if (liveUpdateFiles != null) { if (config == null) { val configFile = File(liveUpdateFiles!!.path + "/capacitor.config.json") diff --git a/TestApp/src/main/java/io/ionic/portals/testapp/TestApplication.kt b/TestApp/src/main/java/io/ionic/portals/testapp/TestApplication.kt index b6f894b..d3196ef 100644 --- a/TestApp/src/main/java/io/ionic/portals/testapp/TestApplication.kt +++ b/TestApp/src/main/java/io/ionic/portals/testapp/TestApplication.kt @@ -4,30 +4,29 @@ import android.app.Application import android.content.Context import android.util.Log -import io.ionic.liveupdatesprovider.LiveUpdatesError.InvalidConfiguration -import io.ionic.liveupdatesprovider.LiveUpdatesError.SyncFailed -import io.ionic.liveupdatesprovider.LiveUpdatesManager -import io.ionic.liveupdatesprovider.LiveUpdatesProvider -import io.ionic.liveupdatesprovider.LiveUpdatesRegistry -import io.ionic.liveupdatesprovider.SyncCallback -import io.ionic.liveupdatesprovider.models.ProviderConfig -import io.ionic.liveupdatesprovider.models.SyncResult +import io.ionic.liveupdateprovider.LiveUpdateError.InvalidConfiguration +import io.ionic.liveupdateprovider.LiveUpdateError.SyncFailed +import io.ionic.liveupdateprovider.LiveUpdateManager +import io.ionic.liveupdateprovider.LiveUpdateProvider +import io.ionic.liveupdateprovider.LiveUpdateProviderRegistry +import io.ionic.liveupdateprovider.SyncCallback +import io.ionic.liveupdateprovider.SyncResult import io.ionic.portals.PortalManager import java.io.File /** - * Mock implementation of LiveUpdatesManager for testing purposes. + * Mock implementation of LiveUpdateManager for testing purposes. * Allows testing config parsing and sync behavior without actual network requests. */ -internal class MockLiveUpdatesManager( +internal class MockLiveUpdateManager( private val appId: String?, private val channel: String?, private val latestAppDir: File?, private val shouldFail: Boolean, private val failureDetails: String, private val didUpdate: Boolean -) : LiveUpdatesManager { +) : LiveUpdateManager { override fun sync(callback: SyncCallback?) { // Simulate async behavior with a small delay Thread(Runnable { @@ -38,43 +37,40 @@ internal class MockLiveUpdatesManager( } if (this.shouldFail) { val error = SyncFailed(this.failureDetails, null) - if (callback != null) { - callback.onError(error) - } + callback?.onError(error) } else { - val result = SyncResult(this.didUpdate, this.latestAppDir) - if (callback != null) { - callback.onComplete(result) + val result = object : SyncResult { + override val didUpdate: Boolean = this@MockLiveUpdateManager.didUpdate } + callback?.onComplete(result) } }).start() } - override fun latestAppDirectory(): File? { - return this.latestAppDir - } + override val latestAppDirectory: File? + get() = this.latestAppDir } -class MockLiveUpdatesProvider(override val id: String) : LiveUpdatesProvider { +class MockLiveUpdateProvider(override val id: String) : LiveUpdateProvider { @Throws(InvalidConfiguration::class) - override fun createManager(context: Context, config: ProviderConfig): LiveUpdatesManager { + override fun createManager(context: Context, config: Map?): LiveUpdateManager { - val data: MutableMap = config.data as MutableMap + val data: Map = config ?: emptyMap() var shouldFail = false - val shouldFailObj = data.get("shouldFail") + val shouldFailObj = data["shouldFail"] if (shouldFailObj is Boolean) { shouldFail = shouldFailObj } var failureDetails = "Mock sync failed" - val failureDetailsObj = data.get("failureDetails") + val failureDetailsObj = data["failureDetails"] if (failureDetailsObj is String) { failureDetails = failureDetailsObj } var didUpdate = false - val didUpdateObj = data.get("didUpdate") + val didUpdateObj = data["didUpdate"] if (didUpdateObj is Boolean) { didUpdate = didUpdateObj } @@ -85,9 +81,9 @@ class MockLiveUpdatesProvider(override val id: String) : LiveUpdatesProvider { "/data/user/0/io.ionic.portals.ecommercewebapp/files/ionic_apps/3fde24f8/5966bde5-da2e-4b40-8487-2b0fef7c458b" val latestAppDir = File(filePath) - return MockLiveUpdatesManager( - data.get("appId") as? String, - data.get("channel") as? String, + return MockLiveUpdateManager( + data["appId"] as? String, + data["channel"] as? String, latestAppDir, // latestAppDir shouldFail, failureDetails, @@ -109,35 +105,33 @@ class TestApplication: Application() { // Register provider - LiveUpdatesRegistry.register(MockLiveUpdatesProvider("mock")) + LiveUpdateProviderRegistry.register(MockLiveUpdateProvider("mock")) // Resolve the provider where you want in the app - val provider = LiveUpdatesRegistry.resolve("mock") + val provider = LiveUpdateProviderRegistry.resolve("mock") if (provider == null) { - Log.e("TestApplication", "Failed to register MockLiveUpdatesProvider") + Log.e("TestApplication", "Failed to register MockLiveUpdateProvider") } else { - Log.d("TestApplication", "Successfully registered MockLiveUpdatesProvider with ID: ${provider.id}") + Log.d("TestApplication", "Successfully registered MockLiveUpdateProvider with ID: ${provider.id}") } // create the 3rd party manager val manager = provider?.createManager( this, - ProviderConfig( - mapOf( - "appId" to "testAppId", - "channel" to "testChannel", - "shouldFail" to false, - "failureDetails" to "Simulated sync failure", - "didUpdate" to true, - "endpoint" to "https://cloud.provider.io", - "apiKey" to "" - ) + mapOf( + "appId" to "testAppId", + "channel" to "testChannel", + "shouldFail" to false, + "failureDetails" to "Simulated sync failure", + "didUpdate" to true, + "endpoint" to "https://cloud.provider.io", + "apiKey" to "" ) ) if (manager == null) { - Log.e("TestApplication", "Failed to create LiveUpdatesManager from MockLiveUpdatesProvider") + Log.e("TestApplication", "Failed to create LiveUpdateManager from MockLiveUpdateProvider") } else { - Log.d("TestApplication", "Successfully created LiveUpdatesManager from MockLiveUpdatesProvider") + Log.d("TestApplication", "Successfully created LiveUpdateManager from MockLiveUpdateProvider") // set the 3rd party manager From cdc27b61fcabecd90e89afd2c3e13d09039da500 Mon Sep 17 00:00:00 2001 From: Trevor Lambert <78672774+trevor-lambert@users.noreply.github.com> Date: Mon, 30 Mar 2026 13:23:31 -0500 Subject: [PATCH 6/6] chore: update naming --- IonicPortals/build.gradle.kts | 2 +- .../main/kotlin/io/ionic/portals/Portal.kt | 40 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/IonicPortals/build.gradle.kts b/IonicPortals/build.gradle.kts index 70424d2..0e3e1e1 100644 --- a/IonicPortals/build.gradle.kts +++ b/IonicPortals/build.gradle.kts @@ -54,7 +54,7 @@ dependencies { api("com.capacitorjs:core:[8.0.0,9.0.0)") compileOnly("io.ionic:liveupdates:0.5.5") - compileOnly("io.ionic:live-updates-provider:LOCAL-SNAPSHOT") + compileOnly("io.ionic:live-update-provider:LOCAL-SNAPSHOT") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") implementation("androidx.core:core-ktx:1.15.0") diff --git a/IonicPortals/src/main/kotlin/io/ionic/portals/Portal.kt b/IonicPortals/src/main/kotlin/io/ionic/portals/Portal.kt index d64f87e..e5306b4 100644 --- a/IonicPortals/src/main/kotlin/io/ionic/portals/Portal.kt +++ b/IonicPortals/src/main/kotlin/io/ionic/portals/Portal.kt @@ -6,9 +6,10 @@ import com.getcapacitor.Plugin import io.ionic.liveupdates.LiveUpdate import io.ionic.liveupdates.LiveUpdateManager import io.ionic.liveupdateprovider.LiveUpdateError -import io.ionic.liveupdateprovider.SyncCallback -import io.ionic.liveupdateprovider.SyncResult -import io.ionic.liveupdateprovider.LiveUpdateManager as ProviderLiveUpdateManager +import io.ionic.liveupdateprovider.ProviderSyncCallback +import io.ionic.liveupdateprovider.ProviderSyncResult +import io.ionic.liveupdateprovider.LiveUpdateProviderManager +import io.ionic.liveupdateprovider.ProviderSyncError /** @@ -94,7 +95,7 @@ class Portal(val name: String) { /** * A LiveUpdate manager, if live updates is being used. */ - var liveUpdatesManager: ProviderLiveUpdateManager? = null + var liveUpdateManager: LiveUpdateProviderManager? = null /** * Whether to run a live update sync when the portal is added to the manager. @@ -320,7 +321,7 @@ class PortalBuilder(val name: String) { private var portalFragmentType: Class = PortalFragment::class.java private var onCreate: (portal: Portal) -> Unit = {} private var liveUpdateConfig: LiveUpdate? = null - private var liveUpdatesManager: ProviderLiveUpdateManager? = null + private var liveUpdateManager: LiveUpdateProviderManager? = null private var devMode: Boolean = true internal constructor(name: String, onCreate: (portal: Portal) -> Unit) : this(name) { @@ -571,18 +572,18 @@ class PortalBuilder(val name: String) { if (!updateOnAppLoad) return this // old way if no manager defined - if (this.liveUpdatesManager == null) { + if (this.liveUpdateManager == null) { LiveUpdateManager.sync(context, arrayOf(liveUpdateConfig.appId)) return this } - this.liveUpdatesManager!!.sync( - callback = object : SyncCallback { - override fun onComplete(result: SyncResult) { - Log.d("PortalBuilder", "Live Update sync complete. Did update: ${result.didUpdate}, latest app dir: ${liveUpdatesManager?.latestAppDirectory}") + this.liveUpdateManager!!.sync( + callback = object : ProviderSyncCallback { + override fun onSuccess(result: ProviderSyncResult) { + Log.d("PortalBuilder", "Live Update sync complete. Latest app dir: ${liveUpdateManager?.latestAppDirectory}") } - override fun onError(error: LiveUpdateError.SyncFailed) { + override fun onFailure(error: ProviderSyncError) { Log.e("PortalBuilder", "Live Update sync failed: ${error.message}") } } @@ -610,16 +611,16 @@ class PortalBuilder(val name: String) { * @return the instance of the PortalBuilder with the LiveUpdateManager set */ @JvmOverloads - fun setLiveUpdateManager(context: Context, liveUpdatesManager: ProviderLiveUpdateManager, updateOnAppLoad: Boolean = true): PortalBuilder { - this.liveUpdatesManager = liveUpdatesManager + fun setLiveUpdateManager(context: Context, liveUpdatesManager: LiveUpdateProviderManager, updateOnAppLoad: Boolean = true): PortalBuilder { + this.liveUpdateManager = liveUpdatesManager if (updateOnAppLoad) { - this.liveUpdatesManager?.sync( - callback = object : SyncCallback { - override fun onComplete(result: SyncResult) { - Log.d("TestApplication", "Live Update sync complete. Did update: ${result.didUpdate}, latest app dir: ${liveUpdatesManager.latestAppDirectory}") + this.liveUpdateManager?.sync( + callback = object : ProviderSyncCallback { + override fun onSuccess(result: ProviderSyncResult) { + Log.d("TestApplication", "Live Update sync complete. Latest app dir: ${liveUpdatesManager.latestAppDirectory}") } - override fun onError(error: LiveUpdateError.SyncFailed) { + override fun onFailure(error: ProviderSyncError) { Log.e("TestApplication", "Live Update sync failed: ${error.message}") } } @@ -666,10 +667,9 @@ class PortalBuilder(val name: String) { portal.initialContext = this.initialContext portal.portalFragmentType = this.portalFragmentType portal.liveUpdateConfig = this.liveUpdateConfig - portal.liveUpdatesManager = this.liveUpdatesManager + portal.liveUpdateManager = this.liveUpdateManager portal.devMode = this.devMode onCreate(portal) return portal } - }