Building a mobile share extension for a React Native app
I recently started working on building a share extension so I decided to share my work with the rest of the world. In this tutorial, I’m assuming you are building an app in React Native. You could either build a react native extension for both, or a native iOS and a native Android extension. I’ll be talking about building the native iOS and the React Native extension for both.
The only reason I’m going to review the native iOS extension is because it’s more common to want to leverage iOS UI and standard dialogs in the share extension. On the other hand, Android is far less standardized.
Building a share extension in React Native has the benefit of mostly working on both platforms without nearly as much additional overhead. On top of that, you’re working in Javascript, which [to me, a humble web dev] is easier and faster than working with Objective-C or Swift.
When building a share extension, it’s generally expected that you have authentication around your app in some form or fashion. Though since it’s not really required, I’ll gloss over that pretty quickly right now.
The practice I’ve found most effective, is to first fetch oauth credentials when logging in on the app and then store those in an app group bucket for iOS or use Shared Preferences on Android. You then fetch them in a similar fashion when making your request.
The building blocks
First, we need to add a Share Extension project to each build.
iOS
Open your project file in Xcode and add a new “target”, select share extension for the type, and name it. You’ll have a ShareViewController object and header file generated within your project. Open your project file and select your share extension target. Open the “Link binaries with libraries” dropdown and hit add. I went ahead and added all of the libraries under “Workspace” except for the Tv-OS.
In your info.plist
file, make sure you add the following:
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>10</integer>
<key>NSExtensionActivationSupportsText</key>
<true/>
</dict>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
This defines the data types that will activate your share extension.
If you’re using app groups to share credentials, this is where you would enable app groups in both the main target app and the share extension app. You’ll need to get on your developer.apple.com account to add the capability to your certificate and create the group.
Android
Right click the java folder of your existing project and select new → package and add your share package (I’d recommend [your existing package].share for the name). You’ll want to make sure that in your AndroidManifest.xml
you see your activity listed. Inside of it, you should add an intent filter that looks like this:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
The first filter opens the view, the second one allows you to receive data. Note the mimeType
data field and SEND
vs. SEND_MULTIPLE.
Building the extension
Now that we have the projects set up, we can dive into the implementation for iOS, then React Native on iOS, and lastly React Native on Android. I won’t go through much React Native code because you can build that however you want.
iOS Native Extension
Let’s start by opening the given ShareViewController.h
and having the class inherit from SLComposeServiceViewController.
You’ll need to #import <Social/Social.h>
and then in your .m
file implement the following:
- (BOOL)isContentValid {}- (void)didSelectPost {}- (void)loadView {}
isContentValid
is called to determine if the post button is tappable.didSelectPost
is called when the user presses the post buttonloadView
is called once the dialog shows up and is where we load data from the app groups bucket.
The default here is going to have a cancel
and a post
button at the top and a text dialog and a preview of the content if it isn’t just plaintext.
In our loadView
we want to do the following:
Defining didSelectPost
should make a POST request to your server to share the data. I’d recommend adding cocoa pods
to your app and using the AFNetworking
module. That’s a majority of what you need to build out your share extension in objective c.
iOS React Native Extension
I’m going to assume that you’ve already built the React Native component. It should be pretty much the same as any other component. Ignore the above changes to the .h and .m files.
Open your ShareViewController.h
and make sure the class inherits from UIViewController<RCTBridgeModule>.
Then open up the .m
file and add the following methods:
Everything is pretty similar to a normal React Native app, except I’ve used share.ios
for the bundle URL instead of index.ios,
so you should create a share.ios.js
file and import your component and register it using the AppRegistry.
The data
method will fetch the extension data, and the close
method will close your extension. Also, don’t forget the RCT_EXPORT_MODULE();
call.
In your React Native component, you can now:
import { NativeModules } from 'react-native';const ShareExtension = NativeModules.ShareExtension;
Then, calling ShareExtension.data
and ShareExtension.close
will fetch the data and close the extension respectively.
You will also want a method that loads the context like this one here. The extractDataFromContext
method referenced in the gist above should be the similar to the loadContext
method from the iOS Native section, except it will pass the data into the callback. Here’s the signature:
- (void)extractDataFromContext:(NSExtensionContext *)context withCallback:(void(^)(NSString *value, NSString* contentType, NSException *exception))callback {}
That should be about everything you need to do to get your react native component showing up as a share extension.
React Native Android
For the react native extension on Android, we add ShareActivity.java
, ShareApplication.java
, ShareModule.java
, and SharePackage.java
.
ShareActivity.java
This file just needs a definition for the getComponentName
method like such:
You’ll want to replace “MyShareExtension” with the name of your React Native component here.
ShareModule.java
This just defines the name that we export with, and the close method. And here is an example of the processIntent
method.
SharePackage.java
Imports the share module and exports it as a React Native module.
ShareApplication.java
This should be the same as any other React Native application. You can copy the base code from a React Native app. The only difference is you should only need to add your SharePackage
from above to the list of ReactNative packages imported.
Conclusion
There you have it. A 10,000 feet walkthrough of building a share extension for a React Native app. I don’t claim to be an outstanding mobile dev, and this was one of my first attempts at working with Objective-C in case you haven’t noticed. Let me know if you have issues that I can assist with, or if there are any errors in the information I shared above. Thanks!