A Flutter app's callable works against the local emulator but fails in production with not-found; how do cloud_functions regions and instanceFor explain it?
answer
- default region us-central1
- instanceFor(region: ...)
- one cached instance per app and region
- httpsCallableFromUrl for a full URL
- emulator: localhost becomes 10.0.2.2
basics
~10 sFirebaseFunctions.instance targets us-central1, so a function deployed elsewhere is not found; use FirebaseFunctions.instanceFor(region: 'europe-west1'), or httpsCallableFromUrl, while useFunctionsEmulator routes every call to the local emulator, whatever the region.
solid answer
~40 s`FirebaseFunctions.instance` is shorthand for `instanceFor(app: Firebase.app())` with region `us-central1`. A callable deployed to `europe-west1` therefore needs `FirebaseFunctions.instanceFor(region: 'europe-west1').httpsCallable('validateCoupon')`. Otherwise the client asks the default region, where no function of that name exists, and the call fails, typically with `not-found`. Locally the bug hides, because `useFunctionsEmulator('localhost', 5001)` points that instance at the emulator. On Android the plugin maps `localhost` to `10.0.2.2` unless you pass `automaticHostMapping: false`. Instances are cached per app and region, so each region gets its own object, and emulator settings apply per instance. For second-generation functions you can also call `httpsCallableFromUrl(url)` with the function's full URL. Keep the region in one constant shared with the backend, and wire the emulator only in debug builds.
code
dart · 23 linesimport 'package:cloud_functions/cloud_functions.dart';
import 'package:flutter/foundation.dart';
const functionsRegion = 'europe-west1'; // must match the deployed functions
FirebaseFunctions createFunctions() {
final functions = FirebaseFunctions.instanceFor(region: functionsRegion);
if (kDebugMode) {
// Same instance the app uses; Android maps localhost to 10.0.2.2.
functions.useFunctionsEmulator('localhost', 5001);
}
return functions;
}
class CouponRepository {
CouponRepository(this._functions);
final FirebaseFunctions _functions;
Future<Object?> validate(String code) async {
final result = await _functions.httpsCallable('validateCoupon').call({'code': code});
return result.data;
}
}go deeper
Remember that FirebaseFunctions.instance uses us-central1, and that a function in another region needs FirebaseFunctions.instanceFor(region: ...).
Explain instance caching per app and region, why emulator routing is per instance, and the automatic localhost to 10.0.2.2 mapping on Android.
Show how you prevent drift with one injected instance and a shared region constant, and how a release-mode smoke test catches what the emulator hides.
Weigh region choice against latency, data residency and multi-region failover, and how the client learns endpoints without a new app release for every backend move.
## How the client decides where to call A **callable** is addressed by project, region and function name. In the **cloud_functions** plugin, the region lives on the `FirebaseFunctions` instance, not on the call: - `FirebaseFunctions.instance` returns `instanceFor(app: Firebase.app())`, and the region defaults to **`us-central1`**. - `FirebaseFunctions.instanceFor({FirebaseApp? app, String? region})` returns an instance for any app and region pair. - Instances are **cached** under a key made of the app name and the region, so asking twice for `europe-west1` returns the same object. When a team deploys functions close to its users, say `europe-west1`, and the app keeps using `FirebaseFunctions.instance`, the request goes to `us-central1`. No function by that name exists there, so the call fails. The usual symptom is a `FirebaseFunctionsException` with code `not-found`. ## Why the emulator hides it `useFunctionsEmulator(host, port)` rewrites the instance's origin to `http://host:port`, and the emulator serves every function the project defines. Local testing never exercises regional routing, so the region bug appears only in a build that talks to production. Two more emulator details: 1. On Android (not web), the plugin maps `localhost` or `127.0.0.1` to `10.0.2.2`, the emulator's alias for the host machine. It prints a message when it does. Pass `automaticHostMapping: false` if you run on a physical device against your machine's LAN address. 2. The emulator setting belongs to one **instance**. If code calls `useFunctionsEmulator` on `FirebaseFunctions.instance` but the call site uses `instanceFor(region: 'europe-west1')`, that regional instance still talks to production. ## The fixes | Approach | Code | When | |---|---|---| | Regional instance | `FirebaseFunctions.instanceFor(region: 'europe-west1')` | the usual fix | | Full URL | `httpsCallableFromUrl('https://...')` or `httpsCallableFromUri(uri)` | second-generation functions, or a custom domain | | One provider | a single `FirebaseFunctions` object injected everywhere | prevents drift between call sites | The documentation for `httpsCallableFromUrl` says the URL should be that of a second-generation callable. ## Designing it so the bug cannot recur - Put the region in **one constant**, and keep it next to, or generated from, the backend's deployment config. - Create the `FirebaseFunctions` object once, in your dependency wiring, and pass it to the repositories that call functions. No call site should reach for `FirebaseFunctions.instance` directly. - Wire `useFunctionsEmulator` on **that same object**, only in debug or emulator flavors. - Add one release-mode smoke test that calls a trivial callable in the real region. Per-flavor Firebase projects, meaning which project a build talks to, are part of project wiring. Choosing and deploying the function's region is server-side work. The client's responsibility is to address the region the function actually lives in. ## Diagnosing from the exception - `not-found`: wrong name or wrong region. Compare the deployed name and region with the instance. - `unavailable` or a connection error in debug: the emulator is not running, or the Android host mapping does not fit your setup. - `unauthenticated` only in production: the emulator was permissive, or no user is signed in on that build.
- The team calls useFunctionsEmulator on FirebaseFunctions.instance, but regional calls still hit production. Why?Emulator routing is stored on each `FirebaseFunctions` instance, and instances are cached per app and region. `FirebaseFunctions.instance` is the `us-central1` instance, while `instanceFor(region: 'europe-west1')` is a different object with its own origin. Configure the emulator on the instance the call sites actually use.
- When would you use httpsCallableFromUrl instead of instanceFor(region:)?When you have the function's full URL, typically for second-generation functions or a URL that does not follow the default project and region pattern. It sidesteps region naming entirely, but it hard-codes an endpoint, so keep the URL in configuration rather than scattered through call sites.
saying these in an interview costs you the question
- FirebaseFunctions.instance automatically finds the region a function is deployed in.
- The region is passed per call in HttpsCallableOptions.
- useFunctionsEmulator on one instance redirects every region's instance.
- On an Android emulator, localhost reaches the host machine without any mapping.
- A region mismatch surfaces as a timeout rather than an error code.