Repository navigation
Conversation
… and iOS On Android the realtime listener called EventSink.error from the SDK's background executor. FlutterJNI throws off the main thread, so the error never reached Dart and the exception was swallowed by the SDK's task continuation. Post it to the main thread, as onUpdate already does. On iOS the error was only printed. Forward it to the stream as a FlutterError with the same code as Android, matching web, which already adds realtime errors to the stream.
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. |
…n with stable codes onConfigUpdated now reports realtime connection errors on Android and iOS; listen with onError. An error with no handler is reported as uncaught, which Crashlytics records as a fatal crash. The stream stays open after an error, and the SDKs keep retrying, so the same error can be reported again. Every platform now delivers these errors as FirebaseException(plugin: 'firebase_remote_config') with one of these codes: config-update-stream-error, config-update-message-invalid, config-update-not-fetched, config-update-unavailable or unknown. Before, Android and iOS sent a PlatformException with no details, which lost the native code, and web sent the raw JS FirebaseError. - Android and iOS put the code and message in the error details, and the method channel stream converts them with the package's existing convertPlatformException. - On iOS, the update error domain is checked first. Its codes overlap with RemoteConfigError (notFetched and throttled are both 8002). A failed fetch of an announced update keeps the codes fetch uses. - On web, onConfigUpdated goes through convertWebExceptions, which now maps the JS SDK's realtime codes to the same names. The onConfigUpdated dartdoc lists the codes. The docs snippet and the example now pass onError.
Description
Realtime errors never reached
onConfigUpdatedlisteners on Android or iOS. They now do, on every platform, as a typedFirebaseException.Android. The
ConfigUpdateListenercalledevents.error(...)directly fromonError. The SDK callsonErrorfrom its own executor:ConfigRealtimeHttpClient.propagateErrorsruns incontinueWith(scheduledExecutorService)(andConfigAutoFetchreports from the same executor).EventSink.errorsends a platform message, andFlutterJNI.dispatchPlatformMessagethrows when it is not on the main thread. So Dart never saw the error, and theRuntimeExceptionwas thrown into the SDK's task continuation, where it disappeared.onUpdatealready posts to the main thread;onErrornow does the same.iOS. The listener only printed the error. It now forwards it to the stream on the main queue.
Typed errors. Before this change, the error reached Dart as
PlatformException('firebase_remote_config', message, details: null)on Android and iOS, so the native code was lost, and as the raw JSFirebaseErroron web. Every platform now deliversFirebaseException(plugin: 'firebase_remote_config', code: ..., message: ...):FirebaseRemoteConfigException.CodeRemoteConfigUpdateErrorconfig-update-stream-errorCONFIG_UPDATE_STREAM_ERRORstreamErrorremoteconfig/stream-errorconfig-update-message-invalidCONFIG_UPDATE_MESSAGE_INVALIDmessageInvalidremoteconfig/update-message-invalidconfig-update-not-fetchedCONFIG_UPDATE_NOT_FETCHEDnotFetchedremoteconfig/update-not-fetchedconfig-update-unavailableCONFIG_UPDATE_UNAVAILABLEunavailableremoteconfig/realtime-unavailableunknownUNKNOWNdetails, as they already do for fetch errors. The method-channel stream converts them withreceiveGuardedBroadcastStreamand the package's existingconvertPlatformException.HTTP status code: 503.RemoteConfigUpdateErrorcodes 8001–8004 overlap withRemoteConfigErrorcodes 8001–8003, sonotFetched(8002) would otherwise have been reported asthrottled. A failed fetch of an announced update arrives inRemoteConfigErrorDomain, and keeps the codesfetchuses.onConfigUpdatednow goes through the package'sconvertWebExceptions. That function now maps the JS SDK's realtime codes to the names above. Its existing prefix strip expectsremote_config/but the JS SDK usesremoteconfig/; the fix applies only to the realtime codes.onConfigUpdateddartdoc lists the codes and says to passonError.Reproduced with the remote_config example and a local, uncommitted hook that calls the SDK's own error path (
ConfigRealtimeHttpClient.propagateErrorson itsscheduledExecutorServiceon Android,RCNConfigRealtime propagateErrors:on iOS), while a Dart test listens toonConfigUpdatedwithonError:mainpropagateErrorsthrowsRuntimeException: Methods marked with @UiThread must be executed on the main thread. Current thread: Firebase Blocking Thread #1, and Dart receives nothing within 30 sThe hook was needed because the realtime errors seen in practice are hard to produce on demand: with an invalid API key both SDKs keep retrying with backoff for minutes before they report one.
Notes for reviewers
ConfigAutoFetch). The "retries used up" error was already delivered on the main thread.android_unit_tests.yamlis also edited by fix(in_app_messaging): re-add Android event listeners after pause and report unheard display messages #18842, fix(storage): keep tasks alive when snapshotEvents listeners cancel, and scope Android task registries per engine #18839, fix(database,android): stop a pending transaction handler from blocking the database forever #18836 and fix(messaging,android): deliver every foreground message to onMessage exactly once #18831.Tests
firebase_remote_config_platform_interface,method_channel_firebase_remote_config_test.dart): a mocked native stream sends each code, then an update. The tests check that the stream emitsFirebaseExceptions with those codes and messages, that the update after an error is still delivered, and that an error without details is reported asunknown. All 3 fail on the previous commit (the stream emittedPlatformException) and pass here.firebase_remote_config_web/test/internals_test.dart, Chrome): JSFirebaseErrors with the four realtime codes come out ofconvertWebExceptionswith the stable codes, and the stream stays open. It fails before the fix (the code wasremoteconfig/stream-error) and passes here.FirebaseRemoteConfigPluginTest, run byandroid-unit-testsas:firebase_remote_config:testDebugUnitTest). The tests use anEventSinkthat throws off the main thread, as FlutterJNI does:onError_isForwardedToDartOnTheMainThread: fails onmainwith the FlutterJNI exception above.onError_reportsAStableCodeForEachNativeCodecovers everyCodevalue, andonError_keepsTheHttpStatusInTheMessagecovers the HTTP status. Both fail whendetailsisnull.onListen_registersAListenerThatReportsOnTheMainThreaddrivesonListenandonCancelthrough an injected registration function. It fails ifonListenbuilds its own listener or bypasses the main-thread executor.onUpdate_isForwardedToDartOnTheMainThreadguards the existing behaviour.Related Issues
May explain part of #17898: after the realtime connection gives up, apps were never told, so they could not listen again.
Checklist
///).melos run analyze) does not report any problems on my PR.Breaking Change
Apps that listen without
onErrorwill now see realtime errors as uncaught errors on Android and iOS, as web already did. On web, these errors are nowFirebaseExceptions instead of raw JS objects.