Is there an existing issue for this?
Which plugins are affected?
Cloud Functions
Which platforms are affected?
Android
Description
On a device whose locale is Turkish (tr), FirebaseFunctionsException.code comes back with a dotless ı (U+0131) instead of i:
[firebase_functions/faıled-precondıtıon] AI_CONSENT_REQUIRED
Note faıled-precondıtıon. Because of this, the documented comparison
if (e.code == 'failed-precondition') { ... }
is false on those devices, and every switch (e.code) silently falls through to its default branch.
Root cause — packages/cloud_functions/cloud_functions/android/src/main/kotlin/io/flutter/plugins/firebase/functions/FlutterFirebaseFunctionsPlugin.kt:
details["code"] = code.replace("_", "-").lowercase(Locale.getDefault())
code is a FirebaseFunctionsException.Code enum name such as FAILED_PRECONDITION. lowercase(Locale.getDefault()) uses the device locale, and in Turkish and Azerbaijani 'I'.lowercase() is 'ı', not 'i'.
Suggested fix — one word:
details["code"] = code.replace("_", "-").lowercase(Locale.ROOT)
This matches what firebase_auth already does in its own single lowercase call (PhoneNumberVerificationStreamHandler.java uses .toLowerCase(Locale.ROOT)).
Affected codes — every canonical code containing the letter i: failed-precondition, invalid-argument, permission-denied, unauthenticated, internal, unavailable, deadline-exceeded, already-exists, unimplemented.
Codes without i (aborted, cancelled, not-found, data-loss, out-of-range, resource-exhausted, unknown) are unaffected, which is why the bug looks intermittent: an app handles some rejections correctly and silently mis-handles the rest.
Platform divergence
- Android — affected. The Kotlin line above.
- iOS — not affected.
FirebaseFunctionsPlugin.swift's mapFunctionsErrorCode(_:) returns hardcoded string literals, so no case conversion happens.
firebase_auth — not affected. The Android side passes getErrorCode() through unchanged; the conversion happens in Dart (firebase_auth_platform_interface/lib/src/method_channel/utils/exception.dart), and Dart's String.toLowerCase() performs Unicode default case mapping and is locale-independent by specification.
cloud_firestore — not affected. No case conversion. Observed on the same device in the same session: cloud_firestore/unavailable, with a normal i.
So the same app, same account and same server response behave differently on Android and iOS purely because of the phone's language setting.
Why this matters beyond one app: comparing the error code is the documented way to branch on callable failures, and unit tests do not catch this — test runners use a neutral locale, so the mapping passes everywhere except on users' phones. For an app whose primary market is Turkey this is not an edge case, it is the default path. In our case a server-side consent rejection reached the user as a generic "service unavailable" message instead of the one telling them which setting to enable.
Reproducing the issue
- Set the device language to Turkish.
- Deploy a callable that throws:
exports.throwing = functions.https.onCall(() => {
throw new functions.https.HttpsError('failed-precondition', 'X');
});
- Call it and print the code:
try {
await FirebaseFunctions.instance.httpsCallable('throwing').call();
} on FirebaseFunctionsException catch (e) {
debugPrint(e.code); // faıled-precondıtıon
debugPrint('${e.code == "failed-precondition"}'); // false
}
Expected: e.code == 'failed-precondition' is true on every device locale.
Actual: false on Turkish and Azerbaijani devices.
Firebase Core version
4.12.1
Flutter Version
3.44.4 (stable)
Relevant Log Output
[firebase_functions/faıled-precondıtıon] AI_CONSENT_REQUIRED
Flutter dependencies
cloud_functions: 6.3.5
firebase_core: 4.12.1
firebase_auth: 6.5.6 (unaffected — conversion happens in Dart)
Additional context and comments
Observed on Android 16 (SDK 36), Samsung tablet, device locale tr-TR.
Workaround for anyone hitting this before the fix lands: normalise the code before comparing.
String normalizeCallableCode(String? code) =>
(code ?? '').toLowerCase().replaceAll('ı', 'i').replaceAll('İ', 'i');
Note the fix on the app side cannot be "lowercase it correctly" — Dart's toLowerCase() is already locale-independent; the string arrives already mangled from the platform channel, so the workaround has to recognise the mangled form.
Is there an existing issue for this?
Which plugins are affected?
Cloud Functions
Which platforms are affected?
Android
Description
On a device whose locale is Turkish (
tr),FirebaseFunctionsException.codecomes back with a dotlessı(U+0131) instead ofi:Note
faıled-precondıtıon. Because of this, the documented comparisonis
falseon those devices, and everyswitch (e.code)silently falls through to its default branch.Root cause —
packages/cloud_functions/cloud_functions/android/src/main/kotlin/io/flutter/plugins/firebase/functions/FlutterFirebaseFunctionsPlugin.kt:codeis aFirebaseFunctionsException.Codeenum name such asFAILED_PRECONDITION.lowercase(Locale.getDefault())uses the device locale, and in Turkish and Azerbaijani'I'.lowercase()is'ı', not'i'.Suggested fix — one word:
This matches what
firebase_authalready does in its own single lowercase call (PhoneNumberVerificationStreamHandler.javauses.toLowerCase(Locale.ROOT)).Affected codes — every canonical code containing the letter
i:failed-precondition,invalid-argument,permission-denied,unauthenticated,internal,unavailable,deadline-exceeded,already-exists,unimplemented.Codes without
i(aborted,cancelled,not-found,data-loss,out-of-range,resource-exhausted,unknown) are unaffected, which is why the bug looks intermittent: an app handles some rejections correctly and silently mis-handles the rest.Platform divergence
FirebaseFunctionsPlugin.swift'smapFunctionsErrorCode(_:)returns hardcoded string literals, so no case conversion happens.firebase_auth— not affected. The Android side passesgetErrorCode()through unchanged; the conversion happens in Dart (firebase_auth_platform_interface/lib/src/method_channel/utils/exception.dart), and Dart'sString.toLowerCase()performs Unicode default case mapping and is locale-independent by specification.cloud_firestore— not affected. No case conversion. Observed on the same device in the same session:cloud_firestore/unavailable, with a normali.So the same app, same account and same server response behave differently on Android and iOS purely because of the phone's language setting.
Why this matters beyond one app: comparing the error code is the documented way to branch on callable failures, and unit tests do not catch this — test runners use a neutral locale, so the mapping passes everywhere except on users' phones. For an app whose primary market is Turkey this is not an edge case, it is the default path. In our case a server-side consent rejection reached the user as a generic "service unavailable" message instead of the one telling them which setting to enable.
Reproducing the issue
Expected:
e.code == 'failed-precondition'istrueon every device locale.Actual:
falseon Turkish and Azerbaijani devices.Firebase Core version
4.12.1
Flutter Version
3.44.4 (stable)
Relevant Log Output
Flutter dependencies
Additional context and comments
Observed on Android 16 (SDK 36), Samsung tablet, device locale
tr-TR.Workaround for anyone hitting this before the fix lands: normalise the code before comparing.
Note the fix on the app side cannot be "lowercase it correctly" — Dart's
toLowerCase()is already locale-independent; the string arrives already mangled from the platform channel, so the workaround has to recognise the mangled form.