Determine whether hardware buttons are drawn on screen in React Native

It appears to be easy using Java to determine whether an Android device has a physical button, so to access that in React Native I would make a Native Module wrapper to return the result of ViewConfiguration.get(context).hasPermanentMenuKey().

You can open the /android directory of your React Native project in Android Studio, then within the /app/src/main/java/<com.companyname.appname>/ folder, at the same level where you see MainActivity and MainApplication files, create a new Android resource directory called detecthardware (or any other appropriate name). Within that create DetectHardwareModule.java and include this:

package com.companyname.appname.detecthardware;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactMethod;

import java.util.List;

public class DetectHardwareModule extends ReactContextBaseJavaModule {

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

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

    @ReactMethod
    public void hasHardwareButtons(final Callback callback) {
        callback.invoke(ViewConfiguration.get(getReactApplicationContext()).hasPermanentMenuKey());
    }
}

Then create DetectHardwarePackage.java and include this:

package com.companyname.appname.detecthardware;

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 DetectHardwarePackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new DetectHardwareModule(reactContext));
        return modules;
    }

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

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

Inside your MainApplication.java you'll need to include a getPackages method so the JavaScript code can access it:

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

Now within your React Native component, make sure you import NativeModules from react-native, as well as Platform so that you only run the function when on Android. In componentDidMount:

componentDidMount() {
    if (Platform.OS === 'android') {
        NativeModules.DetectHardware.hasHardwareButtons(function(result) {
            this.setState({hasHardwareButtons: result})
        }.bind(this));
    }
}

And finally within your render function, calculate the bottom amount to be 0 if the platform is android and hasHardwareButtons is true, otherwise 45:

var bottom = (Platform.OS === 'android' && this.state.hasHardwareButtons) ? 0 : 45

I use RN Dimensions to do this:

import {Dimensions} from 'react-native';

let deviceHeight = Dimensions.get('screen').height;
let windowHeight = Dimensions.get('window').height;
let bottomNavBarHeight = deviceHeight - windowHeight;
if (bottomNavBarHeight > 0) {
    // onscreen navbar
} else {
    // not onscreen navbar
}