How to Build an iMessage Extension for a React Native App (Part 2)

DZone 's Guide to

How to Build an iMessage Extension for a React Native App (Part 2)

In this post, I will show you how we built an iMessage extension by writing a bridge for our React Native-based mobile app.

· Web Dev Zone ·
Free Resource

React Native Development

In this post, I will show you how we built an iMessage extension by writing a bridge for our React Native-based mobile app. This approach took our team around two weeks to explore and might save you significant time if you have a similar intention. This is part two out of two (you can find Part 1 here). 

Project Recap

When we set out to build an iMessage extension for Lisk Mobile using React Native, we immediately hit an exciting challenge. As it turns out, when it comes to third-party applications, Apple likes developers to play by its own rules. If a company wants to benefit from the features of the tech giant's operational systems and rich user base, it needs to be built using Apple's own tools and programming language. iPhone's iMessage is definitely worth this hassle. It has proven to be a big hit since its release in 2016. Within the first six months, iMessage generated thousands of innovative in-messenger extensions including those created by Airbnb and Dropbox. Late in 2018, Mark Zuckerberg admitted this feature is one of Facebook's 'biggest competitors by far.' Since the release of Lisk Mobile during Lisk's Berlin meetup in October 2018, our team has been busy implementing features such as Face ID Authentication, as well as developing blockchain-specific solutions. Identifying the opportunity to extend the iMessage option for our users, we got to work on our own integration.

The iMessage extension was included in Lisk Mobile 0.10.0, which was released in February 2019. Our users can now request and send LSK tokens straight from the iOS-based messenger without opening our app. However, the journey to implement this feature wasn't straightforward — Lisk Mobile is written in JavaScript using React Native, while iMessage requires development in native iOS. During our research, we have found there is just a handful of resources available to help with using React Native to build iOS extensions available out there. There was no clear way to proceed. After thorough deliberation, we decided to try a different approach by building our own bridge implementation. We found it a very educational and motivational journey for our team to develop the feature in this way. We will show you how by breaking the solution down into native and React Native parts and describing how to bind these separated parts together.

The Problem

There was no up-to-date documentation to create an iMessage extension using React Native.

Before we dive into the solution, let's first set out the actual challenge. We used React Native in order to stay aligned with the programming language of the entire Lisk platform. We have been developing our mobile blockchain wallet since April 2018. This means we already have visual components and business logic enhanced by utility functions for cryptographic operations and communicating with the API of Lisk Core, which is a platform containing all the information necessary to interact with our blockchain, including security, consensus algorithms, and much more. The communication is provided by Lisk Elementsour modular JavaScript library.

The first option was to look for existing React Native component and educational material. Unfortunately, we could not find official documentation or up-to-date resources due to the fast pace of change in both React Native and native iOS development.

The second option was to try native iOS development within the Lisk Mobile codebase. The benefit of this approach was example projects and conference talks provided by Apple. However, introducing a considerable amount of Swift or Objective-C into the codebase was not desirable. Such a move would cause too much code duplication due to us having to rewrite most of our existing business logic and UI components.

The Solution

We wrote our own bridge implementation and documented the process.

After careful evaluation, we decided to take an alternative route: writing our own bridge implementation. In the rest of this article, we will explain how we did it. If you want to jump straight into code, we've also created this handy demo project on GitHub.

If you want to know more about Lisk first, check out this short explainer clip, our product page, or our documentation!

Creating Bridge Modules

In this section, we are going to create our helpers and modules on the Swift side in order to expose the required methods and events to communicate with the React Native side.

Creating a Mapper for Swift - JS Communication

Since we are going to send data from Swift to React Native, it would be nice to create a mapper utility for formatting that data properly.

Mapper utilityCreating a Manager for MessagesViewController

Now we are going to create a module named MessagesManager that has a connection with our main MessagesViewController and will help us to interact with from the React Native side.

Based on the guidance from the official documentation we will need access to the activeConversion object and presentation style as well as the methods that will allow us to modify them.

Swift code

Initial structure of MessagesManager.swift

Since we are creating that custom class with Swift, we also need to provide the interface file to make sure it's recognized as a native module by React Native.

React Native code

MessagesManager.rn file that contains interface declaration for React Native

Creating an EventEmitter for MessagesViewController

In addition to the helper methods we have provided in the previous section, now we will create another class by following the EventEmitter guide from the official React Native documentation.

MessagesEventEmitter will help us to keep the JavaScript side informed when there's a change in the iMessage context. Those changes may be applied as a result of an interaction made by the user (like updating presentation style, selecting a message in a conversation) or receiving a new message from one of the remote participants.

Updates we have made on MessagesViewController file in order to use protocol pattern

Initial structure of MessagesEventEmitter

MessagesEventEmmiter.rn file that contains interface declaration for React Native

First of all, we update our  MessagesViewController to define a protocol that can be easily implemented by  MessagesEventEmitter to reduce the effort we need to make (and encourage separation of concerns) in order to follow an event-based design pattern.

Custom Module Initializer

If you create a native module with React Native by following the basic flow, you'll end up with a pre-instantiated object that is created during the initialization step of the bridge. If it was a regular iOS application, we would have a chance to communicate with the root view controller by using UIApplication's shared property but that's not what we were exactly looking for.

Considering that our Native Module needs to be stateful to communicate with the  MessagesAppViewController instance, we ended up creating a custom module initializer by following an approach derived from the dependency injection guide in the React Native documentation.

React Native Code

Custom ModuleInitializer that allows us to use dependency injection pattern at the initialization step of MessagesManager and MessagesEventEmiiter modules.

Then we will update the presentReactNative method of MessagesViewController to use that custom module initializer.

Image title

Updating related parts of MessagesViewController to use custom module initializer

Consuming Native Modules on React Native

Now that we have covered pretty much all the things we need from our native modules, we can start playing with them on the React Native side.

Updating Presentation Style

React Native code

Making use of getPresentationStyle and updatePresentationStyle methods on the React Native side

React Native App

So far so good, huh? But we forgot something. Let's look at the recording below and try to catch what's missing.

React Native App

Since we are not listening to the events related to the presentation style changes triggered by the native UI, we end up in a loop with the sheet continually opening, we don't have the correct value of presentationStyle right after it's been updated. In order to resolve this, we are going to use the MessagesEventEmitter module.

This will allow the sheet to receive updates on state from both the native Swift environment (for when users manually close the sheet) and from within our React Native app.

Image title

Using MessagesEventEmitter for listening to changes on presentationSytle

Now the app responds to both triggers from the codebase, and users manually close or open the sheet.

Composing a Message

Now we are so close to our main goal, composing an iMessage from the React Native side!

Let's start with initializing our blueprint to create the data of a message object that we can through our MessagesManager module.

Image title

Using the composeMessage method of MessagesManager to create a test message.

Image title

Assests section of Xcode where we add images in order to use the Message templates.

Image title

Outcome of the test message

Using the URL Field to Share Data

Now that we are able to compose and send a message, we can create a very simple example to demonstrate the usage of the URL field.

What we are going to do is to put a timestamp and the identifier of the sender to the URL field and present it on the screen. We will also make use of the getActiveConversation method exposed by  MessagesManager, as well as the didReceiveMessage and didSelectMessage events from the MessagesEvents module.

React Native code

Updating App.iMessage.js to make use of URL field

Now if we send a message as Kate to John (or vice-versa) we can observe the change in the timestamps and the sender IDs.

Image title

Sending a message as Kate to John. Kate's sender ID is CD8FB32C-4683-669C6E42DDF8

Image title

Opening and replying to Kate's message as John. John's sender ID is 721FA6A5-CC6F-42FD-4B846E634E62

If you want to share more data and handle complex logic, our recommendation is to use a third-party library for simplifying the URL construction and parsing logic. For a better example, you can check out the iMessage part of our open source Lisk Mobile application.


Unsettled Problems

Mysterious Simulator Crash

When the application is run from Xcode, there's a crash that happens right after we go back from the message detail screen to the list screen. We haven't found the crux of this problem but we were able to reproduce it with the example project from Apple's official documentation.

Our workaround for the crash is to stop Xcode right after we get everything running. It will make more sense if you take a look at the 'Remote Debugger and Reload Menu for Development' section below.

Less Mysterious iOS 10 Crash

Unfortunately, we couldn't make our iMessage extension run successfully within a simulator that has iOS 10. The interface of the iMessage extensions has been updated when there's a migration from iOS 10 to 11. It might be related to that update but there was not enough information for us to track the problem so we ended up choosing iOS 11 as our deployment target of the iMessage extension.

Not Sure if Bug or Feature: Simulator Refreshes Participant IDs

While running the iMessage extension in the simulator, the participant identifiers coming from the activeConversation object of Kate and John which are changed each time you compose a message. Whether it stems from a bug or a feature remains to be seen, but we had to run the application on testing devices in order to make sure our participant identifier logic worked properly.



Remote Debugger and Reload Menu for Development

The setup we have made so far forces us to re-run the iMessage extension from Xcode every time we want to see the latest version of the app after updating the JavaScript part.

Considering the mysterious simulator crash we mentioned in the section above, this whole experience is kind of painful for developers coming from the realm of hot reloading with time travel. In order to improve the developer experience, we have implemented a little utility by using the DevSettings module provided by React Native.

And import it to our App.iMessage.js component as following:

React Native code

Usage of DevMenu component within the root iMessage component

Unfortunately, the DevSettings module does not export a method to get the value of the liveReload and  remoteDebugging flags so we need to double click the buttons within our component to make sure we sync their state correctly.

Deep Link Opener

In our Lisk Mobile application, we also needed a utility for opening the main application and redirecting the user to a specific deep link. We have resolved this issue by enhancing the MessagesManager.

After setting up the main application to handle deep links by following the guide from official React Native documentation, the next step is to update the MessagesManager to provide a utility for opening deep links.

Image title

We have added the openURL method to MessagesManager

Image title

Also updated the interface file of the MessagesManager to contain the openURL method

Image title

Usage of the openURL method on the React Native side

Image title

The openURL method in action!

Display a Loading View During the Initial Load

The natural behavior of iMessage extensions is to show the application logo uploaded in the assets section during the initial load as shown below.

Image title

Default behavior of the launch screen

In our case, we are also waiting for the React Native bundle to be loaded until we see something to interact on the screen. Luckily, RCTRootView has a property named loadingView that allows us to provide a temporary view to be shown while the bundle is loading.

In order to enhance this experience, let's create a view with the activity indicator and assign it to the loadingView property of RCTRootView.

Image titleXcode ->File->New->View

Image title

Using the storyboard to set the layout of the LoadingView

Image title

Updating the presentREactNative method of the MessagesViewController to make sure our RCTRootView instance uses LoadingView

Image title

End result of this section: LoadingView is presented until the React Native bundle is loaded

Controlling the Loading View From the React Native Side

Now that we have our blueprint for displaying that loading view, we can also put in a little effort to make it controllable from the React Native side.

This would be very handy if there's a case you need to delay the initial render (e.g. fetching data from the server) and don't want to deal with creating another loading view that covers full screen for such a task.

First, the presentReactNativeView method of MessagesViewController needs to be refactored a little bit as follows:

Image title

The presentReactNative method initializes and presents a LoadingView on top of the RCTRootView, instead of using the RCTRootView.loadingView property

Image title

In order to control the presence of that loadinView, we have added two methods to the MessagesManager

Image title

Updated interface file of the MessagesManager to contain methods for controlling the loadingView

Now it's time to make use of those freshly added methods on the React Native side. What we are going to do is to fake an asynchronous task in componentDidMount and delay the initial rendering of the content until that task is completed. We will also add an additional button to the sample set to toggle the loading screen again.

Image title

Making use of loadingView related methods on the React Native side

Final Thoughts

Working on user-facing applications in an insular software environment such as that of Apple requires innovative solutions. While Apple definitely provides comprehensive documentation aimed at helping developers coding with native iOS, connecting the existing pieces to the native ecosystem is another thing altogether. After deciding to implement an iMessage extension for the React Native-based Lisk Mobile, we experienced this obstacle first hand. Nonetheless, we found writing a bridge between React Native and the native iOS-based app an exciting and educational experience. We hope this tutorial will save you time in implementing a similar feature to your React Native application as well!

javascript tutorial, mobile application development, react native tutorial, web dev

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}