Firebase collecting and writing documents once production installed

Hello,
question
I got a weird issue that once I have build a app in Expo react native with the firebase sdk in Production mode. And have that installed. The production app starts "before even being opent " writing and collecting document. But this shouldent be even possible becouse al the functins that do those thing requere a user input or button press.

Can anyone help me please but i’m realy lost on how this is happening

Firebase config

import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore,  initializeFirestore } from 'firebase/firestore';

// Initialize Firebase
const firebaseConfig = {
  ###
};


const app = initializeApp(firebaseConfig);
const auth = getAuth()

const db = initializeFirestore(app, {
    experimentalForceLongPolling: true,
});

export {auth ,app, db}

App versions

  • “eas-cli”: “^0.55.1”,
  • “expo”: “^46.0.0”,
  • “react-native-async-storage/async-storage”: “~1.17.3”,
  • “react-native-masked-view/masked-view”: “0.2.7”,
  • “react-native-picker/picker”: “2.4.2”,
  • “react-navigation/bottom-tabs”: “^6.3.1”,
  • “react-navigation/native”: “^6.0.10”,
  • “react-navigation/stack”: “^6.2.1”,
  • “sentry/react-native”: “4.2.2”,
  • “firebase”: “^9.9.1”,
  • “sentry-expo”: “~5.0.0”,

Your initialization code looks fine to me; that said, I can’t see where you are reading and writing from/ to the Firestore. If these calls were, say, not wrapped inside of a function/ callback before being passed to onPress events, that could cause them to be executed prematurely. However, there’s nothing specific to Expo or React Native that should cause such a thing.

If you share some of your read/ write code, someone from the community may be able to suggest something. Also worth looking at general Firebase forums (e.g., Stackoverflow), as the JS SDK can be used in the same manner with web apps, as well.

Hey thanks for replying

This is one component that seems to be triggered. In this component, i create a report document in firebase store

import React, { useEffect, useState } from "react";
import { View, Text,TouchableOpacity, TextInput } from "react-native"
import { useTheme } from "@react-navigation/native";

import Checkbox from "expo-checkbox";
import { addDoc , collection} from "firebase/firestore"; 
import { db } from "../../../../firebaseConfig";

export default function ReportSection({questionId,setReportStatus,closeModal}){
    const {colors, ts} = useTheme()

    const [reportCustomText, setReportCustomText]=useState('');
    const [chosenReportItem, setReportItem]= useState({type: null, text: '' });
    const checkReport = () => {
        if(chosenReportItem.type == null || chosenReportItem.type > 3 || chosenReportItem < 1)return;
        if(chosenReportItem?.type == 1){
            saveReportDB({key : chosenReportItem.type, text: reportCustomText})
            return 
        }

        if(chosenReportItem?.type == 2){
            saveReportDB({key : chosenReportItem.type, text: reportCustomText})
            return
        }

        if(chosenReportItem?.type == 3){
            saveReportDB({key : chosenReportItem.type, text: reportCustomText})
            return 
        }
    }

    const saveReportDB =async({key, text})=>{
        if(questionId){
            try{
                const result = await addDoc(collection(db, "QuestionReports" ), {
                    errorKey: key,
                    errorText: text,
                    questionId: questionId,
                    createdAt: new Date(),
                });
            }catch(err){
                console.error(err)
            }
        }
        closeModal()
    }


    return(
        <View style={{ backgroundColor: colors.black_500, width: '100%',height: '95%'  ,borderRadius:20 , position: 'absolute'}}>
                    <View style={{alignItems: 'center', paddingTop:20}}>
                        <Text style={{color: colors.white_500, fontSize:20, fontWeight:'800'}}>Report</Text>
                    </View>
                    <View style={{paddingHorizontal:20, paddingTop:20, flex:1}}>
                        <View style={{flexDirection: 'row', alignItems: 'center', marginBottom:20}}>
                            <Checkbox
                                color={chosenReportItem?.type == 1 ? colors.white_500: colors.white_500}
                                value={chosenReportItem?.type == 1}
                                onPress={()=>setReportItem({type: 1 })}
                            />
                            <TouchableOpacity onPress={()=> setReportItem({type: 1 })}>
                                <Text style={{color: colors.white_500,fontSize: ts.ts3, marginLeft:10 }}>Title missing</Text>
                            </TouchableOpacity>
                        </View>
                        <View style={{flexDirection: 'row', alignItems: 'center', marginBottom:20}}>
                            <Checkbox
                                color={chosenReportItem?.type == 2 ? colors.white_500: colors.white_500}
                                value={chosenReportItem?.type == 2}
                                onPress={()=> setReportItem({type: 2 })}
                            />
                            <TouchableOpacity onPress={()=> setReportItem({type: 2 })}>
                                <Text style={{color: colors.white_500,fontSize: ts.ts3, marginLeft:10 }}>No correct answer</Text>
                            </TouchableOpacity>
                        </View>
                        <View style={{flexDirection: 'row', alignItems: 'center', marginBottom:20}}>
                            <Checkbox
                                color={chosenReportItem?.type == 3 ? colors.white_500: colors.white_500}
                                value={chosenReportItem?.type == 3}
                                onPress={()=> setReportItem({type: 3, text: 'test' })}
                            />
                            <TouchableOpacity onPress={()=> setReportItem({type: 3 })}>
                                <Text style={{color: colors.white_500,fontSize: ts.ts3, marginLeft:10 }}>Somthing else</Text>
                            </TouchableOpacity>
                        </View>
                        <View style={{ marginBottom:20,   }}>
                            <TextInput 
                                style={{padding:10,borderRadius:5, height:100, color: colors.white_500, backgroundColor: colors.gray_400_opacity_3 }} 
                                value={reportCustomText} 
                                multiline={true}
                                maxLength={200}
                                numberOfLines={5}
                                placeholderTextColor={colors.gray_400} 
                                placeholder="Do you wish to tell more?.."
                                onChangeText={setReportCustomText} 
                            />
                        </View>
                    </View>
                    <View style={{ flexDirection: 'row', alignItems: 'center'}}>
                        <View style={{flex:1,padding:20, alignItems: 'flex-start'}}>
                            <TouchableOpacity onPress={()=> setReportStatus(false)} style={{ height: 45 , width: 120 ,borderWidth: 0.5 , borderColor: colors.white_500, borderRadius:5,  paddingVertical:10}}>
                                <Text style={{color: colors.white_500, fontSize:18, fontWeight: '800',width: '100%', height: '100%', textAlign: 'center' }}>Cancel</Text>
                            </TouchableOpacity>
                        </View>
                        <View style={{flex:1,padding:20, alignItems: 'flex-end'}}>
                            <TouchableOpacity onPress={()=> {checkReport()}} style={{ height: 45 , width: 120 ,backgroundColor: colors.card_box ,borderRadius:5, paddingVertical:10, }}>
                                <Text style={{ color: colors.white_500, fontSize:18, fontWeight: '800', width: '100%', height: '100%',  textAlign: 'center'}}>Submit</Text>
                            </TouchableOpacity>
                        </View>
                    </View>
                </View> 
    )
}

Just a little side question.
How would I be able to debug a play store build? Because in a development build nothing is wrong and I can see an error. But only when I install a play store build did the problems appear. And in sentry I em currently not seeing anything wrong.

@keith-kurak I have done some more testing to find out what is going on. And found that could be the cause. I did a trial where I installed the app and further did nothing. After a few minutes in sentry, I get this error : Error: Ad is not ready. Because I have had this error also in the development build I know where this problem is triggered. I have a component that has a counter that goes up every time a question is collected. But if the counter goes too quickly and hasn’t had time to load the question the error appears.

This makes me believe that from the moment my app is installed an infinity loop is caused what rerenders all my components. What could explain everything. Except for how it is happening. So do you perhaps know if this is possible and where I should look to fix it?

You could get into a situation with useEffect() where it updates some state, which causes the component to re-render, which causes useEffect() to update the state, which causes the component to re-render, …

I can’t tell if that’s what’s happening in your case, though.

If that is what’s happening, maybe the following will help:

1 Like

Maybe you are right. But I don’t know where and how to find out where it is happening. Because its only happening in an production build. But there is one place where it could happen and that its on my questions page. But I don’t know what could cause that.

Question page

import React, { useEffect , useReducer, useState} from "react";
import { View, ImageBackground,Text , TouchableOpacity , ScrollView, Pressable,useColorScheme} from 'react-native';
//firebase
import { collection , query, getDocs, where,limit } from "firebase/firestore";
import { db } from "../../firebaseConfig";
//providers
import { INITIAL_STATE, questionReducer } from "./reducers/questionReducer";
//expo
import { Fontisto  , MaterialCommunityIcons } from '@expo/vector-icons';
//navigation
import { useNavigation, useTheme } from "@react-navigation/native";
//modals
import QuestionModalResult from "./modals/QuestionsModalResult";
import ModalLoading from "../ui/modals/ModalLoading";
//src
import { ACTION_TYPES } from "../../src/ActionTypes";
// assets
import darkBackgroundImage from "../../assets/background.jpg";
import lightBackgroundImage from "../../assets/lightBackground.jpg";
//providers
import { useAds } from "../../providers/AdsProvider";
import { Native } from "sentry-expo";

export default function QuestionsIndex({route}){
    const scheme = useColorScheme();
    const [state, dispatch] = useReducer(questionReducer, INITIAL_STATE);
    
    const { questionCat, random, randomType } = route.params
    const {adsCountAddOne}= useAds()
    //navigation
    const [questionModalResultVisable, setQuestionModalResultVisable]= useState(false)
    const navigation = useNavigation()
    const {colors, ts}= useTheme()

    const nextQuestion = ()=>{
        // const item = questions.docs[currentItemKey]
        if(!state?.questionData?.questions)return;

        const questions = state.questionData.questions
        const newQuestion = questions.docs[state.questionData.selectedKey]
        if(newQuestion?.data()){
            // count the questions played to trigger a add
            dispatch({type: ACTION_TYPES.QUESTION_SET, payload:{question:newQuestion.data(), id:newQuestion.id}})
        }
      
        if(state.questionData.selectedKey >= state.questionData.questions.size - 1){
            getAllQuestions()
        }else{
            dispatch({type: ACTION_TYPES.QUESTION_KEY_ADD_ONE})
            adsCountAddOne()
        }
    }

    const nextStep=async()=>{ 
        //if answer is not showed
        if(state.checkAnswer === false){
            setQuestionModalResultVisable(true)
            dispatch({type: ACTION_TYPES.QUESTION_ANSWER_SET_TRUE})
        }
        //if answer is showed
        if(state.checkAnswer === true){
            setQuestionModalResultVisable(false)
            dispatch({type: ACTION_TYPES.QUESTION_ANSWER_SET_FALSE})
            dispatch({type: ACTION_TYPES.QUESTIONS_CHOSEN_BUTTON_INDEX_UPDATE, payload:{key:null, isRightValue: null}})
            nextQuestion()
        }
    }

    const getAllQuestions= async()=>{
        // setLoading(true)
        dispatch({type: ACTION_TYPES.QUESTIONS_LOADING_STARTED})
        dispatch({type: ACTION_TYPES.QUESTION_KEY_RESET})
        let isMounted = true
        try{
            const randomNumber = Math.floor(1000 + Math.random() * 90000);
            const randomIndex = Math.floor(1 + Math.random() * 3);
            let defaultBook = "Genisis"
            if(questionCat){
                defaultBook= questionCat;
            }
            const q = await createQuery(randomIndex, randomNumber,defaultBook)
            //execute query and getter questions
            await getDocs(q).then((questionsSnap)=>{
                if(questionsSnap && isMounted){
                    checkCollectedDocuments(questionsSnap)  
                }
            }).catch((err)=>{
                Native.captureException(err)
                console.log(err)
            })
          
        }catch(err){
            Native.captureException(err)
            console.log(err)
        }

        return ()=>{ isMounted=false }
    }

    const createQuery=async(randomIndex, randomNumber,defaultBook)=>{
        const questionsRef = collection(db, "ENQuestions","KJV","question");
        if(random){
            if(randomType == "old" || randomType =="new"){
                const q = await query(questionsRef, 
                    // where("questionCat", "in" , questionCats),
                    where("testament", "==" , randomType),
                    where("randomIds."+randomIndex, ">=" ,randomNumber),
                    limit(4)
                )
                return q
            }else{
                const q = await query(questionsRef, 
                    where("randomIds."+randomIndex, ">=" ,randomNumber),
                    limit(4)
                )
                return q
            }
        }else{
            const q = await query(questionsRef, 
                where("questionCat", "==" , defaultBook),
                where("randomIds."+randomIndex, ">=" ,randomNumber),
                limit(4)
            )
            return q
        }
    }

    const checkCollectedDocuments =async (documents)=>{    
        dispatch({type: ACTION_TYPES.QUESTIONS_QUESTIONS_UPDATE, payload:{questions: documents}})
        if(documents?.size == 0){
            dispatch({type: ACTION_TYPES.QUESTIONS_LOADING_ERROR})
            return
        }
        dispatch({type: ACTION_TYPES.QUESTIONS_LOADING_FINISHED})
    }

    const shuffle= (array)=>{
        for (let i = array.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            const temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
        return array;
    }

    // useEffects 
    useEffect(()=>{
        if(state?.questionData?.questions?.size > 0){
            nextQuestion()
        }
    },[state?.questionData?.questions])

    useEffect(()=>{
        //shuffelse the options from the selected question
        async function randomOptions(){
            let options =  state.questionData.selected.options
            const rightAnswer =  options.filter(option => {
                return option.isRight === true;
            })
            const randomOptions =  shuffle(options)
            dispatch({type: ACTION_TYPES.QUESTIONS_OPTIONS_UPDATE, payload:{options: randomOptions, rightAnswer: rightAnswer[0]}})
        }
        if(state.questionData.selected?.options){
            randomOptions()
        }
    },[state.questionData.selected])

    useEffect(()=>{
        console.log('QuestionIndex Initiated')
        getAllQuestions()
    },[])

    return(
        <ImageBackground blurRadius={2} source={scheme == "dark"? darkBackgroundImage:lightBackgroundImage } resizeMethod="scale" style={{flex:1, justifyContent: 'center'}}>
            <QuestionModalResult 
                visableStatus={questionModalResultVisable} 
                nextStep={()=> nextStep()} 
                findableAt={state.questionData?.selected?.location} 
                chosenAnswer={state.chosenButtonIndex} 
                rightAnswer={state.questionData?.rightAnswer} 
                questionId={state.questionData?.selectedId}  
            />
            <ModalLoading 
                errorMessage={state.errorMessage} 
                visableStatus={state.loading} 
                reload={getAllQuestions}
            />

            <ScrollView contentContainerStyle={{ paddingHorizontal: 25, backgroundColor: colors.mainBackground_opacity, flex:1}} showsVerticalScrollIndicator={false}>
                <View style={{marginTop:50}}>
                    <Pressable onPress={()=> navigation.goBack()}>
                        <MaterialCommunityIcons size={30} color={colors.white_500} name={'arrow-u-left-top'} />
                    </Pressable>
                </View>
                <View style={{alignSelf: 'center' , borderRadius:5 ,backgroundColor: colors.black_500, alignItems:'center', minWidth:160, height:35 ,justifyContent: 'center'}}>
                    <Text style={{color:colors.white_500, fontSize: ts.ts3,  }}>{state?.questionData?.selected?.questionCat && state.questionData.selected.questionCat }</Text>
                </View>
                <View style={{height:200, paddingVertical:20 , backgroundColor: colors.black_500 ,marginTop:10,paddingHorizontal:10, borderRadius:20 ,}}>
                    <ScrollView 
                        showsVerticalScrollIndicator={true} 
                        persistentScrollbar={true} 
                        contentContainerStyle={{flexGrow:1,justifyContent: 'center'}} 
                        style={{width: '100%',position: 'relative'}}
                    >
                        <Text style={{color: colors.white_500, textAlign: 'center', fontSize: ts.ts3, paddingHorizontal:5  }}>{state?.questionData?.selected?.title && state.questionData.selected.title}</Text>   
                    </ScrollView>
                </View>
                <View style={{flexWrap: 'wrap', flex:1, flexDirection: "row", marginTop:20}}>
                    {state?.questionData?.options && state.questionData.options.map((option,index)=>{
                        return (
                            <TouchableOpacity key={index} onPress={() => dispatch({type: ACTION_TYPES.QUESTIONS_CHOSEN_BUTTON_INDEX_UPDATE, payload:{key:index, isRightValue: option.isRight}}) }
                                disabled={state.checkAnswer}
                                style={[
                                    {width: '100%', borderRadius:10, paddingHorizontal:20, minHeight: 66, justifyContent:'center', paddingVertical:10,marginBottom:20,backgroundColor: colors.black_500 },
                                    state.chosenButtonIndex.key === index ? { borderWidth: 1 , borderColor: colors.white_500} : { borderWidth: 0},
                                ]}
                            >
                                <Text style={{color: colors.white_500, fontSize: ts.ts3}}>{option?.text && option.text}</Text>
                            </TouchableOpacity>
                        )
                    })}
                </View>
            </ScrollView>
            {state.chosenButtonIndex?.key != null  &&
                <TouchableOpacity disabled={state.checkAnswer} onPress={()=> nextStep()} style={{position: 'absolute' , bottom: 20 , right: 20, backgroundColor: colors.black_500, paddingHorizontal:20, paddingVertical:10 , alignItems: 'center', justifyContent: 'center', borderWidth: 0.5 , borderColor: colors.white_500, borderRadius:10}}>
                    <Text style={{color: colors.white_500, fontSize: ts.ts3}}>Check</Text>
                </TouchableOpacity>
            }
        </ImageBackground>
      
    )
}

One thing you may want to try to see if you can reproduce this situation locally is to use the --no-dev flag when running npx expo start. This should simulate the production bundle without needing to rebuild.

1 Like

Does this also work with an development build app?

It should

I fix the problem!
I am not sure what has caused it. But I was using a reducer in my questions logic because I have a lot of useState and wanted to clean that up. But I have now removed the reduce and used the useState instead even do it makes the code messier. But during the removal of the reducer and only using useState. An infinity loop appeared and after fixing that and making a production build. The problem didn’t appear anymore.

I am not sure if the problem didn’t appear because the reducer update was just slow enough to prevent the infinity loop in a production build. But I am glad that it is working properly now.

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.