For some reason, only on android, the keyboard compatible view with stream chat doesn't calculate the proper height correctly until I start to type. I have tried many different configurations and can't figure out why it is only on android and why it readjusts as soon as I start to type.
Here is the code for the chat.
import "react-native-gesture-handler";
import {
StyleSheet,
Text,
View,
Pressable,
KeyboardAvoidingView,
Platform,
Dimensions,
StatusBar,
Keyboard,
} from "react-native";
import { useHeaderHeight } from "@react-navigation/elements";
import React, { useState, useContext, useEffect } from "react";
import Header from "../components/Header";
import { StreamChat } from "stream-chat";
import {
SafeAreaView,
useSafeAreaInsets,
SafeAreaProvider,
} from "react-native-safe-area-context";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { ChatContext } from "../../contexts/ChatContext";
import {
Channel,
Chat,
MessageInput,
MessageList,
OverlayProvider,
ChannelList,
Thread,
} from "stream-chat-expo";
import { Ionicons } from "@expo/vector-icons";
import { getAuth } from "firebase/auth";
import { doc, getDoc } from "firebase/firestore";
import { db } from "../../firebase";
import { UserContext } from "../../contexts/UserContext";
import ChatProfileView from "../components/ChatProfileView";
const client = StreamChat.getInstance(api-key);
const auth = getAuth();
const ChatScreen = () => {
const { channel, setChannel } = useContext(UserContext);
const [chatContactVisible, setChatContactVisible] = useState(false);
const [chatContactToView, setChatContactToView] = useState({});
const [thread, setThread] = useState(null);
const [userName, setUserName] = useState(null);
const [chatName, setChatName] = useState(null);
const [chatId, setChatId] = useState(null);
const [keyboardHeight, setKeyboardHeight] = useState(0);
const { bottom } = useSafeAreaInsets();
const headerHeight = useHeaderHeight();
useEffect(() => {
if (!client.user) {
getStreamUserToken().then((userToken) => {
client.connectUser(
{
id: auth.currentUser.uid,
},
userToken.data
);
console.log("loggeduserin- login");
});
}
}, []);
const clearChannel = () => {
setChannel();
setChatName();
setChatContactToView();
};
const clearThread = () => {
setThread(null);
};
useEffect(() => {
console.log(channel);
}, [channel, setChannel]);
const theme = {
messageList: {
container: {
backgroundColor: "white",
},
},
channelPreview: {
container: { backgroundColor: "white" },
},
};
useEffect(() => {
async function getUserNames() {
const userRef = doc(db, "users", auth.currentUser.uid);
const docSnap = await getDoc(userRef);
if (docSnap.exists()) {
const info = docSnap.data();
setUserName(info.firstName);
}
}
getUserNames();
}, []);
const getUserData = async () => {
const userRef = doc(db, "users", chatId);
const docSnap = await getDoc(userRef);
if (docSnap.exists()) {
const info = docSnap.data();
setChatContactToView(info);
console.log(info);
}
};
useEffect(() => {
if (channel) {
if (userName === channel.data.memberOne) {
setChatName(channel.data.memberTwo);
setChatId(channel.data.memberTwoId);
console.log("---------- id set");
console.log(channel.data.memberTwoId);
} else {
setChatName(channel.data.memberOne);
setChatId(channel.data.memberOneId);
console.log("---------- id set now");
console.log(channel.data.memberTwoId);
}
}
}, [channel, setChannel]);
const CustomPreviewTitle = ({ channel }) => {
if (userName === channel.data.memberOne) {
return <Text>{channel.data.memberTwo}</Text>;
} else {
return <Text>{channel.data.memberOne}</Text>;
}
};
const openChatContact = () => {
getUserData()
.then(setChatContactVisible(true), console.log("opened"))
.catch((err) => {
alert("User no longer exists :(");
});
};
const filters = { members: { $in: [auth.currentUser.uid] } };
return (
<ChatContext.Provider
value={{
chatContactVisible,
setChatContactVisible,
chatContactToView,
setChatContactToView,
}}
>
<GestureHandlerRootView style={styles.container}>
<SafeAreaView style={{ flex: 1 }}>
<Chat client={client} style={theme}>
<Header screenName="Chat" />
{channel ? (
<Channel
channel={channel}
thread={thread}
threadList={!!thread}
keyboardBehavior={"padding"}
keyboardVerticalOffset={0}
>
<View
style={{
justifyContent: "space-between",
flexDirection: "row",
alignItems: "center",
height: 40,
}}
>
<View
style={{
marginLeft: 10,
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
}}
>
<Pressable onPress={clearChannel}>
<Ionicons
name="arrow-back-sharp"
size={30}
color="black"
/>
</Pressable>
<Pressable onPress={openChatContact}>
<Text
style={{
color: "black",
fontSize: 20,
marginLeft: 10,
}}
>
{chatName}
</Text>
</Pressable>
</View>
{thread ? (
<Pressable
style={{
marginRight: 15,
}}
onPress={clearThread}
>
<Text style={{ color: "#3b55d9" }}>Close thread</Text>
</Pressable>
) : (
<></>
)}
</View>
{thread ? (
<Thread />
) : (
<>
<MessageList onThreadSelect={setThread} />
<MessageInput />
</>
)}
</Channel>
) : (
<View style={styles.scroll}>
<ChannelList
onSelect={setChannel}
filters={filters}
PreviewTitle={CustomPreviewTitle}
style={theme}
/>
</View>
)}
</Chat>
{chatContactVisible ? <ChatProfileView /> : <></>}
</SafeAreaView>
</GestureHandlerRootView>
</ChatContext.Provider>
);
};
export default ChatScreen;
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: "column",
backgroundColor: "white",
},
scroll: {
flex: 9,
},
});
`
Here is the code for the header component.
`import { StyleSheet, Text, View, Pressable } from "react-native";
import React, { useState, useEffect, useContext } from "react";
import { auth } from "../../firebase";
import { useNavigation } from "@react-navigation/core";
import { Ionicons } from "@expo/vector-icons";
import { StreamChat } from "stream-chat";
import { UserContext } from "../../contexts/UserContext";
import { getDocs, query, where, collection } from "firebase/firestore";
import { getAuth } from "firebase/auth";
import { db } from "../../firebase";
import { LikesContext } from "../../contexts/LikesContext";
import ProfileView from "./Likes";
const client = StreamChat.getInstance(api-key);
const Header = (props) => {
const [likesVisible, setLikesVisible] = useState(false);
const { tempLikesArray, setTempLikesArray } = useContext(UserContext);
const navigation = useNavigation();
const handleSignOut = () => {
auth
.signOut()
.then(async () => {
navigation.replace("Login");
await client.disconnectUser();
console.log("User disconnected");
})
.catch((error) => alert(error.mesage));
};
const openLikes = () => {
setLikesVisible(true);
};
return (
<LikesContext.Provider
value={{
likesVisible,
setLikesVisible,
}}
>
<View style={styles.header}>
<Text style={styles.logoText}>{props.screenName}</Text>
<View style={{ flexDirection: "row" }}>
<Pressable
style={{ justifyContent: "center", marginRight: 15 }}
onPress={openLikes}
>
<View
style={[
tempLikesArray.length > 0
? styles.likeBubbleVisible
: styles.likeBubbleInvisible,
]}
></View>
<Ionicons name="ios-person-add-outline" size={24} color="black" />
</Pressable>
<Pressable
style={{ justifyContent: "center", marginRight: 15 }}
onPress={handleSignOut}
>
<Ionicons name="ios-log-out-outline" size={24} color="black" />
</Pressable>
</View>
{likesVisible ? <ProfileView /> : <></>}
</View>
</LikesContext.Provider>
);
};
export default Header;
const styles = StyleSheet.create({
header: {
flex: 1,
flexDirection: "row",
flexWrap: "wrap",
backgroundColor: "white",
width: "100%",
justifyContent: "space-between",
marginTop: 10,
},
logoText: {
alignSelf: "center",
fontSize: 30,
color: "#3b55d9",
marginLeft: 20,
fontFamily: "Inter_900Black",
},
logoutButton: {
alignSelf: "center",
},
image: {
width: 20,
height: 20,
alignSelf: "center",
marginRight: 20,
},
likeBubbleVisible: {
width: 5,
height: 5,
borderRadius: 10,
backgroundColor: "blue",
position: "absolute",
display: "flex",
top: 2,
left: 0,
},
likeBubbleInvisible: {
width: 5,
height: 5,
backgroundColor: "blue",
position: "absolute",
display: "none",
top: 2,
left: 0,
},
});
`