Building a React Native module for Windows

This post has been republished via RSS; it originally appeared at: New blog articles in Microsoft Tech Community.

React Native offers a wide range of modules to extend the core functionalities of the platform, developed either by the community or by the React team himself. These modules range from UI controls, helpers and services. Some of them are "generic" and they perform tasks which don't require access to any specific platform feature. For example, Redux is a very popular state management library used in React applications but since, by default, everything is stored in memory, it basically runs everywhere. In fact, the same library can be used either with web applications (using React) or native applications (using React Native).

 

However, there are many scenarios in the native development space where the only way to achieve a task is by interacting with the native APIs offered by the platform. Saving a file in the local storage; getting information about the device; accessing to the GPS are just a few examples. In this case, the module can't be generic, because each platform has its own way to access to native features. This is why React Native supports the concept of native modules: a module that is leveraged in the main application through JavaScript but that, under the hood, invokes a native component specific for the platform where the application is running. Being native, each component isn't built in JavaScript, but using the native language used by the platform. For example, take a look at the popular module AsyncStorage, which can be used to read and write files in the local storage. As you can see, we have a folder called ios, which contains a XCode project; the android folder, instead, contains some Java files. Both projects simply expose the same features, but implemented using the specific language and APIs from iOS and Android.

 

If you have some experience with Xamarin and Xamarin Forms, this approach will be familiar. It's the same exact concept, in fact, behind Xamarin Plugins: the entry point is a C# wrapper which, under the hood, calls Swift, Java or UWP code based on the platform where the application is running.

 

In this blog post we're going to learn how we can build such a native module with Windows support, so that we can access to native Windows APIs when our React Native application is running on Windows.

 

Create a React Native for Windows project

As first step, let's create a React Native project with Windows support. I won't explain all the steps, since they are very well documented on the official GitHub repository. Additionally, I already introduced React Native and the recently added Windows support in another article.

Once you have completed all the steps, open the windows folder inside the project and double click on the Visual Studio solution. As we have learned in the previous article, the solution contains a single application which is the host for our React Native content. In a regular scenario you won't need to touch the code here, since the whole development will happen in the JavaScript world. However, in our case we have to make an exception, since we need to interact with native Windows APIs to build our module.

 

Create the module with a Windows Runtime Component

Let's start by adding a new project to the solution, which type is Windows Runtime Component. The component will give us access to all the Universal Windows Platform APIs. Additionally, being a WinRT component, it can be consumed regardless of the language used to build the main application. As a consequence, you're free to create a C# or C++ component, based on the language you're most familiar with. We'll be able to leverage it from the main React Native host (which is built in C++) regardless.

In my case, C# is my favorite language, so I've chosen the Windows Runtime Component (C#) template. If you want to use C++, instead, you need to choose the Windows Runtime Component (C++/WinRT) template. If you don't see it, you can install it using this extension. Pay attention to not use the C++/CX template, because this approach to build C++ components for the Universal Windows Platform has been deprecated.

 

WindowsRuntimeComponent.png

 

Once the project has been created, you need to reference a couple of projects which are already included as part of the React Native for Windows solution. Right click on the project you have just created and choose Add → Reference.

  • From the Projects section, choose Microsoft.ReactNative. This is the core React Native implementation for Windows.
  • From the Shared Projects section, choose Microsoft.ReactNative.SharedManaged. This project will give us access to C# helpers that will make easy to build a native module. If you're using C++, the project to reference is called Microsoft.ReactNative.Cxx.

Before starting to write some code, expand the References section of the project, select the Microsoft.ReactNative library, right click on it and choose Properties. Set Copy Local to false, to make sure that there are no conflicts with the main application.

Now we can start building our module. We're going to create a simple one to expose the information about the model of the device where the application is running. Let's start by renaming the default class included in the project with a more meaningful name, like SampleComponent. This should trigger the renaming also of the class declared inside the code.

Now we just need to define one or more methods, that will be exposed to the React Native application. In our case, we're going to define a single method that will return the current device name, using the EasClientDeviceInformation class included in the Universal Windows Platform:

 

using Windows.Security.ExchangeActiveSyncProvisioning;

namespace SampleReactModule
{
    class SampleComponent
    {
        public string GetDeviceModel()
        {
            EasClientDeviceInformation info = new EasClientDeviceInformation();
            return info.SystemProductName;
        }
    }
}

The next step is to leverage the attributes provided by the Microsoft.ReactNative library to export the class and the method we have created to React Native. In our scenario we're going to use two of the many available ones:

  • [ReactModule], which we're using to decorate the whole class.
  • [ReactMethod], which we're using to decorate every single method we want to expose.

This is the final look of our class:

 

using Microsoft.ReactNative.Managed;
using Windows.Security.ExchangeActiveSyncProvisioning;

namespace SampleReactModule
{
    [ReactModule]
    class SampleComponent
    {
        [ReactMethod("getDeviceModel")]
        public string GetDeviceModel()
        {
            EasClientDeviceInformation info = new EasClientDeviceInformation();
            return info.SystemProductName;
        }
    }
}

Notice that we have passed the getDeviceModel value as parameter of the ReactMethod attribute. This is how we define the name of the method that will be available through JavaScript to the React Native project.

There are many other attributes, which can be used to expose additional stuff to the React Native application, like properties or event handlers. They are all documented here.

The next thing we need is a ReactPackageProvider, which is a special object that will allow the React Native project to load all the modules exposed by our library. By default, in fact, a React Native application loads only the built-in modules. Right click on the project and choose Add → Class. Name it ReactPackageProvider. The class is very simple:

 

using Microsoft.ReactNative.Bridge;
using Microsoft.ReactNative.Managed;

namespace SampleReactModule
{
    public sealed class ReactPackageProvider: IReactPackageProvider
    {
        public void CreatePackage(IReactPackageBuilder packageBuilder)
        {
            packageBuilder.AddAttributedModules();
        }
    }
}

It just needs to implement the IReactPackageProvider interface, which will require you to implement the CreatePackage() method. Inside it we leverage the packageBuilder object and the AddAttributedModules() method to load all the modules we have decorated with the [ReactModule] attribute inside our project.

 

Register the module in the React Native application

As already mentioned, by default React Native for Windows registers only the modules which are included in the main project. If we would have created the SampleComponent class inside the main React Native project, we wouldn't have needed any additional step. However, in our case we are using a separate Windows Runtime Component, which allows us to leverage a different language (C#), so we need an extra step to register the library in the main application.

 

First, right click on the React Native for Windows project and choose Add → Reference. Select from the list the name of the Windows Runtime Component you have just created (in my sample, it's SampleReactModule) and press Ok.

 

Expand the App.xaml node in the main React Native project and double click on the App.cpp file. Inside the App constructor you will find the following entry:

 

PackageProviders().Append(make<ReactPackageProvider>());

This is the code which loads all the modules that are included in the main project. Let's add below another registration for our new library:

 

PackageProviders().Append(winrt::SampleReactModule::ReactPackageProvider());

SampleReactModule is the name of the Windows Runtime Component we have previously created, thus it's the namespace that contains our ReactPackageProvider implementation. To make this code compiling we need also to include the header file of our module at the top:

 

#include "winrt/SampleReactModule.h"

This is how the whole App.cpp file should look like:

 

#include "pch.h"

#include "App.h"
#include "ReactPackageProvider.h"
#include "winrt/SampleReactModule.h"

using namespace winrt::NativeModuleSample;
using namespace winrt::NativeModuleSample::implementation;

/// <summary>
/// Initializes the singleton application object.  This is the first line of
/// authored code executed, and as such is the logical equivalent of main() or
/// WinMain().
/// </summary>
App::App() noexcept
{
    MainComponentName(L"NativeModuleSample");

#if BUNDLE
    JavaScriptBundleFile(L"index.windows");
    InstanceSettings().UseWebDebugger(false);
    InstanceSettings().UseLiveReload(false);
#else
    JavaScriptMainModuleName(L"index");
    InstanceSettings().UseWebDebugger(true);
    InstanceSettings().UseLiveReload(true);
#endif

#if _DEBUG
    InstanceSettings().EnableDeveloperMenu(true);
#else
    InstanceSettings().EnableDeveloperMenu(false);
#endif

    PackageProviders().Append(make<ReactPackageProvider>()); // Includes all modules in this project
    PackageProviders().Append(winrt::SampleReactModule::ReactPackageProvider());


    InitializeComponent();

    // This works around a cpp/winrt bug with composable/aggregable types tracked
    // by 22116519
    AddRef();
    m_inner.as<::IUnknown>()->Release();
}

 

Using the module from JavaScript

That's it! Now we can move to the React Native project to start using the module we have just built from JavaScript. Open the folder which contains your React Native project with your favorite web editor. For me, it's Visual Studio Code. For simplicity, I've implemented the component which uses the native module directly in the App.js file of my React Native project, so that it will be the startup page.

The first step is importing the NativeModule object exposed by React Native in our component:

 

import { NativeModules } from 'react-native';

Our module will be exposed through this object, by leveraging the name of the class we have decorated with the [ReactModule] attribute. In our case, it's SampleComponent, so we can use the following code to access to the GetDeviceModule() method:

 

NativeModules.SampleComponent.getDeviceModel();

Notice that we can reference the method with the lowercase name, getDeviceModel(), thanks to the parameter we have passed to the [ReactMethod] attribute. However, by default the methods exposed by native modules are implemented in an asynchronous way using callbacks. As such, if we want to consume the getDeviceModel() method, we need to use the following approach:

 

getModel = () => {
  var current = this;
  NativeModules.SampleComponent.getDeviceModel(function(result) {
    current.setState({model: result});
  })
}

The method accepts a function, that is invoked once the asynchronous operation is completed. Inside this function we receive, as parameter, the result returned by our native method (in our case, the model of the device). In our sample, we store it inside the component's state, so that we can display it in the user interface using the JSX syntax:

 

<Text>Model: {this.state.model}</Text>

Before calling the setState() method, however, we need to save a reference to the main context. Inside the callback, in fact, we have moved to a different context, so we don't have access to the helpers exposed by React Native.

Below you can find the full definition of our component:

 

import React, {Fragment} from 'react';
import {
  StyleSheet,
  View,
  Text,
  StatusBar,
  Button
} from 'react-native';


import { NativeModules } from 'react-native';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      model: '',
    }
  }

  getModel = () => {
    var current = this;
    NativeModules.SampleComponent.getDeviceModel(function(result) {
      current.setState({model: result});
    })
  }

  render() {
    return (
      <View style={styles.sectionContainer}>
        <StatusBar barStyle="dark-content" />
        <View>
          <Button title="Get model" onPress={this.getModel} />
          <Text>Model: {this.state.model}</Text>
        </View>
      </View>
    );
  }
 
};

//styles definition

export default App;

If you're familiar with React Native, the code should be easy to understand:

  1. We have defined a function called getModel(), which takes care of interacting with our native module. The model of the device is stored inside the component's state, using the setState() function provided by React Native.
  2. The render() methods defines the UI component, using the JSX syntax. The UI is very simple:
    • We have a <Button>, which invokes the getModel() function by leveraging the onPress event.
    • We have a <Text>, which displays the value of the model property stored inside the state.

 

Testing the code

Now it's time to test the code! First, right click on the React Native project in Visual Studio and choose Deploy. Before doing it, however, use the Configuration Manager dropdown to make sure you're targeting the right CPU architecture. By default, in fact, the solution will target ARM, so you will have to switch to x86 or x64. If it's the first time you build it, it will take a while since C++ compilation isn't exactly fast =) Once it's done, open a command prompt on the folder which contains your React Native project and type yarn start. This will launch the Metro packager, which will serve all the React Native components to your application. Once the dependency graph has been loaded, you can open the Start menu and launch the Windows application you have just deployed from Visual Studio. If you have done everything in the correct way, you will see the very simple UI we have built in our component. By pressing the button you will invoke the native module and you will see the model of your device being displayed:

 

ReactNativeApp.png

Improving the code

If you compare this development experience with the one you have when you add a 3rd party module to your project using yarn or npm, you realize that it's quite different. In such a case, in fact, you don't have to deal with the NativeModules object; or you don't need to use callbacks to call the various methods. Let's use again the example of the popular AsyncStorage module. If you want to store some data in the storage, you just import a reference to the AsyncStorage object and then you call an asynchronous method called setItem:

 

import AsyncStorage from '@react-native-community/async-storage';

storeData = async () => {
    await AsyncStorage.setItem('@storage_Key', 'stored value')
}

Can we achieve the same goal with the native module we have just built? The answer is yes! We just need to build a JavaScript wrapper, that will make consuming our native module more straightforward.

In Visual Studio Code add a new file in your React Native project and call it SampleComponent.js. First, let's import the same NativeModules object we have previously imported in the main component:

 

import { NativeModules } from 'react-native';

Now, using JavaScript Promises, we can build a wrapper to the getResult() method. Thanks to Promises, we can enable an easier approach to consume our asynchronous API, thanks to the async and await keywords. If you have some C# background, this approach is similar to use the TaskCompletionSource class to build asynchronous operations that returns a Task object-

This is how we can wrap the method:

 

import { NativeModules } from 'react-native';

export const getDeviceModel = () => {
    return new Promise((resolve, reject) => {
        NativeModules.SampleComponent.getDeviceModel(function(result, error) {
            if (error) {
                reject(error);
            }
            else {
                resolve(result);
            }
        })
    })
}

The method returns a new Promise, which requires us to fulfill two objects:

  • resolve, which is invoked when the operation has completed successfully.
  • reject, which is invoked when the operation has failed.

Inside the Promise we invoke the getDeviceModel() method, in the same way we were doing before in the component. The only difference is that, in this case, once the callback is completed we pass the result to the resolve() method in case of success; otherwise, we pass the error to the reject() method.

Now that we have built our wrapper, we can simplify the component we have previously built. First, remove the following line:

 

import { NativeModules } from 'react-native';

Then replace it with the following one:

 

import * as SampleComponent from './SampleComponent'

This import will expose the functions we have defined in our wrapper through the SampleComponent object. Now you can change the getModel() function simply with this code:

 

getModel = async () => {
  var model = await SampleComponent.getDeviceModel();
  this.setState( { model: model});
}

As you can see, thanks to Promises and the async and await keywords, the code is simpler to write and read. We just need to mark the function as async, then call the SampleComponent.getDeviceModel() function with the await prefix. Since the asynchronous operation is managed for us, we can treat it like if it's synchronous and just store in a variable the result, which will contain the device model. Since we aren't using callbacks, we can also set the state directly by calling this.setState(). If you are a C# developer, everything should be very familiar, since it's the same async and await approach supported by C#.

That's it! Now launch the application again. In this case we didn't touch the native project, so if the Metro packager was still running, we should already be seeing the updated version. Of course there won't be any difference, since we didn't change the UI or the behavior, but the code is easier to read and maintain now.

 

Exporting the module

So far, we have added the native module implementation directly in the project. What if we want to create a true self-contained module, that we can install using yarn or npm like any other 3rd party module? Well, unfortunately the story isn't complete for Windows yet. By following the documentation available on GitHub it's easy to create the skeleton of the module. We just need to create a new React Native module project, using the create-react-native-module CLI tool, add the React Native for Windows package and then:

 

  1. Include a folder called windows with the Windows Runtime Component we have developed.
  2. Optionally include any JavaScript wrapper we might have created to make easier consuming the module.

If you already have a module with iOS and Android support, you'll just need instead to follow step 1 and include the windows folder with the Windows Runtime Component in your existing project.

However, regardless of your scenario, the current React Native for Windows implementation lacks an important feature called linking. You'll remember that, in the previous section, we had to manually edit the App.cpp file of the React Native project to load, through the ReactPackageProvider class, our Windows Runtime Component. On Android and iOS this operation isn't needed thanks to linking. You just need to run the react-native link command: it will take care of everything for you, without needing to manually edit the Android and iOS projects. React Native 0.60 has brought linking to the next level, by introducing automatic linking. You don't even have to run the link command anymore; just add the package to your project and React Native will take of everything else for you. The React Native implementation for iOS and Android will automatically load all the 3rd party modules that have been added to the project. This feature isn't available on Windows yet and, as such, if you create an independent module and you publish it on NPM, you will still need to manually open the React Native for Windows solution in Visual Studio and register the module in the App.cpp file.

 

Publishing the application

Compared to the last time I wrote about React Native for Windows, there's a big news! Now you can create a deployable version of the application: an AppX / MSIX package which is self-contained and that doesn't need the Metro packager to be up & running. This way, you can publish the application on the Microsoft Store or using one of the many supported deployment techniques (sideloading, SSCM, Intune, etc.). To achieve this goal just start the publishing process, like you would do with any Universal Windows Platform application. Right click on the React Native project in Visual Studio, choose Publish → Create app packages and follow the wizard. Just make sure, in the package configuration step, to choose Release as configuration mode. This way, all the React Native resources will be bundled together, removing the requirement of having the Metro packager up & running.

 

Wrapping up

In this post we have seen how you can build native modules for React Native for Windows. This will allow us to build applications using the React stack but, at the same time, leverage native features provided by Windows, like toast notifications, access to Bluetooth APIs, file system, etc. We have also learned how we can distribute an application built with React Native, either via the Microsoft Store or one of the many techniques supported by MSIX, like sideloading or Microsoft Intune.

 

You can find the sample I've built for this post on GitHub.

 

Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.