Skip to content

Intent (data sharing)

Jacques Bonet edited this page Dec 23, 2019 · 13 revisions

Introduction

React-native provide mechanism that permits to share a document into the desired application (ent).

The application could be in two states:

  • close
  • in background

In Android, we can achieve this with the help of Intent Filters.

Intent Filter

Intent filters are basically filters that define what an app can expect as its input.

Intent filter are specified in androidmanifest.xml file.

Here an extract for ent containing the intent-filter sections:

         <activity
                android:name=".MainActivity"
                android:label="@string/app_name"
                android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
                android:windowSoftInputMode="adjustResize"
                android:launchMode="singleTask"
                android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.SEND"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:mimeType="application/*"/>
                <data android:mimeType="audio/*"/>
                <data android:mimeType="image/*"/>
                <data android:mimeType="text/*"/>
                <data android:mimeType="video/*"/>
            </intent-filter>
        </activity>

The first intent filter permits to:

  • create an icon on the desktop of the application
  • launch the application from the desktop icon

The second intent filter permits to:

  • associate data (intent) to the application
  • launch the application

Another thing interesting to note: intents are embedded inside android activities

Architecture

How the ent application can receive these documents?

There is differents possibilities. We have privilegied API with loose coupling between software layers.

react-native provide 3 api which both permits that:

  • ActivityEventListener,
  • LifecycleEventListener,
  • RCTDeviceEventEmitter

ActivityEventListener

/**
 * Listener for receiving activity events. Consider using {@link BaseActivityEventListener} if
 * you're not interested in all the events sent to this interface.
 */
public interface ActivityEventListener {

  /** Called when host (activity/service) receives an {@link Activity#onActivityResult} call. */
  void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data);

  /** Called when a new intent is passed to the activity */
  void onNewIntent(Intent intent);
}

LifecycleEventListener

/**
 * Listener for receiving activity lifecycle events.
 */
public interface LifecycleEventListener {

  /**
   * Called either when the host activity receives a resume event (e.g. {@link Activity#onResume} or
   * if the native module that implements this is initialized while the host activity is already
   * resumed. Always called for the most current activity.
   */
  void onHostResume();

  /**
   * Called when host activity receives pause event (e.g. {@link Activity#onPause}. Always called
   * for the most current activity.
   */
  void onHostPause();

  /**
   * Called when host activity receives destroy event (e.g. {@link Activity#onDestroy}. Only called
   * for the last React activity to be destroyed.
   */
  void onHostDestroy();
}

RCTDeviceEventEmitter

signal events to JavaScript without being invoked directly

Implementation

We forked the project react-native-file-intent which provided interested functionnalities like filepath calculation.

We added the following enhancments:

  • return the content uri
  • have a "publish subscribe" API

Native code

Here the corresponding native code permitting to notify intent to js code

public RNFileShareIntentModule(ReactApplicationContext reactContext) {
    ...
    reactContext.addActivityEventListener(this);
    reactContext.addLifecycleEventListener(this);
  }

  @Override
  public void onNewIntent(Intent aIntent) {
    /* if all processing is made on onNewIntent, that not works on the second call when application in background */

    if (mIntent == null) {
      mIntent = aIntent;
    }
  }

  @Override
  public void onHostResume() {
    Intent aIntent =  mIntent;

    if (mIntent != null) {
      mIntent = null;
      shareFile(aIntent);
    }
  }

...
  /**
   * Share the intent
   * @param intent
   */
   private void shareFile(Intent intent) {
    String action = intent.getAction();
    String type = intent.getType();
    Activity currentActivity = getCurrentActivity();

    WritableMap res = new WritableNativeMap();
    if (Intent.ACTION_SEND.equals(action)) {
      if (type.startsWith("application/") || type.startsWith("audio/") || type.startsWith("image/") ||
              type.startsWith("video/")) {
          sendEvent( fileHelper.getFileData(intent.getParcelableExtra(Intent.EXTRA_STREAM), currentActivity));
      }
    }

...

  /**
   * send event to js code
   * "FileShareIntent" is the name space subscription
   * @param params
   */
  private void sendEvent(WritableMap params) {
    reactContext
      .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
      .emit("FileShareIntent", params);
  }

js code

We use an HOC above the MainNavigator. The interest to use MainNavigator as Hoc is we don't have to test the existance of MainNavigator because we are main navigator.

export default function withLinkingAppWrapper(WrappedComponent: React.Component): React.Component {
  class HOC extends React.Component {
    eventEmitter: NativeEventEmitter | null = null;

    public componentDidMount() {
      if (Platform.OS === "android") {
        RNFileShareIntent.getFilePath((contentUri: ContentUri) => {
          this.navigate(contentUri);
        });

        this.eventEmitter = new NativeEventEmitter(NativeModules.RNFileShareIntent);

        this.eventEmitter.addListener("FileShareIntent", (contentUri: ContentUri) => {
          this.navigate(contentUri);
        });
      }
    }

    private navigate(contentUri: ContentUri) {
      nainNavNavigate("Workspace", {
        contentUri: null,
        filter: FilterId.root,
        parentId: FilterId.root,
        title: I18n.t("workspace"),
        childRoute: "Workspace",
        childParams: {
          parentId: "owner",
          filter: FilterId.owner,
          title: I18n.t("owner"),
          contentUri: [contentUri],
        },
      });
    }

    public componentWillUnmount(): void {
      if (Platform.OS === "android") this.eventEmitter?.removeListener("FileShareIntent", this.navigate);
    }

    public render() {
      return <WrappedComponent {...this.props} />;
    }
  }

  return HOC;
}

We see in checking the code a explicit call to native module RNFileShareIntent.getFilePath((contentUri: ContentUri) is done.

That's because ActivityEvent and LifecycleEvent are not call when ent application is close.

Clone this wiki locally