React Native and JUCE Part 2: Android

Adam Wilson
4 min readNov 4, 2016

--

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.

--

--