skip to content

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?

level: seniorimportance: should knowfreq 28%

answer

  1. default region us-central1
  2. instanceFor(region: ...)
  3. one cached instance per app and region
  4. httpsCallableFromUrl for a full URL
  5. emulator: localhost becomes 10.0.2.2

basics

~10 s

FirebaseFunctions.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 lines
dart
import '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

for a junior

Remember that FirebaseFunctions.instance uses us-central1, and that a function in another region needs FirebaseFunctions.instanceFor(region: ...).

for a middle

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.

for a senior

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.

for a principal

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.