Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions IonicPortals/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ dependencies {
implementation(kotlin("reflect"))

api("com.capacitorjs:core:[8.0.0,9.0.0)")
api("io.ionic:liveupdateprovider:0.1.0-alpha.2")
compileOnly("io.ionic:liveupdates:0.5.5")

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
Expand Down
141 changes: 125 additions & 16 deletions IonicPortals/src/main/kotlin/io/ionic/portals/Portal.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package io.ionic.portals

import android.content.Context
import android.util.Log
import com.getcapacitor.Plugin
import io.ionic.liveupdates.LiveUpdate
import io.ionic.liveupdates.LiveUpdateManager
import io.ionic.liveupdateprovider.LiveUpdateProviderError
import io.ionic.liveupdateprovider.LiveUpdateProviderManager
import io.ionic.liveupdateprovider.LiveUpdateProviderSyncCallback
import io.ionic.liveupdateprovider.LiveUpdateProviderSyncResult
import java.io.File

/**
* A class representing a Portal that contains information about the web content to load and any
Expand All @@ -25,6 +31,21 @@ import io.ionic.liveupdates.LiveUpdateManager
* @property name the name of the Portal
*/
class Portal(val name: String) {
/**
* The live update provider for a [Portal].
*/
sealed class LiveUpdateProvider {
/**
* Uses Ionic Live Updates to sync and locate the latest web application assets.
*/
data class Ionic(val liveUpdateConfig: LiveUpdate) : LiveUpdateProvider()

/**
* Uses an external live update provider to sync and locate the latest web application assets.
*/
data class Provider(val liveUpdateManager: LiveUpdateProviderManager) : LiveUpdateProvider()
}

/**
* Capacitor [Plugin] registered with the Portal.
*/
Expand Down Expand Up @@ -73,15 +94,16 @@ class Portal(val name: String) {
var devMode: Boolean = true

/**
* A LiveUpdate config, if live updates is being used.
* The live update source responsible for locating and syncing the latest web application assets.
*
* Use [LiveUpdateProvider.Ionic] for Ionic Live Updates, or [LiveUpdateProvider.Provider] for
* an external provider that conforms to the Live Update Provider SDK.
*/
var liveUpdateConfig: LiveUpdate? = null
var liveUpdateProvider: LiveUpdateProvider? = null
set(value) {
field = value
if (value != null) {
if(value.assetPath == null) {
value.assetPath = this.startDir
}
if (value is LiveUpdateProvider.Ionic && value.liveUpdateConfig.assetPath == null) {
value.liveUpdateConfig.assetPath = this.startDir
}
}

Expand All @@ -90,6 +112,52 @@ class Portal(val name: String) {
*/
var liveUpdateOnAppLoad: Boolean = true

/**
* The directory of the latest synced web application assets for this portal.
* Returns null when no live update source is configured or no sync has completed.
*/
fun latestAppDirectory(context: Context): File? {
return when (val provider = liveUpdateProvider) {
is LiveUpdateProvider.Ionic -> LiveUpdateManager.getLatestAppDirectory(context, provider.liveUpdateConfig.appId)
is LiveUpdateProvider.Provider -> provider.liveUpdateManager.latestAppDirectory
null -> null
}
}

/**
* Sync the external live update provider configured for this Portal.
*
* This only applies to Portals configured with [LiveUpdateProvider.Provider]. Portals configured
* with [LiveUpdateProvider.Ionic] should continue to use Ionic Live Updates APIs, such as
* [LiveUpdateManager.sync].
*
* Example usage (kotlin):
* ```kotlin
* portal.syncProvider(callback)
* ```
*
* Example usage (java):
* ```java
* portal.syncProvider(callback);
* ```
*
* @param callback optional callback invoked with the provider sync result or failure.
*/
@JvmOverloads
fun syncProvider(callback: LiveUpdateProviderSyncCallback? = null) {
when (val provider = liveUpdateProvider) {
is LiveUpdateProvider.Provider -> provider.liveUpdateManager.sync(callback)
is LiveUpdateProvider.Ionic -> callback?.onFailure(
LiveUpdateProviderError.SyncFailed(
"Portal is configured for Ionic Live Updates. Use LiveUpdateManager.sync to sync Ionic Live Updates."
)
)
null -> callback?.onFailure(
LiveUpdateProviderError.SyncFailed("No external live update provider is configured for this Portal.")
)
}
}

/**
* Add a Capacitor [Plugin] to be loaded with this Portal.
*
Expand Down Expand Up @@ -308,7 +376,7 @@ class PortalBuilder(val name: String) {
private var initialContext: Any? = null
private var portalFragmentType: Class<out PortalFragment?> = PortalFragment::class.java
private var onCreate: (portal: Portal) -> Unit = {}
private var liveUpdateConfig: LiveUpdate? = null
private var liveUpdateProvider: Portal.LiveUpdateProvider? = null
private var devMode: Boolean = true

internal constructor(name: String, onCreate: (portal: Portal) -> Unit) : this(name) {
Expand Down Expand Up @@ -526,31 +594,31 @@ class PortalBuilder(val name: String) {
}

/**
* Set the [LiveUpdate] config if using the Live Updates SDK with Portals.
* Set the [LiveUpdate] config if using Ionic Live Updates with Portals.
*
* Example usage (kotlin):
* ```kotlin
* val liveUpdateConfig = LiveUpdate("appId", "production")
* builder = builder.setLiveUpdateConfig(liveUpdateConfig)
* builder = builder.setLiveUpdateConfig(context, liveUpdateConfig)
* ```
*
* Example usage (java):
* ```java
* LiveUpdate liveUpdateConfig = new LiveUpdate("appId", "production");
* builder = builder.setLiveUpdateConfig(liveUpdateConfig);
* builder = builder.setLiveUpdateConfig(context, liveUpdateConfig);
* ```
*
* @param context the Android [Context] used with Live Update configuration
* @param liveUpdateConfig the Live Update config object
* @param updateOnAppLoad if a Live Update sync should occur as soon as the Portal loads
* @return the instance of the PortalBuilder with the Live Update config set
* @param context the Android [Context] used with Ionic Live Updates configuration.
* @param liveUpdateConfig the Ionic Live Updates config object.
* @param updateOnAppLoad whether to start an Ionic Live Updates sync as the Portal is configured.
* @return the instance of the PortalBuilder with the Ionic Live Updates config set.
*/
@JvmOverloads
fun setLiveUpdateConfig(context: Context, liveUpdateConfig: LiveUpdate, updateOnAppLoad: Boolean = true): PortalBuilder {
this.liveUpdateConfig = liveUpdateConfig
if(liveUpdateConfig.assetPath == null) {
liveUpdateConfig.assetPath = this._startDir ?: this.name
}
this.liveUpdateProvider = Portal.LiveUpdateProvider.Ionic(liveUpdateConfig)

LiveUpdateManager.initialize(context)
LiveUpdateManager.cleanVersions(context, liveUpdateConfig.appId)
Expand All @@ -561,6 +629,47 @@ class PortalBuilder(val name: String) {
return this
}

/**
* Set an external live update provider manager to be used with the Portal.
*
* Example usage (kotlin):
* ```kotlin
* builder = builder.setLiveUpdateProviderManager(providerManager)
* ```
*
* Example usage (java):
* ```java
* builder = builder.setLiveUpdateProviderManager(providerManager);
* ```
*
* To trigger a provider sync after the Portal is created, use [Portal.syncProvider].
*
* @param liveUpdateManager the external live update provider manager.
* @param updateOnAppLoad whether to start an external provider sync as the Portal is configured.
* @return the instance of the PortalBuilder with the external live update provider manager set.
*/
@JvmOverloads
fun setLiveUpdateProviderManager(
liveUpdateManager: LiveUpdateProviderManager,
updateOnAppLoad: Boolean = true
): PortalBuilder {
this.liveUpdateProvider = Portal.LiveUpdateProvider.Provider(liveUpdateManager)
if (updateOnAppLoad) {
liveUpdateManager.sync(
callback = object : LiveUpdateProviderSyncCallback {
override fun onSuccess(result: LiveUpdateProviderSyncResult) {
Log.d("PortalBuilder", "Live Update sync complete. Latest app dir: ${liveUpdateManager.latestAppDirectory}")
}

override fun onFailure(error: LiveUpdateProviderError.SyncFailed) {
Log.e("PortalBuilder", "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.
Expand Down Expand Up @@ -597,7 +706,7 @@ class PortalBuilder(val name: String) {
portal.addAssetMaps(assetMaps)
portal.initialContext = this.initialContext
portal.portalFragmentType = this.portalFragmentType
portal.liveUpdateConfig = this.liveUpdateConfig
portal.liveUpdateProvider = this.liveUpdateProvider
portal.devMode = this.devMode
onCreate(portal)
return portal
Expand Down
11 changes: 5 additions & 6 deletions IonicPortals/src/main/kotlin/io/ionic/portals/PortalFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import androidx.annotation.NonNull
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.getcapacitor.*
import io.ionic.liveupdates.LiveUpdateManager
import org.json.JSONException
import org.json.JSONObject
import java.io.File
Expand Down Expand Up @@ -270,8 +269,8 @@ open class PortalFragment : Fragment {
* If Live Updates is used and the web content was updated, the new content will be loaded.
*/
fun reload() {
if(portal?.liveUpdateConfig != null) {
val latestLiveUpdateFiles = LiveUpdateManager.getLatestAppDirectory(requireContext(), portal?.liveUpdateConfig?.appId!!)
if(portal?.liveUpdateProvider != null) {
val latestLiveUpdateFiles = portal?.latestAppDirectory(requireContext())
if (latestLiveUpdateFiles != null) {
if (liveUpdateFiles == null || liveUpdateFiles!!.path != latestLiveUpdateFiles.path) {
liveUpdateFiles = latestLiveUpdateFiles
Expand Down Expand Up @@ -326,8 +325,8 @@ open class PortalFragment : Fragment {
.addPluginInstances(initialPluginInstances)
.addWebViewListeners(webViewListeners)

if (portal?.liveUpdateConfig != null) {
liveUpdateFiles = LiveUpdateManager.getLatestAppDirectory(requireContext(), portal?.liveUpdateConfig?.appId!!)
if (portal?.liveUpdateProvider != null) {
liveUpdateFiles = portal?.latestAppDirectory(requireContext())
bridgeBuilder = if (liveUpdateFiles != null) {
if (config == null) {
val configFile = File(liveUpdateFiles!!.path + "/capacitor.config.json")
Expand Down Expand Up @@ -516,4 +515,4 @@ open class PortalFragment : Fragment {
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ object PortalManager {

/**
* Removes the Portal from the Portal Manager. The Portal will be returned if it was present. If not, null is returned.
* Note: if the Portal uses Live Updates and registered an instance on creation, the Live Update instance for the app
* is not removed.
* Note: if the Portal uses Ionic Live Updates and registered an instance on creation, the Ionic
* Live Updates instance for the app is not removed.
*
* @param name the name of the Portal to remove
*/
Expand Down Expand Up @@ -246,4 +246,4 @@ object PortalManager {
internal fun isRegisteredError(): Boolean {
return registeredError
}
}
}
92 changes: 92 additions & 0 deletions LiveUpdates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Live Updates

Portals can load updated web assets from Ionic Live Updates or from an external live update provider. Each Portal has one live update source, represented by `Portal.liveUpdateProvider`.

## Ionic Live Updates

Use Ionic Live Updates with the existing `setLiveUpdateConfig` builder API.

```kotlin
val liveUpdateConfig = LiveUpdate("appId", "production")

PortalManager.newPortal("help")
.setStartDir("webapp")
.setLiveUpdateConfig(context, liveUpdateConfig)
.create()
```

```java
LiveUpdate liveUpdateConfig = new LiveUpdate("appId", "production");

PortalManager.newPortal("help")
.setStartDir("webapp")
.setLiveUpdateConfig(context, liveUpdateConfig)
.create();
```

`setLiveUpdateConfig` configures the Portal with `Portal.LiveUpdateProvider.Ionic(liveUpdateConfig)`, initializes Ionic Live Updates, registers the live update instance, and optionally starts an Ionic Live Updates sync.

Manual Ionic Live Updates syncs should continue to use the Ionic Live Updates SDK.

```java
LiveUpdateManager.sync(context, new String[] { "appId" });
```

## External Providers

Use an external provider by passing a `LiveUpdateProviderManager`.

```kotlin
val providerManager: LiveUpdateProviderManager = getProviderManager()

val helpPortal = PortalManager.newPortal("help")
.setStartDir("webapp")
.setLiveUpdateProviderManager(providerManager, updateOnAppLoad = false)
.create()

helpPortal.syncProvider(callback)
```

```java
LiveUpdateProviderManager providerManager = getProviderManager();

Portal helpPortal = PortalManager.newPortal("help")
.setStartDir("webapp")
.setLiveUpdateProviderManager(providerManager, false)
.create();

helpPortal.syncProvider(callback);
```

`setLiveUpdateProviderManager` configures the Portal with `Portal.LiveUpdateProvider.Provider(providerManager)`. Android Portals does not use a provider registry; the app creates the provider manager and passes that instance to the Portal.

After a provider sync completes, reload the Portal view or fragment to load the synced assets.

```java
helpPortal.syncProvider(callback);
portalFragment.reload();
```

## Migration Notes

`Portal.liveUpdateConfig` has been replaced by `Portal.liveUpdateProvider`.

Builder-based Ionic Live Updates setup still uses `setLiveUpdateConfig`.

```java
PortalManager.newPortal("help")
.setLiveUpdateConfig(context, liveUpdateConfig)
.create();
```

Direct `Portal` property usage should wrap the config in the Ionic live update provider case.

```kotlin
portal.liveUpdateProvider = Portal.LiveUpdateProvider.Ionic(liveUpdateConfig)
```

External providers should use the provider case.

```kotlin
portal.liveUpdateProvider = Portal.LiveUpdateProvider.Provider(providerManager)
```
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ See our docs to [get started with Portals](https://ionic.io/docs/portals/getting

The Ionic Portals library for Android and iOS requires a license key to use. Once you have integrated Portals into your project, login to your ionic account to get a key. See our doc on [how to register and get your Portals license key](https://ionic.io/docs/portals/how-to/get-a-product-key) and refer to the [Android](https://ionic.io/docs/portals/getting-started/android) or [iOS](https://ionic.io/docs/portals/getting-started/iOS) getting started guides to see where to add your key.

## Live Updates

Portals can load updated web assets from Ionic Live Updates or from an external live update provider. See [LiveUpdates.md](./LiveUpdates.md) for setup examples and migration notes.

## FAQ

### What is the pricing for Portals use?
Expand All @@ -57,4 +61,4 @@ The test projects within the repository will only work with a valid Portals key.
portals_key=YOUR_PORTALS_KEY
```

Note: This file is in the `.gitignore` and is not committed to repos by default.
Note: This file is in the `.gitignore` and is not committed to repos by default.
Loading