feat(android) make application more akin to a greenfield RN app
Few (but some) RN modules rely on the main Application implementing ReactApplication. For Detox to actually work on Android we need the app to be a greenfield app. Specifically, the main Application must implement ReactApplication. In order to satisfy this requirement we are introducing a helper ReactNativeHost implementation which encapsulates our RN integration, which is what the ReactApplication needs to expose. While we don't really need this ourselves (except if we end up adopting Detox) we have known people who fork our app and add dependencies that have this requirement to it, so this change will help them too.
This commit is contained in:
parent
6aa0e3902a
commit
e61ccc956f
|
@ -7,6 +7,7 @@
|
||||||
android:extractNativeLibs="true"
|
android:extractNativeLibs="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:name=".MainApplication"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
<meta-data
|
<meta-data
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright @ 2022-present 8x8, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jitsi.meet;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.facebook.react.ReactApplication;
|
||||||
|
import com.facebook.react.ReactNativeHost;
|
||||||
|
|
||||||
|
import org.jitsi.meet.sdk.JitsiReactNativeHost;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application class for Jitsi Meet. The only reason why this exists is for Detox
|
||||||
|
* to believe our app is a "greenfield" app. SDK users need not use this.
|
||||||
|
*/
|
||||||
|
public class MainApplication extends Application implements ReactApplication {
|
||||||
|
private final ReactNativeHost mReactNativeHost = new JitsiReactNativeHost(this);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReactNativeHost getReactNativeHost() {
|
||||||
|
return mReactNativeHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
|
||||||
|
// Initialize RN
|
||||||
|
Log.d(this.getClass().getCanonicalName(), "app onCreate");
|
||||||
|
getReactNativeHost().getReactInstanceManager();
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
package org.jitsi.meet.sdk;
|
package org.jitsi.meet.sdk;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.startup.Initializer;
|
import androidx.startup.Initializer;
|
||||||
|
@ -30,6 +31,8 @@ public class JitsiInitializer implements Initializer<Boolean> {
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Boolean create(@NonNull Context context) {
|
public Boolean create(@NonNull Context context) {
|
||||||
|
Log.d(this.getClass().getCanonicalName(), "create");
|
||||||
|
|
||||||
SoLoader.init(context, /* native exopackage */ false);
|
SoLoader.init(context, /* native exopackage */ false);
|
||||||
|
|
||||||
// Register our uncaught exception handler.
|
// Register our uncaught exception handler.
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
package org.jitsi.meet.sdk;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import com.facebook.react.ReactInstanceManager;
|
||||||
|
import com.facebook.react.ReactNativeHost;
|
||||||
|
import com.facebook.react.ReactPackage;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the minimal implementation of ReactNativeHost that will make things like the
|
||||||
|
* Detox testing framework believe we are a "greenfield" app.
|
||||||
|
*
|
||||||
|
* Generally speaking, apps using the SDK (other than the Jitsi Meet app itself) should not
|
||||||
|
* need to use this because the
|
||||||
|
*/
|
||||||
|
public class JitsiReactNativeHost extends ReactNativeHost {
|
||||||
|
public JitsiReactNativeHost(Application application) {
|
||||||
|
super(application);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getUseDeveloperSupport() {
|
||||||
|
// Unused since we override `createReactInstanceManager`.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<ReactPackage> getPackages() {
|
||||||
|
// Unused since we override `createReactInstanceManager`.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ReactInstanceManager createReactInstanceManager() {
|
||||||
|
ReactInstanceManagerHolder.initReactInstanceManager(this.getApplication());
|
||||||
|
|
||||||
|
return ReactInstanceManagerHolder.getReactInstanceManager();
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,8 @@
|
||||||
package org.jitsi.meet.sdk;
|
package org.jitsi.meet.sdk;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
@ -99,6 +101,69 @@ class ReactInstanceManagerHolder {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<ReactPackage> getReactNativePackages() {
|
||||||
|
List<ReactPackage> packages
|
||||||
|
= new ArrayList<>(Arrays.asList(
|
||||||
|
new com.reactnativecommunity.asyncstorage.AsyncStoragePackage(),
|
||||||
|
new com.ocetnik.timer.BackgroundTimerPackage(),
|
||||||
|
new com.calendarevents.RNCalendarEventsPackage(),
|
||||||
|
new com.corbt.keepawake.KCKeepAwakePackage(),
|
||||||
|
new com.facebook.react.shell.MainReactPackage(),
|
||||||
|
new com.reactnativecommunity.clipboard.ClipboardPackage(),
|
||||||
|
new com.reactnativecommunity.netinfo.NetInfoPackage(),
|
||||||
|
new com.reactnativepagerview.PagerViewPackage(),
|
||||||
|
new com.oblador.performance.PerformancePackage(),
|
||||||
|
new com.reactnativecommunity.slider.ReactSliderPackage(),
|
||||||
|
new com.brentvatne.react.ReactVideoPackage(),
|
||||||
|
new com.swmansion.reanimated.ReanimatedPackage(),
|
||||||
|
new org.reactnative.maskedview.RNCMaskedViewPackage(),
|
||||||
|
new com.reactnativecommunity.webview.RNCWebViewPackage(),
|
||||||
|
new com.kevinresol.react_native_default_preference.RNDefaultPreferencePackage(),
|
||||||
|
new com.learnium.RNDeviceInfo.RNDeviceInfo(),
|
||||||
|
new com.swmansion.gesturehandler.react.RNGestureHandlerPackage(),
|
||||||
|
new org.linusu.RNGetRandomValuesPackage(),
|
||||||
|
new com.rnimmersive.RNImmersivePackage(),
|
||||||
|
new com.swmansion.rnscreens.RNScreensPackage(),
|
||||||
|
new com.zmxv.RNSound.RNSoundPackage(),
|
||||||
|
new com.th3rdwave.safeareacontext.SafeAreaContextPackage(),
|
||||||
|
new com.horcrux.svg.SvgPackage(),
|
||||||
|
new ReactPackageAdapter() {
|
||||||
|
@Override
|
||||||
|
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||||
|
return ReactInstanceManagerHolder.createNativeModules(reactContext);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||||
|
return ReactInstanceManagerHolder.createViewManagers(reactContext);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// AmplitudeReactNativePackage
|
||||||
|
try {
|
||||||
|
Class<?> amplitudePackageClass = Class.forName("com.amplitude.reactnative.AmplitudeReactNativePackage");
|
||||||
|
Constructor constructor = amplitudePackageClass.getConstructor();
|
||||||
|
packages.add((ReactPackage)constructor.newInstance());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
|
||||||
|
}
|
||||||
|
|
||||||
|
// RNGoogleSignInPackage
|
||||||
|
try {
|
||||||
|
Class<?> googlePackageClass = Class.forName("com.reactnativegooglesignin.RNGoogleSigninPackage");
|
||||||
|
Constructor constructor = googlePackageClass.getConstructor();
|
||||||
|
packages.add((ReactPackage)constructor.newInstance());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
|
||||||
|
}
|
||||||
|
|
||||||
|
return packages;
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSCExecutorFactory getReactNativeJSFactory() {
|
||||||
|
// Keep on using JSC, the jury is out on Hermes.
|
||||||
|
return new JSCExecutorFactory("", "");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to send an event to JavaScript.
|
* Helper function to send an event to JavaScript.
|
||||||
*
|
*
|
||||||
|
@ -159,6 +224,35 @@ class ReactInstanceManagerHolder {
|
||||||
return reactInstanceManager;
|
return reactInstanceManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal method to initialize the React Native instance manager. We
|
||||||
|
* create a single instance in order to load the JavaScript bundle a single
|
||||||
|
* time. All {@code ReactRootView} instances will be tied to the one and
|
||||||
|
* only {@code ReactInstanceManager}.
|
||||||
|
*
|
||||||
|
* This method is only meant to be called when integrating with {@code JitsiReactNativeHost}.
|
||||||
|
*
|
||||||
|
* @param app {@code Application} current running Application.
|
||||||
|
*/
|
||||||
|
static void initReactInstanceManager(Application app) {
|
||||||
|
if (reactInstanceManager != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(ReactInstanceManagerHolder.class.getCanonicalName(), "initializing RN with Application");
|
||||||
|
|
||||||
|
reactInstanceManager
|
||||||
|
= ReactInstanceManager.builder()
|
||||||
|
.setApplication(app)
|
||||||
|
.setBundleAssetName("index.android.bundle")
|
||||||
|
.setJSMainModulePath("index.android")
|
||||||
|
.setJavaScriptExecutorFactory(getReactNativeJSFactory())
|
||||||
|
.addPackages(getReactNativePackages())
|
||||||
|
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
||||||
|
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal method to initialize the React Native instance manager. We
|
* Internal method to initialize the React Native instance manager. We
|
||||||
* create a single instance in order to load the JavaScript bundle a single
|
* create a single instance in order to load the JavaScript bundle a single
|
||||||
|
@ -172,63 +266,7 @@ class ReactInstanceManagerHolder {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ReactPackage> packages
|
Log.d(ReactInstanceManagerHolder.class.getCanonicalName(), "initializing RN with Activity");
|
||||||
= new ArrayList<>(Arrays.asList(
|
|
||||||
new com.reactnativecommunity.asyncstorage.AsyncStoragePackage(),
|
|
||||||
new com.ocetnik.timer.BackgroundTimerPackage(),
|
|
||||||
new com.calendarevents.RNCalendarEventsPackage(),
|
|
||||||
new com.corbt.keepawake.KCKeepAwakePackage(),
|
|
||||||
new com.facebook.react.shell.MainReactPackage(),
|
|
||||||
new com.reactnativecommunity.clipboard.ClipboardPackage(),
|
|
||||||
new com.reactnativecommunity.netinfo.NetInfoPackage(),
|
|
||||||
new com.reactnativepagerview.PagerViewPackage(),
|
|
||||||
new com.oblador.performance.PerformancePackage(),
|
|
||||||
new com.reactnativecommunity.slider.ReactSliderPackage(),
|
|
||||||
new com.brentvatne.react.ReactVideoPackage(),
|
|
||||||
new com.swmansion.reanimated.ReanimatedPackage(),
|
|
||||||
new org.reactnative.maskedview.RNCMaskedViewPackage(),
|
|
||||||
new com.reactnativecommunity.webview.RNCWebViewPackage(),
|
|
||||||
new com.kevinresol.react_native_default_preference.RNDefaultPreferencePackage(),
|
|
||||||
new com.learnium.RNDeviceInfo.RNDeviceInfo(),
|
|
||||||
new com.swmansion.gesturehandler.react.RNGestureHandlerPackage(),
|
|
||||||
new org.linusu.RNGetRandomValuesPackage(),
|
|
||||||
new com.rnimmersive.RNImmersivePackage(),
|
|
||||||
new com.swmansion.rnscreens.RNScreensPackage(),
|
|
||||||
new com.zmxv.RNSound.RNSoundPackage(),
|
|
||||||
new com.th3rdwave.safeareacontext.SafeAreaContextPackage(),
|
|
||||||
new com.horcrux.svg.SvgPackage(),
|
|
||||||
new ReactPackageAdapter() {
|
|
||||||
@Override
|
|
||||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
|
||||||
return ReactInstanceManagerHolder.createNativeModules(reactContext);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
|
||||||
return ReactInstanceManagerHolder.createViewManagers(reactContext);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
// AmplitudeReactNativePackage
|
|
||||||
try {
|
|
||||||
Class<?> amplitudePackageClass = Class.forName("com.amplitude.reactnative.AmplitudeReactNativePackage");
|
|
||||||
Constructor constructor = amplitudePackageClass.getConstructor();
|
|
||||||
packages.add((ReactPackage)constructor.newInstance());
|
|
||||||
} catch (Exception e) {
|
|
||||||
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
|
|
||||||
}
|
|
||||||
|
|
||||||
// RNGoogleSigninPackage
|
|
||||||
try {
|
|
||||||
Class<?> googlePackageClass = Class.forName("com.reactnativegooglesignin.RNGoogleSigninPackage");
|
|
||||||
Constructor constructor = googlePackageClass.getConstructor();
|
|
||||||
packages.add((ReactPackage)constructor.newInstance());
|
|
||||||
} catch (Exception e) {
|
|
||||||
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep on using JSC, the jury is out on Hermes.
|
|
||||||
JSCExecutorFactory jsFactory
|
|
||||||
= new JSCExecutorFactory("", "");
|
|
||||||
|
|
||||||
reactInstanceManager
|
reactInstanceManager
|
||||||
= ReactInstanceManager.builder()
|
= ReactInstanceManager.builder()
|
||||||
|
@ -236,8 +274,8 @@ class ReactInstanceManagerHolder {
|
||||||
.setCurrentActivity(activity)
|
.setCurrentActivity(activity)
|
||||||
.setBundleAssetName("index.android.bundle")
|
.setBundleAssetName("index.android.bundle")
|
||||||
.setJSMainModulePath("index.android")
|
.setJSMainModulePath("index.android")
|
||||||
.setJavaScriptExecutorFactory(jsFactory)
|
.setJavaScriptExecutorFactory(getReactNativeJSFactory())
|
||||||
.addPackages(packages)
|
.addPackages(getReactNativePackages())
|
||||||
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
||||||
.setInitialLifecycleState(LifecycleState.RESUMED)
|
.setInitialLifecycleState(LifecycleState.RESUMED)
|
||||||
.build();
|
.build();
|
||||||
|
|
Loading…
Reference in New Issue