React Native and JUCE Part 2: Android
Android — Setting up
First thing we need to do is to use my fork of JUCE.
You will need to use this to (re)build the Projucer.
git clone https://github.com/adamski/JUCE.git
Initial Projucer settings
In the Android Studio Config section in Projucer, add to the following sections:
Extra Compiler Flags:
-DJUCE_ANDROID_ACTIVITY_CLASSNAME=com_company_myapp_MainActivity -DJUCE_ANDROID_ACTIVITY_CLASSPATH='com/company/myapp/MainActivity'
Android Activity class name:
com.company.myapp.MainActivity
Replacing com.company.myapp with your app’s “Bundle Identifier” for both fields.
Minimum SDK Version:
16
Android Theme:
@style/Theme.AppCompat.NoActionBar
Android Application Class:
MainApplication
Assuming you have installed React Native Navigation via npm, we will now link the library to our Android Studio project.
gradle app dependencies:
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:support-v4:+"
compile "com.android.support:appcompat-v7:23.0.2"
compile "com.facebook.react:react-native:+"
compile project(':react-native-navigation')
gradle project repositories:
mavenLocal()
jcenter()maven {
url "$rootDir/../../node_modules/react-native/android"
}
Next, rename MainComponent.cpp to MainComponent.h.
Save and open in Android Studio.
JUCE code
Open up Main.cpp. Update the header includes to the following:
#if JUCE_IOS
#include "iOS/MainWindowIOS.h"
#elif JUCE_ANDROID
#include "MainComponent.h"
#else
#include "MainWindow.h"
#endif
In your JUCEApplication
subclass, we are going to create a ResizableWindow
that will hold our JUCE component.
Change the initialise
method to the following, adapted to your Component class if necessary:
void initialise (const String& commandLine) override
{
#if JUCE_ANDROID
// Set up our windows that will be attached to Android views
container = new ResizableWindow ("MainComponent", true);
container->setContentOwned (new MainContentComponent(), true);
container->setUsingNativeTitleBar (true);
container->setFullScreen (true);
container->setVisible (true);
#else
// Instantiate MainWindow
mainWindow = new MainWindow (getApplicationName());
#endif
}
You will also need to declare your container as a private member of your JUCEApplication (only for the Android build):
#if JUCE_ANDROID
ScopedPointer<ResizableWindow> container;
#else
ScopedPointer<MainWindow> mainWindow;
#endif
For the sake of completeness, we can also change the shutdown()
method to
void shutdown() override
{
#if JUCE_ANDROID
container = nullptr;
#else
mainWindow = nullptr; // (deletes our window)
#endif
}
It’s highly recommended to use an OpenGLContext to render on Android (otherwise drawing will be slow).
Add one as a private member to your JUCEApplication
class:
OpenGLContext openGLContext;
Now, our first foray into JNI code — we have to be careful not to attach the OpenGLContext
right away, due to the way React Native creates and destroys views (otherwise we are likely to hit an assertion in JUCE). So we need to be able to trigger this from Java. Now we’re going to set up a C++ method that is callable from Java using the JUCE helper macro JUCE_JNI_CALLBACK
.
First we need to include the JUCE JNI helpers header. In the topmost#IF JUCE_ANDROID
block add:
namespace juce
{
#include "../JuceModules/juce_core/native/juce_android_JNIHelpers.h"
}
The following code (adapted to your app class) can go at the bottom of your Main.cpp file, adapted to the name of your JUCEApplication
class.
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, attachOpenGLContext, void, (JNIEnv* env, jclass))
{
MyJuceApp* const app = dynamic_cast<MyJuceApp*> (JUCEApplication::getInstance());
if (app != nullptr)
{
app->attachToOpenGLContext();
}
}
And add our public method attachToOpenGLContext
:
void attachOpenGLContext()
{
if (! openGLContext.isAttached())
openGLContext.attachTo (*container);
}
OK, now we have prepared the JUCE/C++ side of things, we can move onto some Java.
Java code
If you are using the React Native Navigation library, we currently need to change it’s build.gradle
to the experimental gradle format. Open the react-native-navigation
build.gradle
file and replace it with the following:
In Android Studio, create a new Java class under your bundle identifier, call it MainActivity.java (if it already exists just overwrite it as below). Add the following after the package
statement:
import android.os.Bundle;
import android.util.Log;
import android.media.AudioManager;
import com.juce.JuceBridge;
import com.reactnativenavigation.controllers.SplashActivity;
public class MainActivity extends SplashActivity
{
private JuceBridge juceBridge;
public static native void attachOpenGLContext (String componentName);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
juceBridge = JuceBridge.getInstance();
juceBridge.setActivityContext(this);
juceBridge.setScreenSaver(true);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
@Override
public void onRequestPermissionsResult (int permissionID, String permissions[], int[] grantResults)
{
juceBridge.onRequestPermissionsResult (permissionID, permissions, grantResults);
}
}
Save the file. Now we need to create our Application
class. Create another Java class (also under your package name), call it MainApplication.java. Copy the following code into it:
import android.support.annotation.NonNull;
import com.facebook.react.ReactPackage;
import com.reactnativenavigation.NavigationApplication;
import java.util.Collections;
import java.util.List;
public class MainApplication extends NavigationApplication {
@Override
public boolean isDebug() {
// Make sure you are using BuildConfig from your own application
return BuildConfig.DEBUG;
}
@NonNull
@Override
public List<ReactPackage> createAdditionalReactPackages() {
// Add the packages you require here.
// No need to add RnnPackage and MainReactPackage
return Collections.emptyList();
}
}
(TODO: add vanilla React version ?)
If you try and run your app now, you should see your title appear and empty space where we will put our JUCE Component.
Adding a Custom View
Next up we will create a custom React view for our JUCE Component to sit in.
First, we need to create a subclass of SimpleViewManager
:
And then, our ReactPackage
that tells React Native about all our custom views and modules:
Then, replace the following line in MainApplication.java
return Collections.emptyList();
with e.g.:
return Arrays.asList(
(ReactPackage) new MyAppReactPackage()
);
Now we can move on to Part 3.