A Step By Step Guide To Writing Your First Expo Config Plugin
Tags: reactnative, javascript, splashscreen, react
Published: 2021-12-16 21:01:25

Frustrated when your managed Expo project misbehaves and you have no option to customize it? Well, that is a thing of the past now. This tutorial will teach you how to start with Expo [config plugins](https://docs.expo.dev/guides/config-plugins/) which allow you to add custom native Android and iOS configurations without ejecting the managed workflow. We will use a practical example by debugging and fixing Android's status bar translucent behavior to allow our splash screen reach all device edges.

## INITIAL SETUP

To initiate our project you can simply clone [this repository](https://github.com/AronBe/article-first-config-plugin) that has all demonstrative assets included. Or you can start yourself by running expo-cli:

```sh
expo init first-config-plugin -t expo-template-blank-typescript
```

Then add [expo-splash-screen](https://docs.expo.dev/versions/latest/sdk/splash-screen/) as your dependency..

```sh
expo install expo-splash-screen
```

..and necessary assets to reproduce the issue. We are using typescript for better development experience, which you will definitely find it helpful writing the plugins.

## THE ISSUE

We want our application to look cool and therefore we show a full screen image on the intro page. For that we need to set the status bar to be `translucent` so that image can stretch under it.

```jsx
<StatusBar style="dark" translucent backgroundColor="transparent" />
```

The issue is that the status bar is not translucent from the start, resulting in our splash screen and logo shifting up once the translucency takes effect on the app mount. Notice also the color shift of the status bar.

<figure>
<img  alt='non translucent status bar demo' src='https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6mrf5cam5e299me97kng.gif' width="320" height="568"/>
<figcaption>Click for better resolution</figcaption>
</figure>

Luckily Expo allows us to configure the Android status bar through [app.json](https://docs.expo.dev/versions/latest/config/app/#androidstatusbar) which will set the default behavior before the app mounts. Lets try to replicate our settings:

```json
  "androidStatusBar": {
      "backgroundColor": "#00000000",
      "barStyle": "dark-content",
      "translucent": true
    }
```

The result is not exactly what we wanted. While we managed to make the color of status bar dark from the app launch and logo is not shifting up anymore as translucency is taking effect, the bar itself has that ugly overlay.

<figure>
<img src='https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f0at5teccqdr7lsl2xon.gif' width='320' height="568" alt='translucent status bar demo'/>
<figcaption>Click for better resolution</figcaption>
</figure>

We need to investigate what is going on. By running `expo run:android` or `expo prebuild -p android` we can generate the Android folder with its configurations. If you don't know where the issue is coming from, you can use a simple strategy to see where changes are being made. Just stage all generated Android files (`git add -A`), remove `translucent: true` from app.json, and run `expo prebuild -p android` to generate native files again.

By removing the translucent property you should see following unstaged changes:

**android/app/src/main/res/values/strings.xml**

```xml
- <string name="expo_splash_screen_status_bar_translucent" translatable="false">true</string>
+ <string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
```

**android/app/src/main/res/values/styles.xml**

```xml
- <item name="android:windowTranslucentStatus">true</item>
```

This already looks promising as the `expo_splash_screen_status_bar_translucent` custom Expo property sounds exactly like something we are trying to influence and being previously `true` we saw the status bar was really translucent during the splash screen. We also notice that `android:windowTranslucentStatus` was set to `true` which with some googling we realize was responsible for the status bar overlay. If you are in managed workflow, in the past you would feel trapped because you can't have one without the other to reach the behavior you wanted and the only option was to raise it in Expo [issues](https://github.com/expo/expo/issues/11604#issuecomment-964953312). But now thanks to Expo config plugins you can take the solution in your hands.

## THE SOLUTION

The solution is to make `expo_splash_screen_status_bar_translucent` set to `true` while keeping `android:windowTranslucentStatus` as `false` - which is the default therefore lets change only the Expo property. With `expo run:android` you can actually change it in your Android native files yourself to prove the effect after a new build. Yet if we want to stay in managed workflow and away from native file changes, we need to write a custom plugin which will do the change for us during the prebuild.

> This means any native changes through plugins will be reflected in Expo development environment only using [expo-dev-client](https://docs.expo.dev/development/getting-started/) - our custom Expo Go.

The `@expo/config-plugins` package is already part of Expo, so we don't need to install any new dependency. We will start by creating our plugin file in typescript, which is a recommended approach and can be useful not only for more advanced changes.

Lets create our initial plugin file `withAndroidSplashScreen.ts` in the root folder:

```ts
import type { ConfigPlugin } from '@expo/config-plugins'
import { withStringsXml } from '@expo/config-plugins'

const withAndroidSplashScreen: ConfigPlugin = (expoConfig) =>
  withStringsXml(expoConfig, (modConfig) => {
    return modConfig
  })

export default withAndroidSplashScreen
```

And start compiling it into javascript:

```sh
yarn tsc withAndroidSplashScreen.ts --watch --skipLibCheck
```

Finally, import resulting `withAndroidSplashScreen.js` file into app.json `plugins` property for Expo to process it on a next build. Our changes look like this:

```json
{
  "expo": {
    ...otherProps,
    "androidStatusBar": {
      "backgroundColor": "#00000000",
      "barStyle": "dark-content"
    },
    "plugins": ["./withAndroidSplashScreen.js"]
  }
}
```

Now you can run `expo prebuild -p android` to see effects of your plugin. Obviously, if you inspect our `withAndroidSplashScreen` code it is not changing anything yet. It just returns whatever it receives. Our plugin is a simple function.

Initially our plugin receives `expoConfig` which is basically content of `app.json` and this object is passed to the `withStringXml` mod. This particular mod (modifier) from Expo enables us to read contents of **android/app/src/main/res/values/strings.xml** and change them based on what config we return (all available mods can be found [here](https://docs.expo.dev/guides/config-plugins/#mod-plugins)). For each mod its content can be read from `modConfig.modResults` - you can actually use `console.log(JSON.stringify(config.modResults, null, 2));` to inspect the values during the `prebuild` command. To apply our desired changes we need to modify `modResults`.

```ts
import type { ConfigPlugin } from '@expo/config-plugins'
import { AndroidConfig, withStringsXml } from '@expo/config-plugins'

const withAndroidSplashScreen: ConfigPlugin = (expoConfig) =>
  withStringsXml(expoConfig, (modConfig) => {
    modConfig.modResults = AndroidConfig.Strings.setStringItem(
      [
        {
          _: 'true',
          $: {
            name: 'expo_splash_screen_status_bar_translucent',
            translatable: 'false'
          }
        }
      ],
      modConfig.modResults
    )
    return modConfig
  })

export default withAndroidSplashScreen
```

As you see, we assign to `modResults` what is returned from AndroidConfig helper method `setStringItem` which accepts the value we want to add and then remaining file strings already existing. Inspecting type of `setStringItem` and typescript in general should help you fill all needed properties correctly. After running `prebuild` we should see a new configuration string:

```xml
+ <string name="expo_splash_screen_status_bar_translucent" translatable="false">true</string>
```

We now have our desired splash screen behavior with a translucent status bar already from the app start and without the ugly overlay.

## SUMMARY

Hopefully this tutorial helped you to understand better the power of config plugins and that customizing your Expo project is actually not that difficult - you can see final solution in [this branch](https://github.com/AronBe/article-first-config-plugin/tree/solution). If you ask what to do with the native Android folder now when you are finished with debugging, you can just delete it together with all generated files. Important is to commit your new plugin file and changes in app.json. The `prebuild` command is a part of EAS build so next time you build your project, you can be sure your plugin will take effect the same way you did it locally.