Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ionic-team/ionic-portals-react-native
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 0.1.0
Choose a base ref
...
head repository: ionic-team/ionic-portals-react-native
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 0.2.0
Choose a head ref
  • 5 commits
  • 29 files changed
  • 2 contributors

Commits on Sep 6, 2022

  1. chore: Update readme to be in line with the other portals libraries (#29

    )
    
    * chore: Update readme to be in line with the other portals libraries
    Steven0351 authored Sep 6, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    baa5c39 View commit details

Commits on Oct 11, 2022

  1. feat: Secure live updates (#31)

    feat(ios): Overhaul iOS implementation to support secure live updates and configuration based Portals.
    
    feat(web): Make all exported functions awaitable.
    BREAKING: Makes previously non-async methods async. The module methods that were not async were essentially doing fire and forget making it impossible to synchronize on the user end. This was not much of an issue until adding secure live updates where the order in which those effects were executed were not in any guaranteed order. This technically isn't a source breaking change, but it is behavior and API changing.
    
    feat(android): Add secure live updates to Android
    Steven0351 authored Oct 11, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    2b0e5b5 View commit details

Commits on Oct 13, 2022

  1. feat: Make getPortal accessible via the JS interface. (#32)

    Fixes discrepancies between the two platforms with regards to how they are called from JS.
    Steven0351 authored Oct 13, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    453cf2c View commit details
  2. 1

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    422af87 View commit details

Commits on Oct 18, 2022

  1. Release 0.2.0

    Co-authored-by: Steven0351 <[email protected]>
    Steven0351 and Steven0351 authored Oct 18, 2022
    1

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    3daa0e9 View commit details
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -69,3 +69,5 @@ lib/

# ignore asset folders generated during build time
example/android/app/src/main/assets
.vercel
docs/
5 changes: 5 additions & 0 deletions .vercelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
docs/
.github/
example/
node_modules/
*.tgz
170 changes: 11 additions & 159 deletions README.md
Original file line number Diff line number Diff line change
@@ -3,13 +3,17 @@
<img src="https://user-images.githubusercontent.com/5769389/134952353-7d7b4145-3a80-4946-9b08-17b3a22c03a1.png" width="560" />
</div>
<div align="center">
⚡️ A supercharged native Web View for iOS and Android ⚡️
⚡️ A supercharged native Web View for React Native ⚡️
</div>
<br />
<p align="center">
<a href="https://github.com/ionic-team/react-native-ionic-portals/actions?query=workflow%3ACI"><img src="https://img.shields.io/github/workflow/status/ionic-team/ionic-portals/CI?style=flat-square" /></a>
<img src="https://img.shields.io/badge/platform-iOS%2013%2B-lightgrey?style=flat-square" alt="Supports iOS 13 and up" />
<img src="https://img.shields.io/badge/platform-Android%20SDK%2021%2B-brightgreen?style=flat-square" alt="Supports Android SDK 21 and up" />
<img src="https://img.shields.io/badge/platform-React%20Native%200.63.3%2B-blue?style=flat-square" alt="Supports React Native 0.63.3 and up" />
</p>
<p align="center">
<a href="https://github.com/ionic-team/ionic-portals/actions?query=workflow%3ACI"><img src="https://img.shields.io/github/workflow/status/ionic-team/react-native-ionic-portals/Verify?style=flat-square" /></a>
<a href="https://www.npmjs.com/package/@ionic/portals-react-native"><img src="https://img.shields.io/npm/l/@ionic/portals-react-native?style=flat-square" /></a>
<a href="https://www.npmjs.com/package/@ionic/portals-react-native"><img src="https://img.shields.io/npm/v/@ionic/portals-react-native?style=flat-square" /></a>
</p>
<p align="center">
<a href="https://ionic.io/docs/portals"><img src="https://img.shields.io/static/v1?label=docs&message=ionic.io/portals&color=blue&style=flat-square" /></a>
@@ -18,166 +22,15 @@

---

Ionic Portals is a supercharged native Web View component for iOS and Android that lets you add web-based experiences to React Native mobile apps. It enables React Native and web teams to better collaborate and bring new and existing web experiences to mobile in a safe, controlled way.
Ionic Portals is a supercharged native Web View component for React Native that lets you add web-based experiences to native mobile apps. It enables native and web teams to better collaborate and bring new and existing web experiences to mobile in a safe, controlled way.

## Getting Started

### Installation
`npm install @ionic/portals-react-native`
or
`yarn add @ionic/portals-react-native`

### Usage
Register Portals with your [product key](#Registration):
```javascript
import { register } from '@ionic/portals-react-native';

register('YOUR_PORTAL_KEY_HERE');
```

Create a Portal and add it to the portal registry:
```javascript
import { addPortal } from '@ionic/portals-react-native';
const helloPortal = {
// A unique name to reference later
name: 'hello',
// This is the location of your web bundle relative to the asset directory in Android and Bundle.main in iOS
// This will default to the name of the portal
startDir: 'portals/hello',
// Any initial state to be provided to a Portal if needed
initialContext: {
greeting: 'Hello, world!'
}
};

addPortal(helloPortal);
```

Create a PortalView in your view hierarchy:
```javascript
import { PortalView } from '@ionic/portals-react-native';

<PortalView
// The name of the portal to be used in the view
name='hello'

// Set any initial context you may want to override.
initialContext={{ greeting: 'Goodbye!' }}

// Setting a size is required
style={{ flex: 1, height: 300 }}
/>
```

#### iOS Specific Configuration
##### AppDelegate
Both Capacitor and React Native have classes named `AppDelegate`. To prevent a clash that can prevent your React Native application from launching,
you will need to rename `AppDelegate` to something else:
```objective-c
// AppDelegate.h
@interface RNAppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>
```
```objective-c
// AppDelegate.m
@implementation RNAppDelegate
@end
```

```objective-c
// main.m
#import <UIKit/UIKit.h>

#import "AppDelegate.h"

int main(int argc, char *argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([RNAppDelegate class]));
}
}
```
##### Podfile
There are two methods you may use to ensure Portals can integrate into your React Native application: a custom `pre_install` hook or adding `use_frameworks!` to your Podfile. Only one of these approaches is needed to ensure that Capacitor is compiled as a dynamic framework.
**pre_install**
Using the `pre_install` hook allows you to keep all the other React Native dependencies as static frameworks:
```ruby
# These frameworks are required to be dynamic.
dynamic_frameworks = ['Capacitor', 'CapacitorCordova']
pre_install do |installer|
installer.pod_targets.each do |pod|
if dynamic_frameworks.include?(pod.name)
def pod.static_framework?
false
end
def pod.build_type
Pod::BuildType.dynamic_framework
end
end
end
end
```

**use_frameworks**

Alternative to the `pre_install` hook, you can add `use_frameworks!` to your Podfile application target. Using this approach requires removing `use_flipper!()` from the Podfile.

### Communicating between React Native and Web
One of the key features of Ionic Portals for React Native is facilitating communication between the web and React Native layers of your application.
Publishing a message to the web:
```javascript
import { publish } from '@ionic/portals-react-native';

publish('topic', { number: 1 })
```

Subscribe to messages from the web:
```javascript
import { subscribe } from '@ionic/portals-react-native';

let subscriptionReference = await subscribe('topic', message => {
// Here you have access to:
// message.data - Any data sent from the web
// message.subscriptionRef - The subscription reference used to manage the lifecycle of the subscription
// message.topic - The topic the message was published on
})
```

When you no longer need to receive events, unsubscribe:
```javascript
import { unsubscribe } from '@ionic/portals-react-native';

unsubscribe('channel:topic', subscriptionReference)
```

To see an example of Portals Pub/Sub in action that manages the lifecycle of a subscription with the lifecycle of a React Native component, refer to the [`PubSubLabel`](https://github.com/ionic-team/react-native-ionic-portals/blob/af19df0d66059d85ab8dde493504368c3bf39127/example/App.tsx#L53) implementation in the example project.

### Using Capacitor Plugins
If you need to use any Capacitor plugins, the classpath of the Android plugins will have to be provided to the `Portal` `androidPlugins` property.

```javascript
const helloPortal = {
name: 'hello',
startDir: 'portals/hello',
androidPlugins: ['com.capacitorjs.plugins.camera.CameraPlugin'],
initialContext: {
greeting: 'Hello, world!'
}
};
```

No configuration for iOS is needed since plugins are automatically registered when the Capacitor bridge initializes on iOS.

### Bundling Your Web Apps
Currently there is no tooling for bundling your web apps directly as part of @ionic/portals-react-native. Please follow the [native guides](https://ionic.io/docs/portals/how-to/pull-in-web-bundle#setup-the-web-asset-directory) to manage this as part of the native build process.
See our docs to [get started with Portals](https://ionic.io/docs/portals/getting-started/guide).

## Registration

Ionic Portals for React Native requires a 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 for free and get your Portals license key](https://ionic.io/docs/portals/how-to/get-a-product-key) and refer to the [usage](#Usage) section on how to add your key to your React Native application.
The Ionic Portals library for React Native requires a **free** 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 for free and get your Portals license key](https://ionic.io/docs/portals/how-to/get-a-product-key) and refer to the [React Native](https://ionic.io/docs/portals/getting-started/react-native) getting started guides to see where to add your key.

## FAQ

@@ -191,7 +44,6 @@ See our [license](https://github.com/ionic-team/ionic-portals/blob/main/LICENSE.

### How is Portals Related to Capacitor and Ionic?

Ionic Portals is a solution that lets you add web-based experiences to your native mobile apps. Portals uses [Capacitor](https://capacitorjs.com) as a bridge between the native code and the web code to allow for cross-communication between the two layers. Because Portals uses Capacitor under the hood, you are able to use any existing [Capacitor Plugins](https://capacitorjs.com/docs/plugins) and even most [Cordova Plugins](https://capacitorjs.com/docs/plugins/cordova) while continuing to use your existing native workflow. Portals for React Native brings these capabilities to React Native applications.
Ionic Portals is a solution that lets you add web-based experiences to your native mobile apps. Portals uses [Capacitor](https://capacitorjs.com) as a bridge between the native code and the web code to allow for cross-communication between the two layers. Because Portals uses Capacitor under the hood, you are able to use any existing [Capacitor Plugins](https://capacitorjs.com/docs/plugins) while continuing to use your existing native workflow.

[Ionic Framework](https://ionicframework.com/) is the open-source mobile app development framework that makes it easy to build top quality native and progressive web apps with web technologies. Your web experiences can be developed with Ionic, but it is not necessary to use Portals.

3 changes: 2 additions & 1 deletion ReactNativePortals.podspec
Original file line number Diff line number Diff line change
@@ -16,5 +16,6 @@ Pod::Spec.new do |s|
s.source_files = "ios/**/*.{h,m,mm,swift}"

s.dependency "React-Core"
s.dependency "IonicPortals", "~> 0.6.3"
s.dependency "IonicPortals", "~> 0.6.4"
s.dependency "IonicLiveUpdates", "~> 0.2.0"
end
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
@@ -126,7 +126,7 @@ dependencies {
//noinspection GradleDynamicVersion
api "io.ionic:portals:0.6.+"
//noinspection GradleDynamicVersion
api "io.ionic:liveupdates:0.0.+"
api "io.ionic:liveupdates:0.1.+"

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0"
}
132 changes: 132 additions & 0 deletions android/src/main/java/io/ionic/portals/reactnative/PortalView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package io.ionic.portals.reactnative

import android.util.Log
import android.view.Choreographer
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.fragment.app.FragmentActivity
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewGroupManager
import com.facebook.react.uimanager.annotations.ReactProp
import com.getcapacitor.CapConfig
import io.ionic.portals.Portal
import io.ionic.portals.PortalFragment
import io.ionic.portals.PortalManager

private data class PortalViewState(
var fragment: PortalFragment?,
var portal: Portal?,
var initialContext: HashMap<String, Any>?
)

internal class PortalViewManager(private val context: ReactApplicationContext) :
ViewGroupManager<FrameLayout>() {
private val createId = 1
private val fragmentMap = mutableMapOf<Int, PortalViewState>()

@ReactProp(name = "portal")
fun setPortal(viewGroup: ViewGroup, portal: ReadableMap) {
val name = portal.getString("name") ?: return
when (val viewState = fragmentMap[viewGroup.id]) {
null -> fragmentMap[viewGroup.id] = PortalViewState(
fragment = null,
portal = PortalManager.getPortal(name),
initialContext = portal.getMap("initialContext")?.toHashMap()
)
}
}

override fun getName() = "AndroidPortalView"

override fun createViewInstance(reactContext: ThemedReactContext): FrameLayout {
return FrameLayout(reactContext)
}

override fun getCommandsMap(): MutableMap<String, Int> {
return mutableMapOf("create" to createId)
}

override fun receiveCommand(root: FrameLayout, commandId: String?, args: ReadableArray?) {
super.receiveCommand(root, commandId, args)
val viewId = args?.getInt(0) ?: return

@Suppress("NAME_SHADOWING")
val commandId = commandId?.toIntOrNull() ?: return

when (commandId) {
createId -> createFragment(root, viewId)
}
}

private fun createFragment(root: FrameLayout, viewId: Int) {
val viewState = fragmentMap[viewId] ?: return
val portal = viewState.portal ?: return

val parentView = root.findViewById<ViewGroup>(viewId)
setupLayout(parentView)

val portalFragment = PortalFragment(portal)

val configBuilder = CapConfig.Builder(context)
.setInitialFocus(false)

RNPortalManager.indexMap[portal.name]
?.let(configBuilder::setStartPath)

portalFragment.setConfig(configBuilder.create())

viewState.initialContext?.let(portalFragment::setInitialContext)

viewState.fragment = portalFragment

val fragmentActivity = context.currentActivity as? FragmentActivity ?: return
fragmentActivity.supportFragmentManager
.beginTransaction()
.replace(viewId, portalFragment, "$viewId")
.commit()
}

override fun onDropViewInstance(view: FrameLayout) {
super.onDropViewInstance(view)
val viewState = fragmentMap[view.id] ?: return

try {
viewState.fragment
?.parentFragmentManager
?.beginTransaction()
?.remove(viewState.fragment!!)
?.commit()
} catch (e: IllegalStateException) {
Log.i("io.ionic.portals.rn", "Parent fragment manager not available")
}

fragmentMap.remove(view.id)
}

private fun setupLayout(view: ViewGroup) {
Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback {
override fun doFrame(frameTimeNanos: Long) {
layoutPortal(view)
view.viewTreeObserver.dispatchOnGlobalLayout()
Choreographer.getInstance().postFrameCallback(this)
}
})
}

private fun layoutPortal(view: ViewGroup) {
for (i in 0 until view.childCount) {
val child = view.getChildAt(i)

child.measure(
View.MeasureSpec.makeMeasureSpec(view.measuredWidth, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(view.measuredHeight, View.MeasureSpec.EXACTLY)
)

child.layout(0, 0, child.measuredWidth, child.measuredHeight)
}
}
}
Loading