Detecting Android soft navigation keys in React Native

Elliot Hesp
4 min readOct 17, 2017

Android devices rarely come with hard bottom navigation keys these days. Whilst you don’t really need to worry about them (by default they’re just black), it is possible to integrate them into the theme of your app, creating a truly immersive experience.

The app below uses react-native-material-bottom-navigation to create the Bottom Navigation tabs in our Android app with soft navigation keys:

Following the steps in “Behind the navigation bar”, we can see how to ‘remove’ (make transparent/translucent) the bottom navigation bar from our app:

Step 1. In order to make the System Navigation translucent, you have to add this to android/app/src/main/res/values/styles.xml:

<!-- Customize your theme here. --><item name="android:navigationBarColor">
@android:color/transparent
</item>
<item name="android:windowTranslucentNavigation">
true
</item>

Step 2. The System Navigation has a height of 48dp. The Bottom Navigation should be 56dp tall. This makes a total height of 104. Use innerStyle to push the tabs above the System Navigation without pushing the whole Bottom Navigation above it.

<BottomNavigation
style={{ height: 104 }}
innerStyle={{ paddingBottom: 48 }}
>

Step 3. You’re done!

What if my device has hard navigation keys?!

In this case, you’re going to be creating a height and applying padding to a space which doesn’t actually exist, leaving a 48hp high block bar below your navigation. The simple solution is to only apply the styles described in step 2 above if the device has soft keys.

The only real way to detect this is hooking to the Android SDK. This is actually super easy to do with React Native. We’re going to provide some constants back which JavaScript can read.

Creating the native module

In Android Studio, go ahead and open the following file:android/app/src/main/java/com/<< yourappname >>/MainApplication.java

Within our getPackages method, we’ll export a new package (which is automatically imported as we’re in the same directory where we’ll create it):

@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new MyAppPackage()

Next create the package in: android/app/src/main/java/com/<< yourappname >>/MyAppPackage.java

Our package will export a module with the current React context (which allows us to hook into React Native stuff such as the current Android activity):

package com.yourappname;

import com.facebook.react.ReactPackage;
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 MyAppPackage implements ReactPackage {

public MyAppPackage() {
}

@Override
public List<NativeModule>
createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<NativeModule>();
modules.add(new MyAppModule(reactContext));
return modules;
}

@Override
public List<ViewManager>
createViewManagers(ReactApplicationContext reactApplicationContext) {
return Collections.emptyList();
}
}

MyAppModule is another java file which is automatically read from the current working directory. Our own package is now just exporting it for use.

Now go ahead and create the MyAppModule file: android/app/src/main/java/com/<< yourappname >>/MyAppModule.java

This file is the one React Native will pick up when we access our module from the JavaScript world. We can provide a getConstants method which returns a static type which can be read. In our case, we simply want a true/false value on whether the device has soft keys.

MyAppModule.java

First up we need to extend the a base class to provide us with the ability to create a module:

package com.yourappname;import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
public class MyAppModule extends ReactContextBaseJavaModule {

We need to now call the “constructor” (same as you would in a React class) and pass the React context to the ReactContextBaseJavaModule class using super:

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

The next step provides React Native to access the module by name via NativeModules in JavaScript land. Lets go ahead and name our module:

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

We can now create our getConstants method, which will return whether we’ve got soft keys or not:

@Override
public Map<String, Object> getConstants() {
Map<String, Object> constants = new HashMap<>();

constants.put("has_soft_keys", hasSoftKeys());

return constants;
}

Here we return a HashMap. Think of it as an Object with key/value pairs. Our key is has_soft_keys and the value is the result of calling the function hasSoftKeys which we’ll create now:

private boolean hasSoftKeys() {
boolean hasSoftwareKeys;

Activity activity = getCurrentActivity();

if (activity == null) {
return true;
}

WindowManager window = getCurrentActivity().getWindowManager();

if(window != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){
Display d = getCurrentActivity().getWindowManager().getDefaultDisplay();

DisplayMetrics realDisplayMetrics = new DisplayMetrics();
d.getRealMetrics(realDisplayMetrics);

int realHeight = realDisplayMetrics.heightPixels;
int realWidth = realDisplayMetrics.widthPixels;

DisplayMetrics displayMetrics = new DisplayMetrics();
d.getMetrics(displayMetrics);

int displayHeight = displayMetrics.heightPixels;
int displayWidth = displayMetrics.widthPixels;

hasSoftwareKeys = (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0;
} else {
boolean hasMenuKey = ViewConfiguration.get(mContext).hasPermanentMenuKey();
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);

hasSoftwareKeys = !hasMenuKey && !hasBackKey;
}

return hasSoftwareKeys;
}

Don’t worry too much about what’s happening here-it’s just detecting whether we’re able to access certain APIs depending on the Android version and checks whether the soft keys are there using various checks.

Don’t forget to import the required classes at the top:

import java.util.HashMap;
import java.util.Map;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.ViewConfiguration;
import android.view.WindowManager;

Accessing our native module

Once you’ve recompiled your app, React Native provides access to our native modules using the NativeModules API. Lets go ahead and grab out the constants we’ve just created, and conditionally apply these to our component styles:

import { NativeModules } from 'react-native';
const hasSoftKeys = !!NativeModules.MyAppModule.has_soft_keys;
....<BottomNavigation
style={{ hasSoftKeys ? { height: 104 } : null }}
innerStyle={{ hasSoftKeys ? { paddingBottom: 48 } : null }}
>

Sorted! We how will only apply the height and padding if there is soft keys on the device, otherwise it’ll sit the BottomNavigation directly at the bottom of the device screen.

--

--