Invalid parameters

[Problem/Question]

I’m implementing the basic logic for the UIKit into a new (within the last 6 months) expo app.

Following the guide and the sample react native app, I have created a layout which contains the sendbird wrapper, and uses the platformServices as outlined for expo in the documentation.

Within this is a stack navigator that houses the login, list fragment, channel create fragment and channel fragment.

So far, only using the app_id I have been able to sign in and find that currentUser updates correctly, I navigate to the GroupChannelListFragment, and receive an error ‘Invalid Parameters’. Should I comment out the jsx for GroupChannelListFragment (and still initialise it) the page will render without the error.

Below is the code and dependancy versions, any help is greatly appreciated. Thanks!

[Versions]
Core Sendbird Dependencies

Platform Services Dependencies

  • expo-av: ~15.1.7

  • expo-clipboard: ~7.1.5

  • expo-document-picker: ~13.1.6

  • expo-file-system: ~18.1.11

  • expo-image-manipulator: ~13.1.7

  • expo-image-picker: ~16.1.4

  • expo-media-library: ~17.1.7

  • expo-notifications: ^0.31.4

  • expo-video-thumbnails: ~9.1.3

Storage

  • react-native-mmkv: ^3.3.3

React Native & Expo

  • react-native: 0.79.6

  • expo: ^53.0.11

  • expo-router: ~5.1.0

[Snippets]

_layout.tsx

import { SendbirdUIKitContainer, useSendbirdChat } from '@sendbird/uikit-react-native';
import { router, Stack, useRouter } from 'expo-router';
import React, { useEffect } from 'react';
import { MMKV } from 'react-native-mmkv';
import { APP_ID } from '../../env';
import platformServices, { setSendbirdSDK } from '../../util/sendbird';


export const mmkv = new MMKV();



export default function ChatLayout() {

  return (
    <SendbirdUIKitContainer
      appId={APP_ID}
      uikitOptions={{
        common: {
          enableUsingDefaultUserProfile: true,
        },
        groupChannel: {
          enableMention: true,
          // typingIndicatorTypes: new Set([TypingIndicatorType.Text, TypingIndicatorType.Bubble]),
          // replyType: localConfigs.replyType,
          // threadReplySelectType: localConfigs.threadReplySelectType,
        },
        groupChannelList: {
          enableTypingIndicator: true,
          enableMessageReceiptStatus: true,
        },
        groupChannelSettings: {
          enableMessageSearch: true,
        },
      }}
      chatOptions={{
        localCacheStorage: mmkv,
        onInitialized: (sdk) => setSendbirdSDK(sdk),
        enableAutoPushTokenRegistration: false,
      }}
      platformServices={platformServices}
      styles={{
        defaultHeaderTitleAlign: 'left', //'center',
        // theme: isLightTheme ? LightUIKitTheme : DarkUIKitTheme,
        // statusBarTranslucent: GetTranslucent(),
      }}
      // errorBoundary={{ ErrorInfoComponent: ErrorInfoScreen }}
      userProfile={{
        onCreateChannel: (channel) => {
          const params = { channelUrl: channel.url };

          if (channel.isGroupChannel()) {
            router.push({ pathname: '/(explore)/chat/channel', params });
          }

          if (channel.isOpenChannel()) {
            router.push({ pathname: '/(explore)/chat/channel', params });
          }
        },
      }}
    >
      <ChatNavigator />
    </SendbirdUIKitContainer>
  );
};


const ChatNavigator = () => {
  const { currentUser } = useSendbirdChat();
  const router = useRouter();

  useEffect(() => {
    console.log('[ChatLayout] currentUser changed:', currentUser?.userId);
    if (currentUser) {
      console.log('navigating to chatList');
      router.replace('/(explore)/chat/chatList');
    } else {
      router.replace('/(explore)/chat/sign-in');
    }
  }, [currentUser, router]);

  console.log('[ChatLayout] currentUser:', currentUser);

  return (
    <Stack screenOptions={{ headerShown: false }} initialRouteName="sign-in">
      <Stack.Screen name="sign-in" />
      <Stack.Screen name="chatList" />
      <Stack.Screen name="create" />
      <Stack.Screen name="channel" />
    </Stack>
  );

} 

sendbird-utils.ts

import {
  createExpoClipboardService,
  createExpoFileService,
  createExpoMediaService,
  createExpoNotificationService,
  createExpoPlayerService,
  createExpoRecorderService,
  SendbirdUIKitContainerProps
} from "@sendbird/uikit-react-native";
import { Logger, SendbirdChatSDK } from '@sendbird/uikit-utils';
import { APP_ID } from '../env';

import * as ExpoAV from 'expo-av';
import * as ExpoClipboard from 'expo-clipboard';
import * as ExpoDocumentPicker from 'expo-document-picker';
import * as ExpoFS from 'expo-file-system';
import * as ExpoImageManipulator from 'expo-image-manipulator';
import * as ExpoImagePicker from 'expo-image-picker';
import * as ExpoMediaLibrary from 'expo-media-library';
import * as ExpoNotifications from 'expo-notifications';
import * as ExpoVideoThumbnail from 'expo-video-thumbnails';

const platformServices: SendbirdUIKitContainerProps['platformServices'] = {
  clipboard: createExpoClipboardService(ExpoClipboard),
  notification: createExpoNotificationService(ExpoNotifications),
  file: createExpoFileService({
    fsModule: ExpoFS,
    imagePickerModule: ExpoImagePicker,
    mediaLibraryModule: ExpoMediaLibrary,
    documentPickerModule: ExpoDocumentPicker,
  }),
  media: createExpoMediaService({
    avModule: ExpoAV,
    thumbnailModule: ExpoVideoThumbnail,
    imageManipulator: ExpoImageManipulator,
    fsModule: ExpoFS,
  }),
  player: createExpoPlayerService({
    avModule: ExpoAV,
  }),
  recorder: createExpoRecorderService({
    avModule: ExpoAV,
  }),
};


export default platformServices;

let AppSendbirdSDK: SendbirdChatSDK | undefined;
export const getSendbirdSDK = () => AppSendbirdSDK;
export const setSendbirdSDK = (sdk: SendbirdChatSDK): SendbirdChatSDK => {
  AppSendbirdSDK = sdk;
  return sdk;
};

const createSendbirdAPI = (appId: string, apiToken: string) => {
  const MIN = 60 * 1000;
  const endpoint = (path: string) => `https://api-${appId}.sendbird.com/v3${path}`;
  const getHeaders = (headers?: object) => ({ 'Api-Token': apiToken, ...headers });

  return {
    async getSessionToken(
      userId: string,
      expires_at = Date.now() + 10 * MIN,
    ): Promise<{ user_id: string; token: string; expires_at: number }> {
      const res = await fetch(endpoint(`/users/${userId}/token`), {
        method: 'post',
        headers: getHeaders({ 'Content-Type': 'application/json' }),
        body: JSON.stringify({ expires_at }),
      });
      if (!res.ok) {
        const text = await res.text();
        Logger.warn('[SendbirdAPI] getSessionToken failed', res.status, text);
        throw new Error(`Session token request failed: ${res.status}`);
      }
      return res.json();
    },
  };
};

export const SendbirdAPI = createSendbirdAPI(
  APP_ID || '',
  'API_TOKEN'
);

sign-in.tsx

import { SendbirdAPI } from '@/app/util/sendbird';
import { SessionHandler } from '@sendbird/chat';
import { useConnection, useSendbirdChat } from '@sendbird/uikit-react-native';
import { Button, Text, TextInput, useUIKitTheme } from '@sendbird/uikit-react-native-foundation';
import React, { useState } from 'react';
import { StyleSheet, View } from 'react-native';


const SignInScreen = () => {
  const [userId, setUserId] = useState('');
  const [nickname, setNickname] = useState('');
  const { sdk } = useSendbirdChat();
  const { connect } = useConnection();

  const connectWith = async (userId: string, nickname?: string, useSessionToken = false) => {
    if (useSessionToken) {
      try {
        const sessionHandler = new SessionHandler();
        sessionHandler.onSessionTokenRequired = (onSuccess, onFail) => {
          SendbirdAPI.getSessionToken(userId)
            .then(({ token }) => onSuccess(token))
            .catch(onFail);
        };
        (sdk as any).setSessionHandler(sessionHandler);

        const data = await SendbirdAPI.getSessionToken(userId);
        await connect(userId, { nickname, accessToken: data.token });
      } catch (e: any) {
        console.error('[Sendbird] session connect failed', e?.code, e?.message || e);
        throw e;
      }
    } else {
      console.log('[Sendbird] connect start', { userId, nickname });
      try {
        await connect(userId, { nickname });
        console.log('[Sendbird] connect success');
      } catch (e: any) {
        console.error('[Sendbird] connect failed', e?.code, e?.message || e);
      }
    }
  };
  const { colors } = useUIKitTheme();

  return (
    <View style={[styles.container, { backgroundColor: colors.background }]}>
      <Text style={styles.title}>{'Sendbird RN-UIKit sample'}</Text>
      <TextInput
        placeholder={'User ID'}
        value={userId}
        onChangeText={setUserId}
        style={[styles.input, { backgroundColor: colors.onBackground04, marginBottom: 12 }]}
      />
      <TextInput
        placeholder={'Nickname'}
        value={nickname}
        onChangeText={setNickname}
        style={[styles.input, { backgroundColor: colors.onBackground04 }]}
      />
      <Button
        style={styles.btn}
        variant={'contained'}
        onPress={async () => {
          if (userId) {
            await connectWith(userId, nickname);
          }
        }}
      >
        {'Sign in'}
      </Button>

      {/* <Versions style={{ marginTop: 12 }} /> */}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 80,
    alignItems: 'center',
    paddingHorizontal: 24,
  },
  logo: {
    width: 48,
    height: 48,
    marginBottom: 24,
  },
  title: {
    fontSize: 24,
    fontWeight: '700',
    marginBottom: 34,
  },
  btn: {
    width: '100%',
    paddingVertical: 16,
  },
  input: {
    width: '100%',
    borderRadius: 4,
    marginBottom: 32,
    paddingTop: 16,
    paddingBottom: 16,
  },
});

export default SignInScreen;

chatList.tsx

import { createGroupChannelListFragment, useSendbirdChat } from '@sendbird/uikit-react-native';
import { Stack, useRouter } from 'expo-router';
import React from 'react';

export default function ChatListScreen() {
    const router = useRouter();
    const { sdk } = useSendbirdChat();

    console.log('sdk.connectionState', sdk.connectionState);

    const GroupChannelListFragment = createGroupChannelListFragment();


    console.log('GroupChannelListFragment', GroupChannelListFragment !== null);

    return (
        <>
            <Stack.Screen
                options={{
                    headerShown: true,
                    title: 'Chat',
                }}
            />
            <GroupChannelListFragment
                onPressCreateChannel={(channelType) =>
                    router.push({ pathname: '/(explore)/chat/create', params: { channelType } })
                }
                onPressChannel={(channel) =>
                    router.push({ pathname: '/(explore)/chat/channel', params: { channelUrl: channel.url } })
                }
            />
        </>
    );
}

[Outputs]

 LOG  [ChatLayout] currentUser: undefined
 LOG  [ChatLayout] currentUser changed: undefined
 LOG  [Sendbird] connect start {"nickname": "Tester", "userId": "Testing-user—login"}
 LOG  [Sendbird] connect success
 LOG  [ChatLayout] currentUser: {"_hashValue": 141110216, "_iid": "su-ce0095c9-e28b-4d2c-ab57-3afeb7460996", "_updatedAt": 0, "connectionStatus": "online", "friendDiscoveryKey": null, "friendName": null, "isActive": true, "lastSeenAt": 0, "metaData": {}, "nickname": "Tester", "plainProfileUrl": "", "preferredLanguages": [], "requireAuth": false, "userId": "Testing-user—login"}
 LOG  [ChatLayout] currentUser changed: Testing-user—login
 LOG  navigating to chatList
 LOG  sdk.connectionState OPEN
 LOG  GroupChannelListFragment true
 ERROR  Warning: SendbirdError: Invalid parameters.

This error is located at:

   5 | export default function ChatListScreen() {
   6 |     const router = useRouter();
>  7 |     const { sdk } = useSendbirdChat();
     |                                    ^
   8 |
   9 |     console.log('sdk.connectionState', sdk.connectionState);
  10 |

Call Stack
  ChatListScreen (app/(explore)/chat/chatList.tsx:7:36)
  ScreenContentWrapper (<anonymous>)
  RNSScreenStack (<anonymous>)
  ChatNavigator (app/(explore)/chat/_layout.tsx:69:42)
  RNCSafeAreaProvider (<anonymous>)
  ChatLayout(./(explore)/chat/_layout.tsx) (<anonymous>)
  ScreenContentWrapper (<anonymous>)
  RNSScreenStack (<anonymous>)
  ThemedNavigation (app/_layout.tsx:71:48)
  ThemeProvider (app/context/ThemeContext.tsx:883:82)
  useEffect$argument_0 (app/_layout.tsx:51:24)
  RootLayout (app/_layout.tsx:107:33)
  RootApp(./_layout.tsx) (<anonymous>)
  RNCSafeAreaProvider (<anonymous>)
  App (<anonymous>)
  ErrorOverlay (<anonymous>)