# Troubleshooting

Here are common setup issues and fixes for Stream Chat React Native.
If you encounter something that is not listed here, try [searching for the issue in GitHub](https://github.com/GetStream/stream-chat-react-native/v8/issues).

## Cannot run remote debugger

React Native is [undergoing major architecture changes](https://github.com/react-native-community/discussions-and-proposals/issues/91), and Reanimated 2 relies on JSI. Due to [known limitations](https://docs.swmansion.com/react-native-reanimated/docs/#known-problems-and-limitations), remote debugging requires [Flipper](https://fbflipper.com/).

## Messages sending in thread instead of channel

There is an internal thread state that is used to track the current thread.
When you click on a thread and take an action such as navigating to another screen the `thread` is set within the `Channel` component in the current screen to the selected `thread`.
When you return to the original screen you need to reset the thread to ensure it is not being set on the messages when they are sent.
Track `thread` state yourself and pass it to any `Channel` component (channel or thread).

```tsx {2}
<Chat client={chatClient} i18nInstance={streami18n}>
  <Channel channel={channel} thread={thread}>
    <MessageList onThreadSelect={setThread} />
    <MessageInput />
  </Channel>
</Chat>
```

```tsx {2}
<Chat client={chatClient} i18nInstance={streami18n}>
  <Channel channel={channel} thread={thread} threadList>
    <Thread onThreadDismount={() => setThread(null)} />
  </Channel>
</Chat>
```

You may be wondering why this is all necessary, this is because in most cases there is only a single `OverlayProvider`.
The `OverlayProvider` keeps track of the currently selected images in the image picker, and the images available to view in the image gallery from a given channel or thread.
To keep these in sync with the currently visible screen some logic is used to determine whether or not they should update, this logic is dependant on the `thread` state.

## Undefined is not a function

Stream Chat for React Native relies heavily on context, in instances where the Render Error `undefined is not a function` occurs it is almost always the case that a context provider is not wrapping a component appropriately.
If you encounter this error please ensure the `OverlayProvider`, `Chat`, and `Channel` components are wrapping your application correctly.

## Incorrect input position

If `MessageInput` is positioned incorrectly when the keyboard shows, a setting is likely wrong.

Ensure the `keyboardVerticalOffset` prop passed to `Channel` is correct and accounts for any header height that may need to be adjusted for.

### Android

On Android if the StatusBar is set to translucent, that is `StatusBar.setTranslucent(true)`, there may be some inaccurate layout calculations occurring.

If you use `android:windowSoftInputMode="adjustResize"`, you can disable layout adjustments with `disableKeyboardCompatibleView` to ignore incorrect measurements.

## Wrong images in gallery

The image viewer is created in `OverlayProvider` to cover the screen. If you wrap navigation in `OverlayProvider`, there is only one instance across the app.
The viewer's images depend on the current `channel` and `thread` passed to `Channel`.
You therefore must keep these props up to date as you navigate to ensure when returning to a channel from a thread the images in the viewer are once again updated to those from the channel.

To do this make sure your `Channel` components are always aware of the thread state, even when being used for a channel.

```tsx
<Channel channel={channel} thread={thread}>
  <MessageList onThreadSelect={setThread} />
  <MessageInput />
</Channel>
```

```tsx
<Channel channel={channel} thread={thread} threadList>
  <Thread onThreadDismount={() => setThread(null)} />
</Channel>
```

## Image gallery not full screen

If the image viewer or message menu is not covering the full screen, for instance it is rendering below or behind a Header, it is likely the `OverlayProvider` is not setup in the correct location within the application.
See [Stream Chat with Navigation](/chat/docs/sdk/react-native/v8/basics/stream_chat_with_navigation/) for proper `OverlayProvider` placement.

## Image picker incorrect height

The image picker opens the gallery to a height based on the location of the `MessageInput`, if there is space below the `MessageInput`, for instance a Safe Area or a Tab Bar, this must be taken into account.
To account for this the prop `bottomInset` can be provided to the `Channel` component.

```tsx
<Channel bottomInset={/** number */}>{/* Inner component */}</Channel>
```

Similarly if the gallery fully open is not at the desired height, this can be adjusted using the `Channel` prop `topInset`.

```tsx
<Channel topInset={/** number */}>{/* Inner component */}</Channel>
```

### Android

On Android if the StatusBar is set to translucent, that is `StatusBar.setTranslucent(true)`, you should add the height adjustments using the `setTopInset` function of the `useAttachmentPickerContext` hook.

```tsx
import { StatusBar } from "react-native";
import { useHeaderHeight } from "@react-navigation/elements";
import { useAttachmentPickerContext } from "stream-chat-react-native";
const headerHeight = useHeaderHeight();
const { setTopInset } = useAttachmentPickerContext();

// When the status bar is translucent, headerHeight includes the status bar height and needs to be subtracted.
const finalHeaderHeight =
  headerHeight -
  (Platform.OS === "android" && StatusBar.currentHeight
    ? StatusBar.currentHeight
    : 0);

useEffect(() => {
  setTopInset(finalHeaderHeight);
}, [setTopInset, finalHeaderHeight]);
```

This can also be passed to the `topInset` prop of `OverlayProvider`.

It is important to note that since Expo 38 `true` is the default for transparent on Android.

## Camera roll not working

Make sure you have the package `@react-native-camera-roll/camera-roll`/`expo-media-library` installed and configured correctly.

### iOS

Add the `NSPhotoLibraryUsageDescription` key in your `Info.plist` with a description of use.<br/>
Add the `NSPhotoLibraryAddUsageDescription` key in your `Info.plist` with a description of use.

### Android

The following permissions are required for the image picker to work on Android, and must be included in the `AndroidManifest.xml` file.

```xml
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
                android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
```

<admonition type="note">

If your Android app targets SDK 33+, use React Native 0.71+ to request media permissions instead of storage permissions.

</admonition>

## Camera not working

### iOS

Add the `NSCameraUsageDescription` key in your `Info.plist` with a description of use.<br/>
Add the `NSPhotoLibraryUsageDescription` key in your `Info.plist` with a description of use.<br/>
Add the `NSMicrophoneUsageDescription` key in your `Info.plist` with a description of use.<br/>

### Android

The standard camera permissions are required for the camera to work on Android, and must be included in the `AndroidManifest.xml` file.

```xml
<uses-permission android:name="android.permission.CAMERA"/>
```

Add `maven { url 'https://maven.google.com' }` to `android/build.gradle`

```java {15}
allprojects {
  repositories {
    mavenLocal()
    maven {
      // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
      url("$rootDir/../node_modules/react-native/v8/android")
    }
    maven {
      // Android JSC is installed from npm
      url("$rootDir/../node_modules/jsc-android/dist")
    }

    google()
    maven { url 'https://maven.google.com' }
    maven { url 'https://www.jitpack.io' }
  }
}
```

Ensure `buildToolsVersion`, `compileSdkVersion`, and `targetSdkVersion` are all >= 26 in `android/build.gradle`

```java
buildscript {
  ext {
    buildToolsVersion = "29.0.3"
    compileSdkVersion = 29
    targetSdkVersion = 29
    ...
  }
  ...
}
```

Add `vectorDrawables.useSupportLibrary = true` to `android/app/build.gradle`

```java
android {
    defaultConfig {
        vectorDrawables.useSupportLibrary = true
        ...
    }
    ...
}
```

## External Link not opening

### Android

Opening an external link doesn't work without permissions on `targetSdkVersion` >= 30. So, the following permission must be included in the `AndroidManifest.xml` file.

```xml
<queries>
  <intent>
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="http" android:host="*" />
  </intent>
  <intent>
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="https" android:host="*" />
  </intent>
</queries>
```

## GIF and WebP not displaying

### Android

GIF and WebP do not work without additional modules by default on Android.

You will need to add some modules in your `android/app/build.gradle`

```java
dependencies {
  // For animated GIF support
  implementation 'com.facebook.fresco:animated-gif:2.6.0'

  // For WebP support, including animated WebP
  implementation 'com.facebook.fresco:animated-webp:2.6.0'
  implementation 'com.facebook.fresco:webpsupport:2.6.0'

  // Provide the Android support library (you might already have this or a similar dependency)
  implementation 'com.android.support:support-core-utils:24.2.1'
  ...
}
```

For latest versions of React Native(>=0.75.0), you will have to use the recent version of Fresco as well.

```java
// For animated GIF support
implementation('com.facebook.fresco:animated-gif:3.6.0')
// For WebP support, including animated WebP
implementation('com.facebook.fresco:animated-webp:3.6.0')
implementation('com.facebook.fresco:webpsupport:3.6.0')
```

## Blank screen when channel gets delete

When a channel is deleted, and if some user is active on that channel, a blank screen appears by default (as per the default logic) as we return `null` in this case.
It might be confusing for end user to see a blank screen and appropriate UX would be to navigate back to channel list screen.
You can implement such UX by listening to `channel.deleted` event on channel screen, and navigate to channel list screen with appropriate notification on app.

```tsx
client.on("channel.deleted", (event) => {
  if (event.cid === channel.cid) {
    // add your action here
  }
});
```

## `Touchables` not working

If you are having trouble with pressing, swiping, or otherwise interacting with components it is likely the result of [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/) not being properly setup.

<admonition type="note">

If your React Native version is (>=0.68) you will need a version of >=2.0.0 for `react-native-gesture-handler` for your app to build.

</admonition>

Be sure to follow all needed [additional steps](https://docs.swmansion.com/react-native-gesture-handler/docs/) to complete the installation.

This includes ensuring you import `react-native-gesture-handler` at the top of your app entry file.

```tsx
import "react-native-gesture-handler";
```

Also do not forget to wrap your component tree(probably above `OverlayProvider`) with `<GestureHandlerRootView>` or `gestureHandlerRootHOC` as mentioned in [RNGH Installation docs](https://docs.swmansion.com/react-native-gesture-handler/docs/installation#js). Not doing so, can cause unusual behaviour with the `Imagegallery` gestures.

```tsx
<GestureHandlerRootView style={{ flex: 1 }}>
  <OverlayProvider>{/* Other underlying components */}</OverlayProvider>
</GestureHandlerRootView>
```

If you are using v1 of `react-native-gesture-handler` library, then you will need extra setup steps for Android.
Update `MainActivity.java` to override the method for creating the `ReactRootView` as mentioned in [RNGH Installation docs](https://docs.swmansion.com/react-native-gesture-handler/docs/installation#js).

```java {4-6,13-21}
package com.swmansion.gesturehandler.react.example;

import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
public class MainActivity extends ReactActivity {

  @Override
  protected String getMainComponentName() {
    return "Example";
  }
  @Override
  protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName()) {
      @Override
      protected ReactRootView createRootView() {
        return new RNGestureHandlerEnabledRootView(MainActivity.this);
      }
    };
  }
}
```

## `Touchables` Inside Custom Components Are Not Working

If you find that a customization that includes a touchable is not working, or the wrong element is receiving the touch event, this is likely related to [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/).
The following could be the reasons behind the issues you might be facing:

- Your component may be being rendered inside of a gesture handler.
- Nested gesture handlers and `touchables` need special treatment and can act differently on iOS & Android. If you want Nested `Touchables` to work on Android you should add the prop `disallowInterruption` as mentioned [here](https://github.com/software-mansion/react-native-gesture-handler/issues/784#issuecomment-786471220).

There are solutions available for almost all use cases so looking into your specific use case in relation to React Native Gesture Handler is suggested.
The solution may require using a different touchable from React Native Gesture Handler and React Native depending on the platform.

## Reanimated 2

We rely on Reanimated 2 heavily for gesture based interactions and animations. It is vital you follow the [Reanimated 2 installation instructions](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/#installation/) for the library to work properly.

### Failed to create a Worklet

If you are encountering errors related to `Reanimated 2 failed to create a worklet` you must likely forgot to add the `react-native-reanimated/plugin` to your [project's babel config file](https://babeljs.io/docs/en/config-files). Below is an example of adding Reanimated babel plugin to `babel.config.js`:

```js
  module.exports = {
      ...
      plugins: [
          ...
          'react-native-reanimated/plugin',
      ],
  };
```

<admonition type="warning">

Reanimated plugin has to be listed last.

</admonition>

<admonition type="note">

If you are using `react-native-reanimated` version `>=4.0.0`, you can replace the `react-native-reanimated/plugin` with `react-native-worklets/plugin` in your application's `babel.config.js`.

```js {7}
module.exports = {
  presets: [
    ... // don't add it here :)
  ],
  plugins: [
    ...
    'react-native-worklets/plugin',
  ],
};
```

Read more [here](https://docs.swmansion.com/react-native-reanimated/docs/guides/migration-from-3.x#renamed-react-native-reanimatedplugin).

</admonition>

### Blank screen on Android

<admonition type="warning">

This step is only required for older version of (Reanimated 2). If you are using version >=3.0.0, you can skip this step.

</admonition>

If you are encountering errors on Android and the screen is blank it is likely you forgot to finish the [Reanimated 2 Android setup](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/#installation/#android).

Ensure Hermes is enabled in `android/app/build.gradle`:

```java
project.ext.react = [
  enableHermes: true  // clean and rebuild if changing
]
```

Add Reanimated in `MainApplication.java`

```java {1-2,13-16}
import com.facebook.react.bridge.JSIModulePackage;
import com.swmansion.reanimated.ReanimatedJSIModulePackage;
...

private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
  ...

  @Override
  protected String getJSMainModuleName() {
    return "index";
  }

  @Override
  protected JSIModulePackage getJSIModulePackage() {
    return new ReanimatedJSIModulePackage();
  }
};
...
```

### Android release bundle is crashing

This is likely due to ProGuard removing reanimated 2 related classes. If you're using ProGuard, make sure to add the rules below:

```text
-keep class com.swmansion.reanimated.** { *; }
-keep class com.facebook.react.turbomodule.** { *; }
```

## Project won't build on a MacBook with Apple M1

On newer MacBooks with the Apple M1 SoC, there are a few required steps that need to be followed for an app to build. The user [aiba](https://github.com/aiba) has written [a guide](https://github.com/aiba/react-native-m1) to make the necessary changes to your project.

The iOS build version can be changed to suit your needs, but keep in mind to change the version to the same major and minor version consistently throughout the guide.

## React Native Video failing with Xcode 17

Following error is a known issue with using Xcode 17 and react-native-video dependency.

```text
The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
```

To temporarily fix this, you can follow the solution as per mentioned in the description of [this issue on GitHub](https://github.com/react-native-video/react-native-video/issues/3154).

Alternatively, you can apply [this patch](https://github.com/react-native-video/react-native-video/files/11932302/react-native-video%2B6.0.0-alpha.6.patch) using the [patch-package](https://www.npmjs.com/package/patch-package) library to the package `react-native-video`.

<admonition type="note">

This shall be fixed soon with the new alpha release of the package `react-native-video`.

</admonition>

## Using `@op-engineering/op-sqlite` with `use_frameworks! :linkage => :static`

If you have enabled offline support and have installed `@op-engineering/op-sqlite` and are trying to use it with another library that requires it to be loaded as a static framework due to the line `use_frameworks! :linkage => :static` on the Podfile, you'll be receiving errors that resemble something like the following:

```bash title="Terminal"
error: Multiple commands produce '<some-path>/op-sqlite/op_sqlite.framework/Headers/libsql.h'
```

or

```bash title="Terminal"
...
^ include of non-modular header inside framework module
```

One such very popular package is `react-native-firebase`, however there are potentially others as well.

Provided below are ways to resolve the issue.

<tabs>

<tabs-item value="rncli" label="RN CLI">

Since you have control over how your `Podfile` looks like, you can follow [this docs from op-sqlite](https://ospfranco.notion.site/Installation-93044890aa3d4d14b6c525ba4ba8686f#acdc7a6723234f8b8440acb1ae0e7d87) in order to resolve the issue.

Adding the following snippet inside the `target 'Appname'` section of `Podfile`, like so:

```ruby
pre_install do |installer|
  installer.pod_targets.each do |pod|
    if pod.name.eql?('op-sqlite')
      def pod.build_type
        Pod::BuildType.static_library
      end
    end
  end
end
```

should do the trick.

</tabs-item>

<tabs-item value="expo" label="Expo">

For `Expo`, solving the issue out of the box is a little bit more complicated. If you're using a `Bare/EAS Workflow`, please refer to the React Native CLI variant of the fix as that should be possible in that instance.

Otherwise, we need to analyze the issue a bit deeper before we can draw any conclusions. The domain of the problem lies in exactly what you described as outlined in both the [`op-sqlite`](https://op-engineering.github.io/op-sqlite/docs/installation#use_frameworks) as well as [RN Firebase libraries](https://rnfirebase.io/#configure-react-native-firebase-modules). We essentially need a way to let the `pod` installation process know that we do not want this particular set of rules (in this case `use_frameworks! :linkage => :static`) be applied to `op-sqlite` specifically, as it breaks.

For this purpose, we can create a custom `config-plugin` that does the heavy lifting, like so:

```javascript
// customPlugin.js
const fs = require("fs");
const {
  withPlugins,
  createRunOncePlugin,
  withDangerousMod,
  withPodfileProperties,
  IOSConfig,
} = require("@expo/config-plugins");

const modifyPodfile = (podfilePath) => {
  const preInstallBlock = `
  pre_install do |installer|
    installer.pod_targets.each do |pod|
      if pod.name.eql?('op-sqlite')
        def pod.build_type
          Pod::BuildType.static_library
        end
      end
    end
  end
`;

  if (fs.existsSync(podfilePath)) {
    let podfileContent = fs.readFileSync(podfilePath, "utf-8");

    // Look for the `use_frameworks!` line
    const useFrameworksLine =
      "use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']";

    if (podfileContent.includes(useFrameworksLine)) {
      // Insert the pre_install block just before the use_frameworks line
      const modifiedContent = podfileContent.replace(
        useFrameworksLine,
        preInstallBlock + "\n" + useFrameworksLine,
      );

      // Write the modified content back to the Podfile
      fs.writeFileSync(podfilePath, modifiedContent);
      console.log(
        "CUSTOM MODIFY PODFILE PLUGIN: Podfile modified to include pre_install block.",
      );
    } else {
      console.log(
        "CUSTOM MODIFY PODFILE PLUGIN: use_frameworks line not found in Podfile.",
      );
    }
  } else {
    console.log("CUSTOM MODIFY PODFILE PLUGIN: Podfile not found.");
  }
};

// we create a mod plugin here
const withModifyPodfile = (config) => {
  return withDangerousMod(config, [
    "ios",
    async (config) => {
      const path = IOSConfig.Paths.getPodfilePath(
        config.modRequest.projectRoot,
      );
      modifyPodfile(path);
      return config;
    },
  ]);
};

// this config plugin is only needed if we have expo-updates installed, reference: https://op-engineering.github.io/op-sqlite/docs/installation/#expo-updates
const withUseThirdPartySQLitePod = (expoConfig) => {
  return withPodfileProperties(expoConfig, (config) => {
    config.modResults = {
      ...config.modResults,
      "expo.updates.useThirdPartySQLitePod": "true",
    };
    return config;
  });
};

const withStreamOfflineMode = (config) => {
  return withPlugins(config, [
    withModifyPodfile,
    // only add this is expo-updates is installed
    withUseThirdPartySQLitePod,
  ]);
};

module.exports = createRunOncePlugin(
  withStreamOfflineMode,
  "custom-modify-podfile-plugin",
  "0.0.1",
);
```

which we can then add to our `app.json` like so:

```js
{
  "expo": {
    // ... rest of the app.json here
    "plugins": [
     // ... other plugins here
      "./customPlugin.js"
    ]
  }
}
```

It's important to make sure that the plugin is the last in the list, as plugin execution is done in a pipeline fashion and we want all other changes to have been processed before we proceed with ours.

Having this logic as a separate plugin means that it will be executed in the `prebuild` phase and it should come as a given that all changes should be processed before `pod install` starts internally and so the updated build rules applied in our generated `Podfile` during `eas build`.

It should also work out of the box for local builds (`expo run:ios` for example) as the same logic applies. Given the logs, the output should look something like this:

```bash title="Terminal"
✔ Created native directory
✔ Updated package.json | no changes
CUSTOM MODIFY PODFILE PLUGIN: Podfile modified to include pre_install block.
✔ Finished prebuild
✔ Installed CocoaPods
› Skipping dev server
› Planning build
...
```

<admonition type="info">

Please note that the custom plugin example is just a representation of what the final plugin would look like.

It is not intended or recommended that it is copied word for word and we strongly encourage integrators to modify it themselves for their particular use-case and `Podfile`.

</admonition>

Please feel free to refer to [this Github issue](https://github.com/GetStream/stream-chat-react-native/v8/issues/2926) if you need more context.

</tabs-item>

</tabs>

## `KeyboardCompatibleView` not pushing content out properly on Android 15

In versions `0.77.0` and onwards of React Native, an issue might occasionally occur where our `KeyboardCompatibleView` does not push the content out properly whenever the keyboard is opened on `Android` devices for `Android 15` and `targetSdkVersion = 35`.

Even thought this does not happen on all devices (and in all apps), the issue itself is pretty breaking for the chat use-case.

The reason why this issue appears is because of [these changes](https://developer.android.com/about/versions/15/behavior-changes-15#ux).

To fix it, you will need to do some minor adjustments to your `android/MainActivity.kt` file.

<tabs>

<tabs-item value="rncli" label="RN CLI">

For RN CLI, the easiest way to fix this is to add a custom inset listener within the `onCreate` method override of your `MainActivity`.

That would look something like this (assuming you have the default `MainActivity.kt` needed by React Native):

```kotlin {8-13,17-39}
package com.yourapp

import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate

import android.os.Bundle
import android.os.Build
import android.view.View
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding

class MainActivity : ReactActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(null)

        if (Build.VERSION.SDK_INT >= 35) {
            val rootView = findViewById<View>(android.R.id.content)


          ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, insets ->
              val bars = insets.getInsets(
                  WindowInsetsCompat.Type.systemBars()
                          or WindowInsetsCompat.Type.displayCutout()
                          or WindowInsetsCompat.Type.ime() // adding the ime's height
              )
              rootView.updatePadding(
                   left = bars.left,
                   top = bars.top,
                   right = bars.right,
                   bottom = bars.bottom
               )
              WindowInsetsCompat.CONSUMED
          }
        }
      }
    /**
     * Returns the name of the main component registered from JavaScript. This is used to schedule
     * rendering of the component.
     */
    override fun getMainComponentName(): String = "YourApp"

    /**
     * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
     * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
     */
    override fun createReactActivityDelegate(): ReactActivityDelegate =
        DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
}

```

This will essentially make sure that whenever the window insets change, you're listening to those changes and adjusting accordingly.

If you're already overriding the `onCreate` method for different reasons, you will simply need to make sure that this code is invoked after `super.onCreate(null)`.

</tabs-item>

<tabs-item value="expo" label="Expo">

For `Expo`, solving the issue out of the box is a little bit more complicated. If you're using a `Bare/EAS Workflow`, please refer to the React Native CLI variant of the fix as that should be possible in that instance.

If you're using `Expo Go` however, you will need to create a custom `config-plugin` that will do this for you. That would look something like this:

```js
// plugins/keyboardInsetMainActivityListener.js
const {
  withMainActivity,
  createRunOncePlugin,
} = require("@expo/config-plugins");

const requiredImports = [
  "import android.os.Build",
  "import android.os.Bundle",
  "import android.view.View",
  "import androidx.core.view.ViewCompat",
  "import androidx.core.view.WindowInsetsCompat",
  "import androidx.core.view.updatePadding",
];

const customInsetHandler = `
    if (Build.VERSION.SDK_INT >= 35) {
        val rootView = findViewById<View>(android.R.id.content)

        ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, insets ->
            val bars = insets.getInsets(
                WindowInsetsCompat.Type.systemBars()
                        or WindowInsetsCompat.Type.displayCutout()
                        or WindowInsetsCompat.Type.ime()
            )
            rootView.updatePadding(
                left = bars.left,
                top = bars.top,
                right = bars.right,
                bottom = bars.bottom
            )
            WindowInsetsCompat.CONSUMED
        }
    }
`;

const withCustomMainActivity = (config) => {
  return withMainActivity(config, (mod) => {
    if (mod.modResults.language !== "kt") {
      throw new Error(
        "MainActivity must be written in Kotlin for this plugin.",
      );
    }

    let contents = mod.modResults.contents;

    // Add missing imports
    const packageLineMatch = contents.match(/^package\s+[^\n]+\n/);
    if (packageLineMatch) {
      const packageLine = packageLineMatch[0];
      for (const imp of requiredImports) {
        if (!contents.includes(imp)) {
          contents = contents.replace(packageLine, packageLine + imp + "\n");
        }
      }
    }

    // Inject inside onCreate(), right after super.onCreate(null)
    // Match the full onCreate method
    const onCreateMethodRegex =
      /override fun onCreate\(savedInstanceState: Bundle\?\) \{([\s\S]*?)^\s*}/m;

    // If the method exists and doesn't already contain a custom ViewCompat.setOnApplyWindowInsetsListener, inject it
    if (
      onCreateMethodRegex.test(contents) &&
      !contents.includes("ViewCompat.setOnApplyWindowInsetsListener")
    ) {
      contents = contents.replace(onCreateMethodRegex, (match, body) => {
        // Inject after super.onCreate(null)
        const modifiedBody = body.replace(
          /super\.onCreate\(null\);?/,
          (superLine) => `${superLine}\n${customInsetHandler}`,
        );

        return `override fun onCreate(savedInstanceState: Bundle?) {\n${modifiedBody}\n}`;
      });
    }

    mod.modResults.contents = contents;
    return mod;
  });
};

module.exports = createRunOncePlugin(
  withCustomMainActivity,
  "keyboard-inset-main-activity-listener-plugin",
  "0.0.1",
);
```

and then add it to your `app.json` file like so:

```js
{
  "expo": {
    // ... rest of the app.json here
    "plugins": [
     // ... other plugins here
      "./plugins/keyboardInsetMainActivityListener.js"
    ]
  }
}
```

What this plugin does is the following:

- It adds the missing imports right after your `package.<your-app-name>` directive (only if they are not present)
- If `onCreate` already exists as an override, it will add the listener code right after the `super.onCreate(null)` invocation
  - It will also make sure that if there is a listener present, it will not add the listener at all (in that case you can tweak it to your needs, or wherever it makes sense to add it)
- If `onCreate` does not exist as an override, it will add it as the first one within your `MainActivity`

<admonition type="info">

Please note that the custom plugin example is just a representation of what the final plugin would look like.

While it should cover the generic `MainActivity.kt` file that is generated by default through `Expo Go`, it might not work in a general use-case where you're already doing modifications through other plugins and it might need to be tweaked.

However, please feel free to use it as a good starting point as we encourage you to modify it to fit your particular integration better.

</admonition>

</tabs-item>

</tabs>

Finally, it is also highly recommended that if you're using the [`keyboardVerticalOffset` prop](/chat/docs/sdk/react-native/v8/core-components/channel/#keyboardverticaloffset), it is the same and consistent across every place where it's passed.

For any additional or more specific fixes, please refer to the well documented issue on the [React Native upstream repo](https://github.com/facebook/react-native/v8/issues/49759) or to [this PR in our SDK repo](https://github.com/GetStream/stream-chat-react-native/v8/pull/3134).

## Notifee not building when using Expo

If you are using Notifee and you are running into an issue where the Notifee SDK is not working as expected on Android, you might run into an issue where the Notifee SDK is not working as expected on Android because the maven repo is missing/not linked to the project.

To fix this, you will need to add the following to your `app.json` file:

```json
[
  "expo-build-properties",
  {
    "android": {
      "extraMavenRepos": [
        "$rootDir/../../../node_modules/@notifee/react-native/v8/android/libs"
      ]
    }
  }
]
```

## React Native Firebase not working on Expo 54

If you are using Expo 54 and React Native Firebase, you might run into an issue where the Firebase SDK is not working as expected on iOS.

The fix is to add the following to your `app.json` file:

```json
[
  "expo-build-properties",
  {
    "ios": {
      "useFrameworks": "static",
      "forceStaticLinking": ["RNFBApp", "RNFBMessaging"]
    }
  }
]
```

Make sure you have `expo-build-properties` installed in your app.


---

This page was last updated at 2026-04-17T17:33:45.454Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v8/basics/troubleshooting/](https://getstream.io/chat/docs/sdk/react-native/v8/basics/troubleshooting/).