I have configured firebase to run locally for debugging using the emulator by following this link.
Now I want to be able to run my app connected to the localhost for debugging triggers as well. Is there a way to achieve this by configuring my flutter app to use the localhost?
My emulator is running as following:
My latest setup of flutter fire
void main() {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
const bool USE_EMULATOR = true;
if (USE_EMULATOR) {
// [Firestore | localhost:8080]
FirebaseFirestore.instance.settings = const Settings(
host: 'localhost:8080',
sslEnabled: false,
persistenceEnabled: false,
);
// [Authentication | localhost:9099]
await FirebaseAuth.instance.useEmulator('http://localhost:9099');
// [Storage | localhost:9199]
await FirebaseStorage.instance.useEmulator(
host: 'localhost',
port: 9199,
);
}
}
Make sure your host and port matches from firebase emulators:start
NOTE: in main.dart now you can always provide 'localhost'
await FirebaseAuth.instance.useEmulator('http://localhost:9099');
because it will change automagically to '10.0.2.2' if it is running on android
Long story short!
for latest guide follow https://firebase.flutter.dev/docs/firestore/usage#emulator-usage
Old but, Gold. Detailed config.(Outdated)
STEP 1 [setup firestore in flutter in main.dart]
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); <--- Important!
await Firestore.instance.settings(
host: '192.168.1.38:5002', <--- Make sure to put your local ip
sslEnabled: false); it will not work if you use 'localhost:5002'
Google it "how to find my local ip"
}
STEP 1 [setup firestore in flutter in main.dart] for newer version of firebase
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); <--- Important!
String host = Platform.isAndroid ? '10.0.2.2:5002' : 'localhost:5002';
await FirebaseFirestore.instance.settings = Settings(
host: host,
sslEnabled: false,
);
}
STEP 2 [init firebase project]
firebase init
STEP 3 [config firestore emulator e.g firebase.json]
"emulators": {
"ui": {
"enabled": true,
"host": "localhost",
"port": 4000
},
"functions": {
"port": 5001
},
"firestore": {
"host": "0.0.0.0", <------ Make sure to set it "0.0.0.0"
"port": 5002
},
}
STEP 4 [run emulators and flutter app]
firebase emulators:start
flutter run
Worked both on iOS simulator and Android emulators
P.S: try to restart firestore emulator or/and flutter app
Done!
BONUS [import export data to firestore emulator]
when you stop firestore emulator all data in firestore will be gone.
So maybe before stoping emulator if you want to continue from where
you left of you can export firestore emulator data like so
firebase emulators:export ../data (../data can be any path you want 😎)
to load exported data
firebase emulators:start --import ../data
you can save different states of your firestore emulator for different cases, example
firebase emulators:start --import ../initialData
firebase emulators:start --import ../otherStateData
❤️ Note for myself use dart for firebase functions ❤️
If you want to use dart for firebase functions you can follow this https://github.com/pulyaevskiy/firebase-functions-interop
one good thing I found for myself to detect if your function is running in emulator or production you can read more here
long story short
functions/index.js
export const prepopulateFirestoreEmulator = functions.https.onRequest(
(request, response) => {
if (process.env.FUNCTIONS_EMULATOR && process.env.FIRESTORE_EMULATOR_HOST) {
// TODO: prepopulate firestore emulator from 'yourproject/src/sample_data.json
response.send('Prepopulated firestore with sample_data.json!');
} else {
response.send(
"Do not populate production firestore with sample_data.json"
);
}
}
);
functions/index.dart
import 'package:firebase_functions_interop/firebase_functions_interop.dart';
import 'package:node_interop/node.dart';
import 'package:node_interop/util.dart';
void devPrepopulateCollections(ExpressHttpRequest request) {
var env =
new Map<String, String>.from(dartify(process.env)); // <-- important part
if (env.containsKey('FUNCTIONS_EMULATOR') &&
env.containsKey('FIRESTORE_EMULATOR_HOST')) {
// TODO: prepopulate firestore emulator from 'yourproject/src/sample_data.json
request.response
..write("Prepopulated firestore with sample_data.json!")
..close();
} else {
request.response
..write("Do not populate production firestore with sample_data.json")
..close();
}
}
After carefully going through the docs here, I got it working by configuring the host setting on the firestore instance:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:tracker/screens/screens.dart';
void main() async {
// This will set the app to communicate with localhost
await Firestore.instance.settings(host: "10.0.2.2:8080", sslEnabled: false);
runApp(AppSetupScreen());
}
Note: This will only work with emulator and not with physical device.
An add-on:
To make the Firebase emulators work with the physical devices
Work for both iOS and Android devices
1. Cloud functions setup:
in firebase.json
{
// ...other configs
"emulators": {
"functions": {
"port": 5001,
"host": "0.0.0.0" // must have
},
"firestore": {
"port": 8080,
"host": "0.0.0.0" // must have
},
}
}
2. Your Flutter app setup:
The IP address you use for cloud functions and firestore should be the same
// The FirebaseFunctions config
// ! You need to replace the placeholder with your IP address below:
FirebaseFunctions.instance.useFunctionsEmulator(origin: 'http://<YOUR_IP_ADDRESS>:5001');
// The Firestore config
// ! You need to replace the placeholder with your IP address below:
FirebaseFirestore.instance.settings = Settings(
host: '<YOUR_IP_ADDRESS>:8080',
sslEnabled: false,
persistenceEnabled: false,
);
for cloud_firestore: ^0.14.1+2, instead of using FirebaseFirestore.instance.settings use this -
FirebaseFunctions.instance.useFunctionsEmulator(
origin: "http://localhost:5001",
);
It internally handles setting up 10.0.2.2 if the device is android.
Your main block should look like the following
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseFunctions.instance.useFunctionsEmulator(
origin: "http://localhost:5001",
);
runApp(MyApp());
}
Looks like i've connected ios to localhost:8080, but db works very slow and I also didn't notice any logs in a file. #UsmanZaheer, can you please tell when did it write logs and was it working fast?
Steps:
firebase init
add links that have been created by ini to package.json in functions;
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
firebase emulators:start
in main() write
await Firestore.instance.settings(
host: 'http://localhost:8080',
sslEnabled: false,
persistenceEnabled: false,
timestampsInSnapshotsEnabled: true
).catchError((e) => print(e));
Adjustment for Flutter Web
addition to the correct answer by #Sultanmyrza
Platform requires dart:io/dart:html which are mutually exclussive so to check for the platform I use kIsWeb
FirebaseFirestore __firestore;
FirebaseFirestore get _firestore {
if (__firestore != null) {
return __firestore;
}
debugPrint('isFirebaseEmulator: $isFirebaseEmulator');
__firestore = FirebaseFirestore.instance;
if (isFirebaseEmulator) {
__firestore.settings = const Settings(
host: kIsWeb ? 'localhost:8080' : '10.0.2.2:8080',
sslEnabled: false,
);
}
return __firestore;
}
Latest Update: To connect flutter app to your local firebase emulator suite, follow this official instruction for configuration.
My init function looks like so:
init_firebase.dart
Future<void> initFirebase() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
if (!kReleaseMode) {
try {
await Future.wait([
FirebaseAuth.instance.useAuthEmulator('localhost', 9099),
FirebaseStorage.instance.useStorageEmulator('localhost', 9199),
]);
FirebaseFunctions.instance.useFunctionsEmulator('localhost', 5001);
FirebaseFirestore.instance.useFirestoreEmulator('localhost', 8080);
} catch (e) {}
}
}
By using !kReleaseMode, we don't have to have a boolean that we switch each time. It will just use emulator by default during development
By wrapping it in try-catch block we avoid some errors from hot reload
Your main.dart should look like this:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firestore.instance
.settings(
host: 'http://localhost:8080',
sslEnabled: false,
persistenceEnabled: false,
)
.catchError((e) => print(e));
//
// ...
//
runApp(App(...));
}
in your firebase.json file
"emulators": {
"firestore": {
"host": "localhost",
"port": 8080
},
...
}
you should also set the following in your terminal:
export FIRESTORE_EMULATOR_HOST=localhost:8080
and then run
firebase emulators:start
Related
Yes, this warning/error has been posted many times, but I have tried everything mentioned on StackOverflow and Github, but nothing is working for me.
Auth Emulator and FireStore Emulator up and running.
FireStore always gives a warning (with the confusing text "errored") when I try to use it read any data:
WARN [2022-09-13T22:23:28.341Z] #firebase/firestore: Firestore (9.9.4): Connection WebChannel transport errored: ...
... "j": {"g": [Circular]}, "l": "http://localhost:8080/google.firestore.v1.Firestore/Listen/channel", "o": undefined, "s": false, "v": true}, "type": "c"}
I also see this popup from time to time inconsistently:
ERROR [2022-09-13T22:43:13.780Z] #firebase/firestore: Firestore (9.9.4): Could not reach Cloud Firestore backend. Connection failed 1 times. Most recent error: FirebaseError: [code=unavailable]: The operation could not be completed
This typically indicates that your device does not have a healthy Internet connection at the moment. The client will operate in offline mode until it is able to successfully connect to the backend.
Here is the upfront boilerplate:
const firebaseConfig = {
...
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
connectAuthEmulator(auth, "http://localhost:9099");
const db = getFirestore(app);
connectFirestoreEmulator(db, "localhost", 8080);
A few of the many variations I have tried:
const db = getFirestore();
Every combination of the following flags alone and together:
const db = initializeFirestore(app, {useFetchStreams: false, experimentalForceLongPolling: true});
This is a React Native application. I am running the app inside the Android Emulator.
I have the above code in a Firebase.js that is imported by my App.js.
Anyone understand what the root cause is here?
Here is an example of trying to read:
const snap = getDocs(collection(db, "users")).then(snap => {
console.log(snap); // prints "[object Object]"
snap.forEach(user => {
console.log(`${user.id} => ${user.data()}`); // not reached
})
});
And that prints essentially nothing, as mentioned in comments, but no errors.
Here is a slightly different example that causes an error:
const randomDoc = getDoc(doc(db, "users", "o3ou4uwi9CYeM1dM8egg")).then(doc => {
console.log(randomDoc);
});
Output:
WARN Possible Unhandled Promise Rejection (id: 0):
FirebaseError: Failed to get document because the client is offline.
construct#[native code]
construct#[native code]
...
The Firebase Emulator GUI shows an empty requests page for Firestore, as if no requests have been made.
I have a collection named "users" in my Root, which contains a few dummy documents with {name: "foo"} fields, so I'm expecting the forEach above to print a few times.
There were 3 separate problems:
The Android emulator doesn't run on localhost / 127.0.0.1. It runs on 10.0.2.2. After changing that, I am able to successfully use the Auth Emulator, and the Firestore emulator is now registering my requests, but returning the error above FirebaseError: Failed to get document because the client is offline..
To solve the second error, you must edit firestore.rules and change everything to allow for testing:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
After (1) and (2) we are seemingly back to square 1 (transport error), but use this to init db: const db = initializeFirestore(app, {experimentalForceLongPolling: true});
Finally...success.
My init looks like this for now:
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
if (__DEV__) {
try {
if (Platform.OS === "android") {
connectAuthEmulator(auth, "http://10.0.2.2:9099");
} else {
connectAuthEmulator(auth, "http://localhost:9099");
}
} catch (error) {
console.error(error);
}
}
var db = null;
if (__DEV__) {
try {
if (Platform.OS === "android") {
db = initializeFirestore(app, {experimentalForceLongPolling: true});
connectFirestoreEmulator(db, "10.0.2.2", 8080);
} else {
db = getFirestore(app);
connectFirestoreEmulator(db, "localhost", 8080);
}
} catch (error) {
console.error(error);
}
}
else {
db = getFirestore(app);
}
Im trying to set up my testing environment to test my security fules with firestore. I've copied this code from https://firebase.google.com/docs/rules/unit-tests#before_you_run_the_emulator
let testEnv : RulesTestEnvironment;
beforeAll(async () => {
testEnv = await initializeTestEnvironment({
projectId: "demo-project-1234",
firestore: {
rules: fs.readFileSync('firestore.rules', 'utf8'),
},
});
});
However, I'm getting this error.
The host and port of the firestore emulator must be specified. (You may wrap the test script with firebase emulators:exec './your-test-script' to enable automatic discovery, or specify manually via initializeTestEnvironment({firestore: {host, port}}).
Anyone know how to solve this?
EDIT
I tried adding host and port to my running emulator like so
let testEnv : RulesTestEnvironment;
beforeAll(async () => {
testEnv = await initializeTestEnvironment({
projectId: "comment-section-e9c09",
firestore: {
rules: fs.readFileSync('firestore.rules', 'utf8'),
host:'localhost',
port:8080
},
});
});
Now it seems to be able to connect to my emulator, but when I try to fx clear the database like
test("sefse", () => {
testEnv.clearDatabase()
})
I get the following error
[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "Error: The host and port of the database emulator must be specified. (You may wrap the test script with 'firebase emulators:exec './your-test-script'' to enable automatic discovery, or specify manually via initializeTestEnvironment({database: {host, port}}).".] {
I give u a "mocha-based" starting point:
security.test.js:
import { readFileSync } from 'fs';
import { assertFails, assertSucceeds, initializeTestEnvironment } from "#firebase/rules-unit-testing";
import { doc, getDoc, setDoc } from "firebase/firestore";
let testEnv;
let unauthedDb;
describe("general database behaviour", () => {
before(async () => {
testEnv = await initializeTestEnvironment({
projectId: "demo-project-1234",
firestore: {
rules: readFileSync("firestore.rules", "utf8"),
host: "127.0.0.1",
port: "8080"
},
});
unauthedDb = testEnv.unauthenticatedContext().firestore();
});
after(async () => {
await testEnv.cleanup();
});
it("should let read anyone the database", async () => {
await testEnv.withSecurityRulesDisabled(async (context) => {
await setDoc(doc(context.firestore(), 'data/foobar'), { foo: 'bar' });
});
await assertSucceeds(getDoc(doc(unauthedDb, 'data/foobar')))
})
it("should not allow writing the database", async () => {
await assertFails(setDoc(doc(unauthedDb, '/data/foobar'), { something: "something" }))
})
})
Did you specify the firestore port and rules path in your firebase.json file like so?
"emulators": {
"firestore": {
"port": 8080
}
},
"firestore": {
"rules": "./rules/firestore.rules"
}
I have a forehead in ANGULAR - IONIC.
I have an API with NESTJS.
I hosted the API on the cloud functions of firebase with this tutorial: https://www.youtube.com/watch?v=IVy3Tm8iHQ0&ab_channel=Fireship
When I run the API locally (npm a start) it works perfectly!
I have a system for the cors which is the following (stored in main.ts):
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const options = new DocumentBuilder()
.setTitle('MY WEB API')
.setDescription('READ ONLY')
.setVersion('1.0')
.addTag('TAG')
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('api', app, document);
app.use(helmet());
const whitelist = ['http://localhost:8100/', 'http://localhost:8100', '*'];
app.enableCors({
origin: function (origin, callback) {
if (whitelist.indexOf(origin) !== -1) {
console.log('allowed cors for:', origin);
callback(null, true);
} else {
console.log('blocked cors for:', origin);
callback(new Error('Not allowed by CORS'));
}
},
allowedHeaders:
'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept, Observe',
methods: 'GET, OPTIONS',
credentials: true,
});
await app.listen(3000);
}
bootstrap();
Unfortunately when I run the api on the firebase environment (firebase serve --only functions) I get the following error:
(I checked, I can use it well with Postman)
I have tried many things to fix that:
Directly from the controller
#Get('ByName')
#Header('Access-Control-Allow-Origin', 'https://localhost:8100')
async findByName(#Query('name') name: string) {
return await this.personnesService.findByName(name);
}
Desactivate the cors
const app = await NestFactory.create(AppModule, {
logger: console,
cors: false,
});
Activate them like that
const app = await NestFactory.create(AppModule);
const corsOptions = {
methods: 'GET',
preflightContinue: true,
optionsSuccessStatus: 204,
credentials: true,
origin: ['http://localhost:8100/', 'http://localhost:8100'],
};
app.enableCors(corsOptions);
I've checked on the cloud functions, the code is up to date (you never know!) and the requests arrive well.
In fact, when I launch the request and I get the error, the API still executes the request (a console.log in the API allows to check it) but doesn't seem to return the result, from what I understand because of the cors.
In reality, it doesn't go through the main.ts either since this console.log doesn't appear. I don't know how to make it go through there.
How can I activate the cors (or deactivate them?) so I don't get the error anymore?
Some information about my versions:
NESTJS API :
"#nestjs/common": "^7.5.1",
"firebase-functions": "^3.13.0",
"#types/express": "^4.17.8",
"typescript": "^4.0.5
"node": "12"
My FRONT:
"#angular/common": "~10.0.0",
"#angular/core": "~10.0.0",
"ts-node": "~8.3.0",
"typescript": "~3.9.5"
I now understand my mistake. My configuration is correct, but it's not in the right place to make it work in production!
In a classic NESTJS application, the main.ts is used to launch the application. In my case, having followed this tutorial https://fireship.io/snippets/setup-nestjs-on-cloud-functions/, the index.ts replaces the main.ts.
So the solution is to move my configuration to the index.ts!
Here is my new function.
const server = express();
export const createNestServer = async (expressInstance) => {
const app = await NestFactory.create(
AppModule,
new ExpressAdapter(expressInstance),
);
const corsOptions = {
methods: 'GET',
preflightContinue: true,
optionsSuccessStatus: 204,
credentials: true,
origin: ['http://localhost:8100/', 'http://localhost:8100'],
};
app.enableCors(corsOptions);
return app.init();
};
createNestServer(server)
.then((v) => console.log('Nest Ready'))
.catch((err) => console.error('Nest broken', err));
export const api = functions.https.onRequest(server);
Don't forget to build before relaunching!
npm run build
firebase serve --only functions
firebase deploy --only functions
I am trying to follow the firebase docs to test security rules (Build unit tests), specifically the video.
But an error does not allow me to continue... this.
My emulators are ok... but anything is not working because with the emulators activated, I get the same error as before. (...the client is offline)
Anyone can help me?
The testing code is the same as in the video (I have changed the project id with 'xxxxxxxxxxxxxx' to keep it private):
const assert = require('assert')
const firebase = require('#firebase/testing')
const MY_PROJECT_ID = 'xxxxxxxxxxxxxx'
describe('Our social app', () => {
it('Understands basic addition', () => {
assert.strictEqual(2 + 2, 4)
})
it('Can read items in the read-only collection', async () => {
const db = firebase.initializeTestApp({ projectId: MY_PROJECT_ID }).firestore()
const testDoc = db.collection('readonly').doc('testDoc')
await firebase.assertSucceeds(testDoc.get())
})
})
I had changed Firestore port to 8090 in firebase.json
"emulators": {
"functions": {
"port": 5001
},
"firestore": {
"port": 8090
},
But the method initializaTestApp sets 8080 as default. It was invoked in the testing file test.js
const db = firebase.initializeTestApp({ projectId: MY_PROJECT_ID }).firestore()
A console.log of db shows the problem (port 8080)
I've set some config variables using the command firebase functions:config:set algolia.appid="app_id" algolia.apikey="api_key", but how do I utilize them in my Flutter app? I have firebase_core installed.
In TypeScript you would write the following:
import * as admin from 'firebase-admin';
admin.initializeApp();
const env = functions.config();
console.log(env.algolia.appid);
But what about in Dart and Flutter?
Thanks
The configuration variables you set through firebase functions:config:set are (as the command implies) available only in Cloud Functions. They're not in any way propagated to client-side application by this command. In general that'd be an anti-pattern, as such configuration variables are often used for keeping trusted credentials.
If you have a use-case where the value needs to be available in the client-side application too, you have a few ways to do that:
Create an additional Cloud Functions endpoint where you expose the value of the configuration variable. Typically this would be a HTTPS or Callable function, which you then call from your client-side code.
Push the value into another place where your application code can read it from at the same time that you call firebase functions:config:set. This could be a configuration file of your app, or even a .dart file that you generate.
I also ran into this problem and found myself on this S.O. thread. I tried following Frank van Puffelen's suggestion above.
In functions/.runtimeconfig.json:
{
"algolia": {
"appid": "ID",
"apikey": "KEY"
},
"webmerge": {
"key": "KEY",
"secret": "SECRET",
"stashkey": "STASH_KEY"
},
}
In functions/index.ts:
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
. . .
const cors = require('cors')({origin: true})
const envObj = functions.config()
. . .
export const getEnv = functions.https.onRequest((req, resp) => {
cors(req, resp, () => resp.status(200).send(JSON.stringify(envObj)));
});
. . .
NOTE: I used the cors package to get around CORS errors when working locally. I would get these errors when localhost:5000 (Emulator hosting) called localhost:5001 (Emulator functions).
In web_flutter/main.dart:
Future<Map<String, dynamic>> fetchEnv(String functionsURL) async {
var response = await http.get('${functionsURL}/getEnv');
return json.decode(response.body);
}
Future<void> main() async {
try {
var functionsURL = 'FUNCTIONS_URL';
var app = fb.initializeApp(firebase app details);
if (window.location.hostname == 'localhost') {
app.firestore().settings(Settings(
host: 'localhost:8080',
ssl: false,
));
functionsURL = 'http://localhost:5001';
}
var env = await fetchEnv(functionsURL);
var searchClient = Algolia.init(
applicationId: env['algolia']['appid'],
apiKey: env['algolia']['apikey']);
runApp(MyApp(
repository: Repository(app.firestore(), searchClient),
authentication: Authentication(app.auth())));
} on fb.FirebaseJsNotLoadedException catch (e) {
print(e);
}
}
Once I confirmed that this was working locally, I was able to use firebase functions:config:set to set this data in the live Functions environment and deploy my updated hosting and functions with firebase deploy.