Access Platform APIs with React Native Modules

Sajjad Ashraf
Share

This article was peer reviewed by Wern Ancheta. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

React Native from Facebook is an addition to the popular React JavaScript library for creating Native Mobile Apps. It has proved more popular, performant and feature packed than other frameworks, but there are times when its feature set falls short. For these times, React Native has an excellent way of creating your own modules for accessing a Native API not yet supported. In this tutorial I will show you how to create React Native Modules that exposes the Android MediaPlayer api to react Native.

You can find the full code for the project on GitHub.

Getting Started with React Native

SitePoint has a full guide for installing React Native available here. Once it’s installed, create a new project with the following command:

react-native init ReactNativeModuleTutorial

This will create a new folder named ReactNativeModuleTutorial in your current working directory. Navigate to the folder and run the new project on your device or emulator using the following command.

Note: If you are using an Android emulator then you have to start the emulator before running the command.

react-native run-android

This will create all the necessary files and folders in the project and you should see the following screen on your emulator.

Application Screenshot

Creating a React Native Android Native Module

Now that you have set the project up, it’s time to create a React Native module. Create a new folder named myaudiomodule in the following directory:

ReactNativeModuleTutorial/android/app/src/main/java/com/reactnativemoduletutorial

Directory Structure

To create a simple native module you need to at least two files.

  1. React Package class
  2. A java class that extends ReactContextBaseJavaModule

The React Package file takes care of packaging different modules together in one file to used later in JavaScript code. Create a file named MyAudioPlayerPackage.java inside the myaudioplayer folder. You don’t have to pay much attention to this file as the important part is to add the module (that you will create shortly) to the package inside the createNativeModules method. If you decide to create multiple module files then you will need to add those here as well.

Add the following to MyAudioPlayerPackage.java:

package com.reactnativemoduletutorial.myaudioplayer;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MyAudioPlayerPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new MyAudioPlayerModule(reactContext)); // adding the module to package
        return modules;
    }
    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

It’s time to move on to the exciting part of writing the module that you will use later in the JavaScript code. Create another file in the same folder named MyAudioPlayerModule.java. In this file you will implement all the logic of the module and to access the Android API’s that are not yet available in React Native.

package com.reactnativemoduletutorial.myaudioplayer;

import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
import java.util.Map;

// these classes are required for playing the audio
import android.media.MediaPlayer;
import android.media.AudioManager;

public class MyAudioPlayerModule extends ReactContextBaseJavaModule {

  private static MediaPlayer mediaPlayer = null;

  public MyAudioPlayerModule(ReactApplicationContext reactContext) {
    super(reactContext);
  }

   @Override
   public String getName() {
     return "MyAudioPlayer";
   }

}

The package is com.reactnativemoduletutorial.myaudioplayer since you are inside the myaudioplayer folder. You can also create these files anywhere inside the reactnativemoduletutorial folder if you change the package accordingly.

The code first imports all the classes needed to create the functionality the module needs. Every module class extends ReactContextBaseJavaModule and ReactContextBaseJavaModule requires the getName method. The method allows you to set a name for the module used inside the JavaScript code to access the module.

@Override
public String getName() {
  return "MyAudioPlayer";
}

The methods annotated with @ReactMethod will be accessible in the JavaScript code and these bridge methods are always of return type void. You must declare every method you want to use in the JavaScript code this way.

I have created a couple of methods for playing the audio file in the module but the implementation of the audio player is up to you, feel free to write your own code for the player.

@ReactMethod
public void preparePlayer(String url) {
  try{
    if (mediaPlayer != null) {
      mediaPlayer.release();
      mediaPlayer = null;
    }
    mediaPlayer = new MediaPlayer();
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mediaPlayer.setDataSource(url);
    mediaPlayer.setLooping(true);
    mediaPlayer.prepareAsync();
  }catch(Exception e){  }
}

@ReactMethod
public void play() {
  try{
    if (mediaPlayer != null) {
      if (!mediaPlayer.isPlaying()) {
        mediaPlayer.start();
      }
    }
  }catch(Exception e){}
}

@ReactMethod
public void pause(){
  try{
    if (mediaPlayer != null) {
      if (mediaPlayer.isPlaying()) {
        mediaPlayer.pause();
      }
    }
  }catch(Exception e){}
}

The code is reasonably self-explanatory with methods for setting up the audio player, playing and pausing using the MediaPlayer class available in Android.

The vital part of writing a native module is creating methods that accept callback methods invoked after certain tasks. This is how you can pass values from Java to JavaScript.

Create a new method called setOnPreparedCallback which will take a callback method as an argument and will fire this callback when the audio file is ready to play.

@ReactMethod
public void setOnPreparedCallback(Callback onPrepared){
  final Callback onPreparedCallback = onPrepared;
  try{
    mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
      @Override
      public void onPrepared(MediaPlayer player) {
        try{
          onPreparedCallback.invoke(mediaPlayer.getDuration()); // invoking the callback with duration as argument
        }catch(Exception e){}
      }
    });
  }catch(Exception e){}
}

The last step is to tell React Native about the package. Edit MainApplication.java in ReactNativeModuleTutorial/android/app/src/main/java/com/reactnativemoduletutorial/ to import MyAudioPlayerPackage.java from the myaudioplayer folder.

import com.reactnativemoduletutorial.myaudioplayer.MyAudioPlayerPackage;

Update the getPackages method to the following:

return Arrays.<ReactPackage>asList(
    new MainReactPackage()
    new MyAudioPlayerPackage() // the line added
);

Now you can use the module in the React Native application. Open index.android.js in the root of the project and paste the following code in, replacing anything that is already there. I have designed a simple application that has play and pause buttons with 3 indicators of the current state of the application.

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  ToastAndroid,
  View,
  NativeModules,
  TouchableHighlight
} from 'react-native';

var MyAudioPlayer  = NativeModules.MyAudioPlayer;

var buttonStyles = { marginTop: 8, backgroundColor: '#dddddd', padding: 10 };
var statStyle = { flex: 0.5, backgroundColor: '#cccccc', padding: 8, borderColor: '#ffffff', borderWidth: 1, margin: 2 };

class ReactNativeModuleTutorial extends Component {
  constructor(props){
    super(props);
    this.state = {
      mp3Url: 'http://www.example.com/audio.mp3',
      prepared: false,
      playing: false,
      duration: 0
    };
  }
  componentDidMount(){
    MyAudioPlayer.preparePlayer(this.state.mp3Url);
    MyAudioPlayer.setOnPreparedCallback((duration) => {
      this.setState({ prepared: true, duration: duration });
      ToastAndroid.show('Audio prepared', ToastAndroid.LONG);
    });
  }
  playSound(){
    if (this.state.prepared === true) {
      this.setState({ playing: true });
      MyAudioPlayer.play();
      return true;
    }
    return false;
  }
  pauseSound(){
    if (this.state.prepared === true && this.state.playing === true) {
      MyAudioPlayer.pause();
      this.setState({ playing: false })
      return true;
    }
    return false;
  }
  render() {
    return (
      <View style={{ flex:1, alignItems: 'stretch', backgroundColor: '#F5FCFF' }}>
        <View style={{ padding: 10, backgroundColor: '#939cb0' }}>
          <Text style={{ color: '#ffffff', textAlign: 'center', fontSize: 24 }}>Audio Player</Text>
        </View>
        <View style={{ alignItems: 'flex-start', flexDirection: 'row', marginTop: 8 }}>
          <View style={statStyle}><Text style={{ textAlign: 'center' }}>Prepared : {(this.state.prepared) ? 'Yes' : 'No'}</Text></View>
          <View style={statStyle}><Text style={{ textAlign: 'center' }}>Playing : {(this.state.playing) ? 'Yes' : 'No'}</Text></View>
          <View style={statStyle}><Text style={{ textAlign: 'center' }}>Duration : {this.state.duration}</Text></View>
        </View>
        <View style={{ padding: 5 }}>
          <TouchableHighlight
            style={buttonStyles}
            onPress={this.playSound.bind(this)}>
            <Text style={{ textAlign: 'center' }}>Play</Text>
          </TouchableHighlight>
          <TouchableHighlight
            style={buttonStyles}
            onPress={this.pauseSound.bind(this)}>
            <Text style={{ textAlign: 'center' }}>Pause</Text>
          </TouchableHighlight>
        </View>
      </View>
    );
  }
}

AppRegistry.registerComponent('ReactNativeModuleTutorial', () => ReactNativeModuleTutorial);

App Interface

First import the NativeModules component, as it contains the module that just created and any other native modules.

You accessed the module using the following code:

var MyAudioPlayer  = NativeModules.MyAudioPlayer;

Now all the methods defined in the module are available. Look at the componentDidMount method, where you prepare the player first using MyAudioPlayer.preparePlayer method and then set the callback method for OnPreparedListener.

componentDidMount(){
  MyAudioPlayer.preparePlayer(this.state.mp3Url);
  MyAudioPlayer.setOnPreparedCallback((duration) => {
    this.setState({ prepared: true, duration: duration });
    ToastAndroid.show('Audio prepared', ToastAndroid.LONG);
  });
}

A Cross-Platform Native Bridge

By creating your own React Native modules you have the possibility to bridge native Android (and iOS) functionality to a cross-platform codebase. You need to understand coding for your native codebase of choice, but by doing so help React Native developers access new functionality and possibilities.

Have you ever created your own React Native module? What did you make and how did you find the process?