Access Platform APIs with React Native Modules
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.
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
To create a simple native module you need to at least two files.
- React Package class
- 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);
First import the NativeModule
s 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?