DEV Community

Cover image for Adding Notch support to your React Native apps: Android, iOS & Web
Bruno Lemos
Bruno Lemos

Posted on

Adding Notch support to your React Native apps: Android, iOS & Web

In this tutorial we'll learn how to properly support notches (aka “display cutout”) on Android, iOS and Web with just a few lines of code.

Here's our Android Emulator showing a Double cutout:

Android Emulator with Notches at the top and bottom

If you don't have an Android device with Notch, open an Android Emulator and emulate the display cutout by going to Android Settings > System > Advanced > Developer options > Display cutout > Double cutout

You can see in the screenshot above that the wallpaper shows behind the notch. That is the correct behavior and your app should do it too.

But let's see what happens when we render a simple app:

black-bars-around-app

By default, the app does not handle the notches. You can see in the image above that it rendered two black bars, making the screen feel smaller to the user. That is not good, let's fix that.

Here the fun starts. After researching and trying different methods for hours, I found out this is what you need to add to your MainActivity.java:

public class MainActivity extends ReactActivity {

+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+            layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+            getWindow().setAttributes(layoutParams);
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+        }
+
+        super.onCreate(savedInstanceState);
+    }
Enter fullscreen mode Exit fullscreen mode

This code does three things: set layoutInDisplayCutoutMode to edgeInsets to stop showing the black bars, and set both status and navigation to translucent to render our app behind the notch and navigation buttons.

Here's the result after adding this code:

content-render-behing-notch

Yes! That is an improvement. Now we use the whole screen. But you can see the text content is being hidden by the notches.

React Native has a built-in component called SafeAreaView. It fixes this exact issue, but... only on iPhone X. It still doesn't have Android support.

Thanks to @janicduplessis, we can use react-native-safe-area-context, that supports all platforms we want: iOS, Android and Web!

If you use Expo, this lib will be included on SDK v35

If you use react-native < 0.60, you can apply this patch using patch-package

The api looks like this:

const safeAreaInsets = useSafeArea()
Enter fullscreen mode Exit fullscreen mode

And we add the paddings to the View:

<View
  style={{
    flex: 1,
    paddingTop: safeAreaInsets.top,
    paddingBottom: safeAreaInsets.bottom,
    paddingLeft: safeAreaInsets.left,
    paddingRight: safeAreaInsets.right,
  }}
>
Enter fullscreen mode Exit fullscreen mode

And here's the final result:

text-rendered-correctly

It works perfectly 🎉🎉🎉
Android is ready, now let's see how our iOS app is looking:

iPhone X rendering correctly

iOS is already perfect as well! 🎉
That's 2 out of 3. How about web? Let's see:

screenshot-mobile-safari-black-bars

Hum, web is still showing the black bars.

If your app doesn’t support web yet, check out my other tutorial: How to share code between iOS, Android & Web using React Native, react-native-web and monorepo

But that is easy to fix, you just need to add viewport-fit=cover to your viewport meta tag:

-<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
Enter fullscreen mode Exit fullscreen mode

screenshot-mobile-safari-showing-correctly

And voilà! Our app now properly supports notches on iOS, Android and Web! And again, it was this easy thanks to the awesome react-native-safe-area-context.

Here's the gist with the code above, the tweet in case you want to retweet and my Twitter account: @brunolemos 💚

Thanks for reading!

Top comments (12)

Collapse
 
ph0ph0 profile image
ph0ph0 • Edited

Thanks very much for the tutorial Bruno!

Please can you provide some more information on the part where you add the onCreate() method to the MainActivity class? I have added the code that you suggested and it is riddled with errors.

I have an error on the Override statement "Method does not override method from its superclass".

An error on onCreate(Bundle...) "Method onCreate(Bundle) is never used"

And an error on super.onCreate(savedInstanceState) "onCreate(android.os.Bundle) in ReactActivity cannot be applied to Bundle"

I'm not an android dev, so finding it difficult to debug this myself. Thanks a lot!

Collapse
 
moox profile image
Max Thirouin

We may need to import import android.view.WindowManager; in MainActivity.java

Collapse
 
amanhimself profile image
Aman Mittal

Thank you so much for sharing a cross-platform solution for this problem. I was wondering the SafeArea insets would have to be added to every screen in the app (assuming that app contains multiple screens). Is there anyway to do provide a context or something and wrap around the main component such that all other screen components do not have to be defined explicitly?

Also, does the package react-native-safe-area-context works with Expo apps?

Collapse
 
amanhimself profile image
Aman Mittal

Nevermind, I found this: github.com/react-native-community/... :)

Collapse
 
devsumanaditya profile image
Aditya Suman • Edited

Thanks a lot for this <3

android/app/src/main/java/{...}/MainActivity.java
MainActivity.java =>

Edit* => I have removed these two lines -
// getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
// getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);

and used "react-native-immersive" to hide statusbar & navigation bar. (i.e. immersive mode)
Link to react-native-immersive - npmjs.com/package/react-native-imm...

/* MainActivity Code below */

package com.testapp;
import com.facebook.react.ReactActivity;
import android.view.WindowManager;
import android.os.Build;
import android.os.Bundle;

public class MainActivity extends ReactActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(layoutParams);
}
super.onCreate(savedInstanceState);
}

@Override
protected String getMainComponentName() {
return "testapp";
}

}

Collapse
 
mbakhtiyor profile image
MBakhtiyor

We may need to import:

import android.view.WindowManager;
import android.os.Build;
import android.os.Bundle;

in MainActivity.java

Collapse
 
martipello profile image
martipello

So Chris banes created a library to do this called inserter also flutter handles this by wrapping layouts in something called a safe area, the more you know 😉

Collapse
 
orteidni profile image
orteidni

Hello! Thanks for the great article. How can I get this working with the Modal component? When using a Modal, the notch space is filled again like by default.

Collapse
 
chaudhary919 profile image
ԷerresגcԷ

greatt.. thanks very much Brunoo

Collapse
 
jdnichollsc profile image
J.D Nicholls

Thanks for this awesome article!

Collapse
 
rohankm profile image
roo12312

KeyboardAvoidView is glitchy when the above code is used.. any solution?