Ice candidates getting added infinietly leading app to crash

When i click start meeting, i see the following error in terminal. When i call first time i dont see error, but after ending call when i start meeting second time then i see this error:

 WARN Possible Unhandled Promise Rejection (id: 115):

Error: The remote description was null

after this error when other user joins call he get’s thefollowing error:
Your app just crashed. See the error below. java.lang.NullPointerException: Attempt to invoke virtual method 'org.webrtc.PeerConnection com.oney.WebRTCModule.PeerConnectionObserver.getPeerConnection()' on a null object reference com.oney.WebRTCModule.WebRTCModule.lambda$peerConnectionSetRemoteDescription$25$com-oney-WebRTCModule-WebRTCModule(WebRTCModule.java:1051) com.oney.WebRTCModule.WebRTCModule$$ExternalSyntheticLambda21.run(Unknown Source:8) java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644) java.lang.Thread.run(Thread.java:1012)

I do i get rid of these errors

Here’re my Video Call Components:

JoinScreen.jsx :

import React, { useState, useEffect } from "react";
    import { Text, StyleSheet, Button, View } from "react-native";
    
    import {
     RTCPeerConnection,
     RTCView,
     mediaDevices,
     RTCIceCandidate,
     RTCSessionDescription,
     MediaStream,
    } from "react-native-webrtc";
    import { db } from "../firebase";
    import {
     addDoc,
     collection,
     doc,
     setDoc,
     getDoc,
     updateDoc,
     onSnapshot,
     deleteField,
    } from "firebase/firestore";
    import CallActionBox from "./CallActionBox";
    
    const configuration = {
     iceServers: [
      {
       urls: ["stun:stun1.l.google.com:19302", "stun:stun2.l.google.com:19302"],
      },
     ],
     iceCandidatePoolSize: 10,
    };
    
    export default function JoinScreen({ roomId, screens, setScreen }) {
     const [localStream, setLocalStream] = useState();
     const [remoteStream, setRemoteStream] = useState();
     const [cachedLocalPC, setCachedLocalPC] = useState();
    
     const [isMuted, setIsMuted] = useState(false);
     const [isOffCam, setIsOffCam] = useState(false);
    
     //Automatically start stream
     useEffect(() => {
      startLocalStream();
     }, []);
     useEffect(() => {
      if (localStream) {
       joinCall(roomId);
      }
     }, [localStream]);
    
     //End call button
     async function endCall() {
      if (cachedLocalPC) {
       const senders = cachedLocalPC.getSenders();
       senders.forEach((sender) => {
        cachedLocalPC.removeTrack(sender);
       });
       cachedLocalPC.close();
      }
    
      const roomRef = doc(db, "room", roomId);
      await updateDoc(roomRef, { answer: deleteField(), connected: false });
    
      setLocalStream();
      setRemoteStream(); // set remoteStream to null or empty when callee leaves the call
      setCachedLocalPC();
      // cleanup
      setScreen(screens.ROOM); //go back to room screen
     }
    
     //start local webcam on your device
     const startLocalStream = async () => {
      // isFront will determine if the initial camera should face user or environment
      const isFront = true;
      const devices = await mediaDevices.enumerateDevices();
    
      const facing = isFront ? "front" : "environment";
      const videoSourceId = devices.find(
       (device) => device.kind === "videoinput" && device.facing === facing
      );
      const facingMode = isFront ? "user" : "environment";
      const constraints = {
       audio: true,
       video: {
        mandatory: {
         minWidth: 500, // Provide your own width, height and frame rate here
         minHeight: 300,
         minFrameRate: 30,
        },
        facingMode,
        optional: videoSourceId ? [{ sourceId: videoSourceId }] : [],
       },
      };
      const newStream = await mediaDevices.getUserMedia(constraints);
      setLocalStream(newStream);
     };
    
     //join call function
     const joinCall = async (id) => {
      const roomRef = doc(db, "room", id);
      const roomSnapshot = await getDoc(roomRef);
    
      if (!roomSnapshot.exists) return;
      const localPC = new RTCPeerConnection(configuration);
      localStream.getTracks().forEach((track) => {
       localPC.addTrack(track, localStream);
      });
    
      const callerCandidatesCollection = collection(roomRef, "callerCandidates");
      const calleeCandidatesCollection = collection(roomRef, "calleeCandidates");
    
      localPC.addEventListener("icecandidate", (e) => {
       if (!e.candidate) {
        console.log("Got final candidate!");
        return;
       }
       addDoc(calleeCandidatesCollection, e.candidate.toJSON());
      });
    
      localPC.ontrack = (e) => {
       const newStream = new MediaStream();
       e.streams[0].getTracks().forEach((track) => {
        newStream.addTrack(track);
       });
       setRemoteStream(newStream);
      };
    
      const offer = roomSnapshot.data().offer;
      await localPC.setRemoteDescription(new RTCSessionDescription(offer));
    
      const answer = await localPC.createAnswer();
      await localPC.setLocalDescription(answer);
    
      await updateDoc(roomRef, { answer, connected: true }, { merge: true });
    
      onSnapshot(callerCandidatesCollection, (snapshot) => {
       snapshot.docChanges().forEach((change) => {
        if (change.type === "added") {
         let data = change.doc.data();
         localPC.addIceCandidate(new RTCIceCandidate(data));
        }
       });
      });
    
      onSnapshot(roomRef, (doc) => {
       const data = doc.data();
       if (!data.answer) {
        setScreen(screens.ROOM);
       }
      });
    
      setCachedLocalPC(localPC);
     };
    
     const switchCamera = () => {
      localStream.getVideoTracks().forEach((track) => track._switchCamera());
     };
    
     // Mutes the local's outgoing audio
     const toggleMute = () => {
      if (!remoteStream) {
       return;
      }
      localStream.getAudioTracks().forEach((track) => {
       track.enabled = !track.enabled;
       setIsMuted(!track.enabled);
      });
     };
    
     const toggleCamera = () => {
      localStream.getVideoTracks().forEach((track) => {
       track.enabled = !track.enabled;
       setIsOffCam(!isOffCam);
      });
     };
    
     return (
      <View className="flex-1">
       <RTCView
        className="flex-1"
        streamURL={remoteStream && remoteStream.toURL()}
        objectFit={"cover"}
       />
    
       {remoteStream && !isOffCam && (
        <RTCView
         className="w-32 h-48 absolute right-6 top-8"
         streamURL={localStream && localStream.toURL()}
        />
       )}
       <View className="absolute bottom-0 w-full">
        <CallActionBox
         switchCamera={switchCamera}
         toggleMute={toggleMute}
         toggleCamera={toggleCamera}
         endCall={endCall}
        />
       </View>
      </View>
     );
    }

RoomScreen.jsx :

    import React, { useEffect, useState } from "react";
    import { Text, View, TextInput, TouchableOpacity, Alert } from "react-native";
    
    import { db } from "../firebase";
    import {
     addDoc,
     collection,
     doc,
     setDoc,
     getDoc,
     updateDoc,
     onSnapshot,
     deleteField,
    } from "firebase/firestore";
    import { useDataContext } from "./DataContext";
    import AsyncStorage from "@react-native-async-storage/async-storage";
    
    export default function RoomScreen({ setScreen, screens, setRoomId, roomId }) {
     const { userData, currentUserData, videoCallEmail, setVideoCallEmail, serverAddress, fetchCurrentUserByEmail } = useDataContext()
     const onCallOrJoin = async (screen) => {
      if (roomId.length > 0) {
       setScreen(screen);
      }
      try {
       const response = await fetch(`${serverAddress}/api/update-receiving-call/${videoCallEmail}`, {
        method: 'PUT',
        headers: {
         'Content-Type': 'application/json',
        },
       });
       if (response.ok) {
        console.log('Updated Receiving Call of another user');
       } else {
        console.error('Error updating receiving call status');
       }
      } catch (error) {
       console.error('Error updating receiving call status:', error);
      }
     };
    
     //checks if room is existing
     const checkMeeting = async () => {
      if (roomId) {
       const roomRef = doc(db, "room", roomId);
       const roomSnapshot = await getDoc(roomRef);
    
       // console.log(roomSnapshot.data());
    
       if (!roomSnapshot.exists() || roomId === "") {
        // console.log(`Room ${roomId} does not exist.`);
        Alert.alert("Wait for your instructor to start the meeting.");
        return;
       } else {
        onCallOrJoin(screens.JOIN);
       }
      } else {
       Alert.alert("Provide a valid Room ID.");
      }
     };
    
     return (
      <View>
       {/* <Text className="text-2xl font-bold text-center">Enter Room ID:</Text>
       <TextInput
        className="bg-white border-sky-600 border-2 mx-5 my-3 p-2 rounded-md"
        value={roomId}
        onChangeText={setRoomId}
       /> */}
       <View className="gap-y-3 mx-5 mt-2">
        <TouchableOpacity
         className="bg-sky-300 p-2 rounded-md"
         onPress={() => onCallOrJoin(screens.CALL)}
        >
         <Text className="color-black text-center text-xl font-bold ">
          Start meeting
         </Text>
        </TouchableOpacity>
        {
         (currentUserData.email == videoCallEmail) && 
         <TouchableOpacity
          className="bg-sky-300 p-2 rounded-md"
          onPress={() => checkMeeting()}
         >
          <Text className="color-black text-center text-xl font-bold ">
           Join meeting
          </Text>
         </TouchableOpacity>
        }
       </View>
      </View>
     );
    }

VideoCall.jsx:

    import React, { useState } from "react";
    import { Text, SafeAreaView } from "react-native";
    import RoomScreen from "./RoomScreen";
    import CallScreen from "./CallScreen";
    import JoinScreen from "./JoinScreen";
    
    // Just to handle navigation
    export default function VideoCall() {
     const screens = {
      ROOM: "JOIN_ROOM",
      CALL: "CALL",
      JOIN: "JOIN",
     };
    
     const [screen, setScreen] = useState(screens.ROOM);
     const [roomId, setRoomId] = useState("myroom");
    
     let content;
    
     switch (screen) {
      case screens.ROOM:
       content = (
        <RoomScreen
         roomId={roomId}
         setRoomId={setRoomId}
         screens={screens}
         setScreen={setScreen}
        />
       );
       break;
    
      case screens.CALL:
       content = (
        <CallScreen roomId={roomId} screens={screens} setScreen={setScreen} />
       );
       break;
    
      case screens.JOIN:
       content = (
        <JoinScreen roomId={roomId} screens={screens} setScreen={setScreen} />
       );
       break;
    
      default:
       content = <Text>Wrong Screen</Text>;
     }
    
     return (
      <SafeAreaView className="flex-1 justify-center ">{content}</SafeAreaView>
     );
    }
**CallScreen.jsx**:
import React, { useState, useEffect } from "react";
import { View } from "react-native";

import {
  RTCPeerConnection,
  RTCView,
  mediaDevices,
  RTCIceCandidate,
  RTCSessionDescription,
  MediaStream,
} from "react-native-webrtc";
import { db } from "../firebase";
import {
  addDoc,
  collection,
  doc,
  setDoc,
  getDoc,
  updateDoc,
  onSnapshot,
  deleteField,
} from "firebase/firestore";

import CallActionBox from "./CallActionBox";

const configuration = {
    iceServers: [

    ],  
  iceCandidatePoolSize: 10,
};

export default function CallScreen({ roomId, screens, setScreen }) {
  const [localStream, setLocalStream] = useState();
  const [remoteStream, setRemoteStream] = useState();
  const [cachedLocalPC, setCachedLocalPC] = useState();

  const [isMuted, setIsMuted] = useState(false);
  const [isOffCam, setIsOffCam] = useState(false);

  useEffect(() => {
    return () => {
      setLocalStream(null);
      setRemoteStream(null);
      setCachedLocalPC(null);
    };
  }, []);  
  useEffect(() => {
    startLocalStream();
  }, []);

  useEffect(() => {
    if (localStream && roomId) {
      startCall(roomId);
    }
  }, [localStream, roomId]);

  //End call button
  async function endCall() {
    try {
      if (cachedLocalPC) {
        const senders = cachedLocalPC.getSenders();
        senders.forEach((sender) => {
          cachedLocalPC.removeTrack(sender);
        });
        cachedLocalPC.close();
      }
  
      const roomRef = doc(db, "room", roomId);
      await updateDoc(roomRef, { answer: deleteField() });
  
      setLocalStream(null);
      setRemoteStream(null);
      setCachedLocalPC(null);
      setScreen(screens.ROOM);
    } catch (error) {
      console.error("Error ending call:", error);
    }
  }  

  //start local webcam on your device
  const startLocalStream = async () => {
    try {
      const isFront = true;
      const devices = await mediaDevices.enumerateDevices();
      const facing = isFront ? "front" : "environment";
      const videoSourceId = devices.find(
        (device) => device.kind === "videoinput" && device.facing === facing
      );
      const facingMode = isFront ? "user" : "environment";
      const constraints = {
        audio: true,
        video: {
          mandatory: {
            minWidth: 500,
            minHeight: 300,
            minFrameRate: 30,
          },
          facingMode,
          optional: videoSourceId ? [{ sourceId: videoSourceId }] : [],
        },
      };
      const newStream = await mediaDevices.getUserMedia(constraints);
      setLocalStream(newStream);
    } catch (error) {
      console.error("Error starting local stream:", error);
    }
  };
  

  const startCall = async (id) => {
    const localPC = new RTCPeerConnection(configuration);
    
    localStream.getTracks().forEach((track) => {
      localPC.addTrack(track, localStream);
    });

    const roomRef = doc(db, "room", id);
    const callerCandidatesCollection = collection(roomRef, "callerCandidates");
    const calleeCandidatesCollection = collection(roomRef, "calleeCandidates");

    localPC.addEventListener("icecandidate", (e) => {
      if (!e.candidate) {
        console.log("Got final candidate!");
        return;
      }
      addDoc(callerCandidatesCollection, e.candidate.toJSON());
    });

    localPC.ontrack = (e) => {
      const newStream = new MediaStream();
      e.streams[0].getTracks().forEach((track) => {
        newStream.addTrack(track);
      });
      setRemoteStream(newStream);
    };

    const offer = await localPC.createOffer();
    await localPC.setLocalDescription(offer);

    await setDoc(roomRef, { offer, connected: false }, { merge: true });

    // Listen for remote answer
    onSnapshot(roomRef, (doc) => {
      const data = doc.data();
      if (!localPC.currentRemoteDescription && data.answer) {
        try {
          const rtcSessionDescription = new RTCSessionDescription(data.answer);
          localPC.setRemoteDescription(rtcSessionDescription);
        } catch (error) {
          console.error('Error setting remote description:', error);
        }
      }
      else {
        setRemoteStream(null); // set remoteStream to null when caller leaves the call
      }
    });

    // when answered, add candidate to peer connection
    onSnapshot(calleeCandidatesCollection, (snapshot) => {
      snapshot.docChanges().forEach((change) => {
        if (change.type === "added") {
          let data = change.doc.data();
          localPC.addIceCandidate(new RTCIceCandidate(data));
        }
      });
    });

    setCachedLocalPC(localPC);
  };

  const switchCamera = () => {
    localStream.getVideoTracks().forEach((track) => track._switchCamera());
  };

  // Mutes the local's outgoing audio
  const toggleMute = () => {
    if (!remoteStream) {
      return;
    }
    localStream.getAudioTracks().forEach((track) => {
      track.enabled = !track.enabled;
      setIsMuted(!track.enabled);
    });
  };

  const toggleCamera = () => {
    localStream.getVideoTracks().forEach((track) => {
      track.enabled = !track.enabled;
      setIsOffCam(!isOffCam);
    });
  };

  return (
    <View className="flex-1 bg-red-600">
      {!remoteStream && (
        <RTCView
          className="flex-1"
          streamURL={localStream && localStream.toURL()}
          objectFit={"cover"}
        />
      )}

      {remoteStream && (
        <>
          <RTCView
            className="flex-1"
            streamURL={remoteStream && remoteStream.toURL()}
            objectFit={"cover"}
          />
          {!isOffCam && (
            <RTCView
              className="w-32 h-48 absolute right-6 top-8"
              streamURL={localStream && localStream.toURL()}
            />
          )}
        </>
      )}
      <View className="absolute bottom-0 w-full">
        <CallActionBox
          switchCamera={switchCamera}
          toggleMute={toggleMute}
          toggleCamera={toggleCamera}
          endCall={endCall}
        />
      </View>
    </View>
  );
}

I’m using expo 49 with firebase

Can anyone please help? Im still stuck