Fix: Release builds on android (#1325)

Summary:
After `react-native-flipper` 0.48.0 I cannot build releases on android (I think that passed on CI tests because the example was running an older version of this package, 0.47.0).
So I moved all the `android/src/main` content to `android/src/debug` because we will not use Flipper in another Build Variant

## Changelog

I moved all content from `react-native-flipper/android/src/main` to `react-native-flipper/android/src/debug`.

Probably solves https://github.com/facebook/flipper/issues/1303
Pull Request resolved: https://github.com/facebook/flipper/pull/1325

Test Plan: Maybe create a custom CI script to verify if Flipper deps are present on Release Builds, [something like this](https://github.com/facebook/flipper/issues/1274#issue-641197153)

Reviewed By: cekkaewnumchai

Differential Revision: D22333432

Pulled By: passy

fbshipit-source-id: 4abbab5ecbe08d44752b2138569ff60d25724087
This commit is contained in:
Joao Alves
2020-07-02 04:03:37 -07:00
committed by Facebook GitHub Bot
parent 228d09d572
commit 51e37311d0
8 changed files with 6 additions and 2 deletions

View File

@@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
</manifest>

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.reactnative;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import javax.annotation.Nonnull;
/**
* The FlipperModule is a React Native Native Module. The instance handles incoming calls that
* arrive over the React Native bridge from JavaScript. The instance is created per
* ReactApplicationContext. Which means this module gets reinstated if RN performs a reload. So it
* should not hold any further state on its own. All state is hold by the Plugin and PluginManager
* classes.
*/
@ReactModule(name = FlipperModule.NAME)
public class FlipperModule extends ReactContextBaseJavaModule {
public static final String NAME = "Flipper";
private final FlipperReactNativeJavaScriptPluginManager mManager;
public FlipperModule(
FlipperReactNativeJavaScriptPluginManager manager, ReactApplicationContext reactContext) {
super(reactContext);
mManager = manager;
}
@Override
@Nonnull
public String getName() {
return NAME;
}
@ReactMethod
public void registerPlugin(
final String pluginId, final Boolean inBackground, final Callback statusCallback) {
mManager.registerPlugin(this, pluginId, inBackground, statusCallback);
}
@ReactMethod
public void send(String pluginId, String method, String data) {
mManager.send(pluginId, method, data);
}
@ReactMethod
public void reportErrorWithMetadata(String pluginId, String reason, String stackTrace) {
mManager.reportErrorWithMetadata(pluginId, reason, stackTrace);
}
@ReactMethod
public void reportError(String pluginId, String error) {
mManager.reportError(pluginId, error);
}
@ReactMethod
public void subscribe(String pluginId, String method) {
mManager.subscribe(this, pluginId, method);
}
@ReactMethod
public void respondSuccess(String responderId, String data) {
mManager.respondSuccess(responderId, data);
}
@ReactMethod
public void respondError(String responderId, String data) {
mManager.respondError(responderId, data);
}
void sendJSEvent(String eventName, WritableMap params) {
final ReactApplicationContext context = getReactApplicationContextIfActiveOrWarn();
if (context != null) {
context
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.reactnative;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Collections;
import java.util.List;
/**
* Exposes the react native modules that should be created per ReactApplicationContext. Note that an
* application context lives shorter than the application itself, e.g. reload creates a fresh one.
*/
public class FlipperPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.<NativeModule>singletonList(
new FlipperModule(FlipperReactNativeJavaScriptPluginManager.getInstance(), reactContext));
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.reactnative;
import com.facebook.flipper.core.FlipperConnection;
import com.facebook.flipper.core.FlipperPlugin;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
/**
* This class holds the state of a single plugin that is created from the JS world trough
* `Flipper.addPlugin`. It's main concern is managing the FlipperConnection to the Desktop client.
*
* <p>This class is abstract, as Flipper does not support having multiple instances of the same
* class as plugins, But every JS plugin will store it state in a FlipperPlugin, so for every plugin
* we will create an anonymous subclass.
*
* <p>Note that this class does not directly interact back over the JS bridge to React Native, as
* the JavaPlugin has a longer lifecycle than it's JS counter part, which will be recreated on
* reload. However, if the native module reload, we keep these instances to not loose the connextion
* to the Flipper Desktop client.
*/
public abstract class FlipperReactNativeJavaScriptPlugin implements FlipperPlugin {
private final String mPluginId;
private final boolean mInBackground;
private FlipperConnection mConnection;
private FlipperModule mModule;
FlipperReactNativeJavaScriptPlugin(FlipperModule module, String pluginId, boolean inBackground) {
mPluginId = pluginId;
mModule = module;
mInBackground = inBackground;
}
@Override
public String getId() {
return mPluginId;
}
@Override
public void onConnect(FlipperConnection connection) {
mConnection = connection;
fireOnConnect();
}
void fireOnConnect() {
if (!isConnected()) {
throw new RuntimeException("Plugin not connected " + mPluginId);
}
mModule.sendJSEvent("react-native-flipper-plugin-connect", getPluginParams());
}
@Override
public void onDisconnect() {
mModule.sendJSEvent("react-native-flipper-plugin-disconnect", getPluginParams());
mConnection = null;
}
@Override
public boolean runInBackground() {
return mInBackground;
}
boolean isConnected() {
return mConnection != null;
}
FlipperConnection getConnection() {
return mConnection;
}
void setModule(FlipperModule module) {
mModule = module;
}
private WritableMap getPluginParams() {
final WritableMap params = Arguments.createMap();
params.putString("plugin", mPluginId);
return params;
}
}

View File

@@ -0,0 +1,168 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.reactnative;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.core.FlipperArray;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.flipper.core.FlipperObject;
import com.facebook.flipper.core.FlipperPlugin;
import com.facebook.flipper.core.FlipperResponder;
import com.facebook.react.bridge.Callback;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
/**
* This class manages all loaded FlipperPlugins. It is a singleton to make sure plugin states are
* preserved even when the native modules get reloaded (e.g. due to a reload in RN). This avoids
* loosing our connections with Flipper.
*
* <p>Note that this manager is not bound to a specific FlipperModule instance, as that might be
* swapped in and out over time.
*/
public final class FlipperReactNativeJavaScriptPluginManager {
private static FlipperReactNativeJavaScriptPluginManager sInstance;
public static synchronized FlipperReactNativeJavaScriptPluginManager getInstance() {
if (sInstance == null) {
sInstance = new FlipperReactNativeJavaScriptPluginManager();
}
return sInstance;
}
private final FlipperClient mFlipperClient;
// uniqueResponderId -> ResponderObject
private final Map<String, FlipperResponder> mResponders = new ConcurrentHashMap<>();
// generated the next responder id
private final AtomicLong mResponderId = new AtomicLong();
private FlipperReactNativeJavaScriptPluginManager() {
mFlipperClient = AndroidFlipperClient.getInstanceIfInitialized();
}
public void registerPlugin(
FlipperModule module,
final String pluginId,
final Boolean inBackground,
final Callback statusCallback) {
if (mFlipperClient == null) {
// Flipper is not available in this build
statusCallback.invoke("noflipper");
return;
}
final FlipperReactNativeJavaScriptPlugin existing = getPlugin(pluginId);
if (existing != null) {
// Make sure events are emitted on the right application context
existing.setModule(module);
// this happens if the plugin hot reloaded on JS side, but we had it here already
if (existing.isConnected()) {
existing.fireOnConnect();
}
statusCallback.invoke("ok");
return;
}
// we always create a new plugin class on the fly,
// as Flipper only allows one plugin per type to be registered!
final FlipperPlugin plugin =
new FlipperReactNativeJavaScriptPlugin(module, pluginId, inBackground) {
// inner class with no new members
};
mFlipperClient.addPlugin(plugin);
statusCallback.invoke("ok");
}
void send(String pluginId, String method, String data) {
final FlipperReactNativeJavaScriptPlugin plugin = getPlugin(pluginId);
if (data == null) {
plugin.getConnection().send(method, (FlipperObject) null);
return;
}
// Optimization: throwing raw strings around to the desktop would probably avoid some double
// parsing...
final Object parsedData = parseJSON(data);
if (parsedData instanceof FlipperArray) {
plugin.getConnection().send(method, (FlipperArray) parsedData);
} else {
plugin.getConnection().send(method, (FlipperObject) parsedData);
}
}
void reportErrorWithMetadata(String pluginId, String reason, String stackTrace) {
getPlugin(pluginId).getConnection().reportErrorWithMetadata(reason, stackTrace);
}
void reportError(String pluginId, String error) {
getPlugin(pluginId).getConnection().reportError(new Error(error));
}
void subscribe(FlipperModule module, String pluginId, String method) {
final FlipperReactNativeJavaScriptReceiver receiver =
new FlipperReactNativeJavaScriptReceiver(this, module, pluginId, method);
// Fresh connection should be the case for a new subscribe...
getPlugin(pluginId).getConnection().receive(method, receiver);
}
void respondSuccess(String responderId, String data) {
final FlipperResponder responder = mResponders.remove(responderId);
if (data == null) {
responder.success();
} else {
final Object parsedData = parseJSON(data);
if (parsedData instanceof FlipperArray) {
responder.success((FlipperArray) parsedData);
} else {
responder.success((FlipperObject) parsedData);
}
}
}
void respondError(String responderId, String data) {
final FlipperResponder responder = mResponders.remove(responderId);
final Object parsedData = parseJSON(data);
if (parsedData instanceof FlipperArray) {
responder.success((FlipperArray) parsedData);
} else {
responder.success((FlipperObject) parsedData);
}
}
private FlipperReactNativeJavaScriptPlugin getPlugin(String pluginId) {
return mFlipperClient.getPlugin(pluginId);
}
String createResponderId(FlipperResponder responder) {
final String id = String.valueOf(mResponderId.incrementAndGet());
mResponders.put(id, responder);
return id;
}
private static Object /* FlipperArray | FlipperObject */ parseJSON(String json) {
if (json == null) {
return null;
}
// returns either a FlipperObject or Flipper array, pending the data
try {
final JSONTokener tokener = new JSONTokener(json);
final char firstChar = tokener.nextClean();
tokener.back();
if (firstChar == '[') {
return new FlipperArray(new JSONArray(tokener));
} else {
return new FlipperObject(new JSONObject(tokener));
}
} catch (final JSONException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.reactnative;
import com.facebook.flipper.core.FlipperObject;
import com.facebook.flipper.core.FlipperReceiver;
import com.facebook.flipper.core.FlipperResponder;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
public class FlipperReactNativeJavaScriptReceiver implements FlipperReceiver {
String plugin;
String method;
FlipperReactNativeJavaScriptPluginManager manager;
FlipperModule module;
public FlipperReactNativeJavaScriptReceiver(
FlipperReactNativeJavaScriptPluginManager manager,
FlipperModule module,
String plugin,
String method) {
this.plugin = plugin;
this.method = method;
this.manager = manager;
this.module = module;
}
@Override
public void onReceive(FlipperObject params, FlipperResponder responder) {
final WritableMap eventData = Arguments.createMap();
eventData.putString("plugin", plugin);
eventData.putString("method", method);
eventData.putString("params", params.toJsonString());
if (responder != null) {
final String responderId = manager.createResponderId(responder);
eventData.putString("responderId", responderId);
}
module.sendJSEvent("react-native-flipper-receive-event", eventData);
}
}