Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[🐛] Local Emulator not working in Expo #8049

Closed
2 of 10 tasks
KieranTH opened this issue Oct 6, 2024 · 10 comments
Closed
2 of 10 tasks

[🐛] Local Emulator not working in Expo #8049

KieranTH opened this issue Oct 6, 2024 · 10 comments
Labels

Comments

@KieranTH
Copy link

KieranTH commented Oct 6, 2024

Issue

When using a local emulator for Firebase with Expo & react-native-firebase it seems like there is an issue with the setup or connection between this lib and the emulator.

I have migrated my previous Firebase SDK Expo project to this library, as I ended up having to write my own hooks for most Firebase functionality, which became tedious - So based on Expo's documentation recommending this library, here I am!

I've managed to migrate all my code to the structures expected for this lib, sadly my trouble with the Emulator not running as I expected.

Previously the emulation setup was as simple as this - through a /src/config.ts.

import { initializeApp } from "firebase/app";
import { connectAuthEmulator, getAuth } from "firebase/auth";
import { connectDatabaseEmulator, getDatabase } from "firebase/database";
import { connectFirestoreEmulator, getFirestore } from "firebase/firestore";

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

const app = initializeApp(firebaseConfig);
const store = getFirestore(app);
const db = getDatabase(app);

const auth = getAuth(app);

if (__DEV__) {
  const ip =
    process.env.EXPO_PUBLIC_LOCAL_DEV !== "false"
      ? "127.0.0.1"
      : "192.168.XX.XX";
  console.log("Emulator IP: ", ip);
  connectAuthEmulator(auth, `http://${ip}:9099/`);
  connectDatabaseEmulator(db, ip, 9000);
  connectFirestoreEmulator(store, ip, 8080);
}

Which worked perfectly for the Firebase SDK - Now, there doesn't seem to be nearly any documentation for this lib in terms of initial setup that resembles something like this. - The closest I could find is here: https://rnfirebase.io/app/usage.

After hours of debugging and googling, I've tried numerous different setups, and this is what I currently have:

const firebaseConfig = {
  ...
};

let app;
if (firebase.apps.length === 0) {
  app = firebase.initializeApp(firebaseConfig);
} else {
  app = firebase.app();
}
const auth = firebase.auth();
const store = firebase.firestore();

if (__DEV__) {
  const ip =
    process.env.EXPO_PUBLIC_LOCAL_DEV !== "false"
      ? "localhost"
      : "192.168.50.27";
  console.log("Emulator IP: ", ip);
  auth.useEmulator(`http://${ip}:9099`);
  store.useEmulator(ip, 8080);
  // db.useEmulator(ip, 9000);
  // connectAuthEmulator(auth, `http://${ip}:9099/`);
  // connectDatabaseEmulator(db, ip, 9000);
  // connectFirestoreEmulator(store, ip, 8080);
}

In terms of the useEmulator function, I ran into numerous issues, ranging from the docs not mentioning anything about needing the firebase.X() needing to be ran beforehand, I only found that by looking at the source. Plus the fact that the db.useEmulator seems to cause an error where the database module is not found. But that's beyond this issue.

I would greately appreciate some guidance on how the initial setup for this should be orchestrated - As this lib attempts to mirror the SDK as much as possible it seems, i would have thought this would be as simple with the SDK.


Project Files

Javascript

Click To Expand

config.ts:

const firebaseConfig = {
  ...
};

let app;
if (firebase.apps.length === 0) {
  app = firebase.initializeApp(firebaseConfig);
} else {
  app = firebase.app();
}
const auth = firebase.auth();
const store = firebase.firestore();

if (__DEV__) {
  const ip =
    process.env.EXPO_PUBLIC_LOCAL_DEV !== "false"
      ? "localhost"
      : "192.168.50.27";
  console.log("Emulator IP: ", ip);
  auth.useEmulator(`http://${ip}:9099`);
  store.useEmulator(ip, 8080);
  // db.useEmulator(ip, 9000);
  // connectAuthEmulator(auth, `http://${ip}:9099/`);
  // connectDatabaseEmulator(db, ip, 9000);
  // connectFirestoreEmulator(store, ip, 8080);
}

firebase.json for react-native-firebase v6:

# N/A

iOS

Click To Expand

ios/Podfile:

  • I'm not using Pods
  • I'm using Pods and my Podfile looks like:
# N/A

AppDelegate.m:

// N/A


Android

Click To Expand

Have you converted to AndroidX?

  • my application is an AndroidX application?
  • I am using android/gradle.settings jetifier=true for Android compatibility?
  • I am using the NPM package jetifier for react-native compatibility?

android/build.gradle:

// N/A

android/app/build.gradle:

// N/A

android/settings.gradle:

// N/A

MainApplication.java:

// N/A

AndroidManifest.xml:

<!-- N/A -->


Environment

Click To Expand

react-native info output:

 OUTPUT GOES HERE
  • Platform that you're experiencing the issue on:
    • iOS
    • Android
    • iOS but have not tested behavior on Android
    • Android but have not tested behavior on iOS
    • Both
  • react-native-firebase version you're using that has this issue:
    • e.g. 21.0.0
  • Firebase module(s) you're using that has the issue:
    • e.g. Auth, Firestore, Database
  • Are you using TypeScript?
    • Y & ~5.3.3


@mikehardy
Copy link
Collaborator

mikehardy commented Oct 7, 2024

Hi there - not sure in depth about what exactly is going wrong, but perhaps just starting clean would be best, that is, just a simple App.js as a kind of playground to make sure things work


let app;
if (firebase.apps.length === 0) {
  app = firebase.initializeApp(firebaseConfig);

That shouldn't be necessary, this is a native wrapper and the default app is initialized natively (otherwise, things like crashlytics and analytics for startup events wouldn't work as they need to be working before the javascript layer exists...)

Rather than have a bunch of conditionals, why not just - for sake of testing - attempt a direct emulator connection.

You should be able to do firebase.firestore().useEmulator('localhost', 8080);

Then you should be connected to and able to issue any commands to firestore, but it will go to the emulator. Same for auth.

And that should really be it.

Here is - as an example - a full build demo from npx react-native init - https://github.com/mikehardy/rnfbdemo/blob/main/make-demo.sh - and the App file in it is completely simple yet does exercise an API (native - just to verify the module is alive - but could be any API) https://github.com/mikehardy/rnfbdemo/blob/main/App.tsx - you could do a useEmulator there and it would work

@KieranTH
Copy link
Author

Hi there - not sure in depth about what exactly is going wrong, but perhaps just starting clean would be best, that is, just a simple App.js as a kind of playground to make sure things work


let app;
if (firebase.apps.length === 0) {
  app = firebase.initializeApp(firebaseConfig);

That shouldn't be necessary, this is a native wrapper and the default app is initialized natively (otherwise, things like crashlytics and analytics for startup events wouldn't work as they need to be working before the javascript layer exists...)

Rather than have a bunch of conditionals, why not just - for sake of testing - attempt a direct emulator connection.

You should be able to do firebase.firestore().useEmulator('localhost', 8080);

Then you should be connected to and able to issue any commands to firestore, but it will go to the emulator. Same for auth.

And that should really be it.

Here is - as an example - a full build demo from npx react-native init - https://github.com/mikehardy/rnfbdemo/blob/main/make-demo.sh - and the App file in it is completely simple yet does exercise an API (native - just to verify the module is alive - but could be any API) https://github.com/mikehardy/rnfbdemo/blob/main/App.tsx - you could do a useEmulator there and it would work

Hey, I appreciate the reply!

I tested out the logic by removing any conditionals, and still no luck. Based on my experience it seems like there's a timeout error (pointing to an issue with the routing) - But I'm not getting an error from the store.collection(...) promise either.

I ended up trying this directly firebase.firestore().useEmulator("localhost", 8080); to no avail sadly.

The one major difference I can think of is that expo doesn't have an index file unlike your demo example with an App.tsx. I know the docs had stated that its required to set-up all the emulators etc before any queries are attempted, otherwise it wont work, so it could be a race condition. I'll try and play around with that, but as of this time, still no luck - The lack of errors is making it difficult too. Is there a way to read the response from the API at all? I.e headers, status codes etc?

Cheers

@mikehardy
Copy link
Collaborator

Is there a way to read the response from the API at all? I.e headers, status codes etc?

Not that I'm aware of, no
I believe if you try to set useEmulator after you have already done something non-emulator, it will splat a pretty big error in to the logs

Have you tried actually just using my make-demo.sh script as a test rig, and pointing it with useEmulator to a local emulator to make sure anything can work? Perhaps you have some local firewalling in place or something I dunno. But there's a minimum + but complete working example generator just ready to give you a baseline

@KieranTH
Copy link
Author

Is there a way to read the response from the API at all? I.e headers, status codes etc?

Not that I'm aware of, no I believe if you try to set useEmulator after you have already done something non-emulator, it will splat a pretty big error in to the logs

Have you tried actually just using my make-demo.sh script as a test rig, and pointing it with useEmulator to a local emulator to make sure anything can work? Perhaps you have some local firewalling in place or something I dunno. But there's a minimum + but complete working example generator just ready to give you a baseline

Yeah - I don't seem to be getting any massive errors, so doesn't seem like a race condition.

After playing around more with the core firebase console, it seems like my timeout issue is actually just the cold-start time from the service. So currently my app is always hitting the prod service.

After looking at the code for the useEmulator function, it's just passing the variables to the native firebase sdk function, so i'm struggling to see why it wouldn't work. Could it be that google had updated the usage of useEmulator to connectXEmulator?

Like I showed previously, connectXEmulator was working for me directly with the firebase SDK, so if this library is just a wrapper, that's quite strange!

I haven't had time to use your test rig script sadly as there's some config work I'd need to do for it as mentioned in the README.

It's a shame that firebase doesn't give much in terms of logging/info about whether it's being pointed at an emulator or not!

I'll play around and if i get a chance to use the script, I'll report back with if it works or not. My first assumption is that it wouldn't make much of a difference seeing as what I'm currently running in my app has a very similar initialisation structure as your App.tsx example.

Appreciate the direction and help anyway!

@KieranTH
Copy link
Author

KieranTH commented Oct 13, 2024

I had a chance to test out the demo script you had, and it... worked!

Sadly this points to another issue I guess 🤣 - In reality the only difference between the demo project, and my project is that one uses vanilla RN vs Expo. So really annoying to debug.

Here's all the changes I made to the demo App.tsx:

firestore().useEmulator("localhost", 8080);
 const [data, setData] = useState();

  useEffect(() => {
    firestore()
      .collection("users")
      .get()
      .then((doc) => {
        setData(doc);
      });
  }, []);

 console.log("data", data);

Which correctly returned the emulator data.

With the exact same setup in Expo in the initial _layout.tsx:

auth().useEmulator(`http://localhost:9099`);
firestore().useEmulator("localhost", 8080);

  useEffect(() => {
    firestore()
      .collection("users")
      .get()
      .then((doc) => {
        console.log("DATA", doc.docs);
      });
  }, []);

It's still hitting the Prod service... I'm honestly baffled!

If you have any suggestions I'd be happy to try them.

Thanks!

@KieranTH
Copy link
Author

Right I've managed to narrow it down - Something in my Expo app is the problem, as I made a from scratch Expo app, and it worked fine. So I'm gonna try and dissect it myself. I'll close this issue for now!

Thanks for the help mate

@KieranTH
Copy link
Author

Right - I've got it...

It seems like builds from EAS don't work with the emulators, you HAVE to use npx expo prebuild --clean and use the internal builds from that. Attempting to use a build made from EAS - even if the distribution is set as internal will fail.

May be worth putting that in the docs? Because there was a comment in the docs mentioning EAS, which is why I was under the impression it would work.

Glad to have sorted that though

@mikehardy
Copy link
Collaborator

Wow, that sounds so frustrating. Your own correctly-working code was basically "lying" to you (or not) that it was working (or not) based on build provider? That's a new one.

May be worth putting that in the docs? Because there was a comment in the docs mentioning EAS, which is why I was under the impression it would work.

Yes please! Every docs page has an edit button right at the top and just writing in whatever you think makes sense, following the standard github web UI text PR flow (quick and easy...) would work

@KieranTH
Copy link
Author

Wow, that sounds so frustrating. Your own correctly-working code was basically "lying" to you (or not) that it was working (or not) based on build provider? That's a new one.

May be worth putting that in the docs? Because there was a comment in the docs mentioning EAS, which is why I was under the impression it would work.

Yes please! Every docs page has an edit button right at the top and just writing in whatever you think makes sense, following the standard github web UI text PR flow (quick and easy...) would work

Sounds good! Before I do any docs changes - I'll do some more robust testing to see if there's any gotcha's with my assumption about EAS. Wanna be thorough before taking it further! Wild situation though to say the least!

Appreciate your help on the issue :)

@SKempin
Copy link

SKempin commented Oct 14, 2024

@KieranTH also experiencing this with EAS

#8060

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants