firebase / flutterfire

🔥 A collection of Firebase plugins for Flutter apps.
https://firebase.google.com/docs/flutter/setup
BSD 3-Clause "New" or "Revised" License
8.61k stars 3.95k forks source link

[firebase_database]: Hot restarting flutter app does not fire onDisconnect updates #13195

Closed muzzah closed 1 week ago

muzzah commented 3 weeks ago

Is there an existing issue for this?

Which plugins are affected?

Database

Which platforms are affected?

iOS

Description

Setting a trigget using DatabaseReference.child("...sompath...").onDisconnect().update({key : value}) does not fire when hot restarting the app. Im also seeing this behavior when backgrounding the application and the connections are dropped.

Reproducing the issue

Call ``DatabaseReference.child("...sompath...").onDisconnect().update({key : value})``` on some reference Hot Restart the app

Firebase Core version

3.3.0

Flutter Version

11.0.4

Relevant Log Output

No logging available since the events are not fired

Flutter dependencies

Expand Flutter dependencies snippet
```yaml Dart SDK 3.5.0 Flutter SDK 3.24.0 siia 1.0.0+1 dependencies: - animated_bottom_navigation_bar 0.3.3 [flutter] - animations 2.0.11 [flutter] - cloud_firestore 5.2.1 [cloud_firestore_platform_interface cloud_firestore_web collection firebase_core firebase_core_platform_interface flutter meta] - crypto 3.0.3 [typed_data] - cupertino_icons 1.0.8 - dotted_border 2.1.0 [flutter path_drawing] - dropdown_button2 2.3.9 [flutter meta] - equatable 2.0.5 [collection meta] - firebase_analytics 11.2.1 [firebase_analytics_platform_interface firebase_analytics_web firebase_core firebase_core_platform_interface flutter] - firebase_auth 5.1.4 [firebase_auth_platform_interface firebase_auth_web firebase_core firebase_core_platform_interface flutter meta] - firebase_core 3.3.0 [firebase_core_platform_interface firebase_core_web flutter meta] - firebase_crashlytics 4.0.4 [firebase_core firebase_core_platform_interface firebase_crashlytics_platform_interface flutter stack_trace] - firebase_database 11.0.4 [firebase_core firebase_core_platform_interface firebase_database_platform_interface firebase_database_web flutter] - fl_chart 0.68.0 [equatable flutter] - flutter 0.0.0 [characters collection material_color_utilities meta vector_math sky_engine] - flutter_intro 2.3.1 [flutter] - flutter_keyboard_visibility 6.0.0 [meta flutter_keyboard_visibility_platform_interface flutter_keyboard_visibility_linux flutter_keyboard_visibility_macos flutter_keyboard_visibility_web flutter_keyboard_visibility_windows flutter] - flutter_localizations 0.0.0 [flutter intl characters clock collection material_color_utilities meta path vector_math] - flutter_modular 5.0.3 [flutter_modular_annotations modular_core meta flutter] - flutter_redux 0.10.0 [redux flutter] - flutter_rounded_progress_bar 0.3.2 [flutter] - flutter_translate 3.1.0 [flutter universal_io] - form_field_validator 1.1.0 [flutter intl] - get_it 7.7.0 [async collection meta] - google_sign_in 6.2.1 [flutter google_sign_in_android google_sign_in_ios google_sign_in_platform_interface google_sign_in_web] - http 0.13.6 [async http_parser meta] - im_stepper 1.0.1+1 [flutter] - introduction_screen 3.1.14 [flutter collection dots_indicator flutter_keyboard_visibility] - json_annotation 4.9.0 [meta] - line_awesome_icons 1.0.4+2 [flutter] - modal_bottom_sheet 3.0.0 [flutter] - optional 6.1.0+1 [collection] - path 1.9.0 - path_provider 2.1.4 [flutter path_provider_android path_provider_foundation path_provider_linux path_provider_platform_interface path_provider_windows] - quiver 3.2.1 [matcher] - random_string 2.3.1 - scroll_to_index 3.0.1 [flutter] - shared_preferences 2.3.1 [flutter shared_preferences_android shared_preferences_foundation shared_preferences_linux shared_preferences_platform_interface shared_preferences_web shared_preferences_windows] - simple_animations 5.0.2 [flutter collection] - supercharged 2.1.1 [supercharged_dart flutter] - universal_platform 1.1.0 dev dependencies: - build_runner 2.4.12 [analyzer args async build build_config build_daemon build_resolvers build_runner_core code_builder collection crypto dart_style frontend_server_client glob graphs http_multi_server io js logging meta mime package_config path pool pub_semver pubspec_parse shelf shelf_web_socket stack_trace stream_transform timing watcher web_socket_channel yaml] - cloud_firestore_platform_interface 6.3.1 [_flutterfire_internals collection firebase_core flutter meta plugin_platform_interface] - firebase_analytics_platform_interface 4.2.1 [_flutterfire_internals firebase_core flutter meta plugin_platform_interface] - firebase_auth_platform_interface 7.4.3 [_flutterfire_internals collection firebase_core flutter meta plugin_platform_interface] - flutter_test 0.0.0 [flutter test_api matcher path fake_async clock stack_trace vector_math leak_tracker_flutter_testing async boolean_selector characters collection leak_tracker leak_tracker_testing material_color_utilities meta source_span stream_channel string_scanner term_glyph vm_service] - integration_test 0.0.0 [flutter flutter_driver flutter_test path vm_service async boolean_selector characters clock collection fake_async file leak_tracker leak_tracker_flutter_testing leak_tracker_testing matcher material_color_utilities meta source_span stack_trace stream_channel string_scanner sync_http term_glyph test_api vector_math webdriver] - json_serializable 6.8.0 [analyzer async build build_config collection json_annotation meta path pub_semver pubspec_parse source_gen source_helper] - mockito 5.4.4 [analyzer build code_builder collection dart_style matcher meta path source_gen test_api] - test 1.25.7 [analyzer async boolean_selector collection coverage http_multi_server io js matcher node_preamble package_config path pool shelf shelf_packages_handler shelf_static shelf_web_socket source_span stack_trace stream_channel test_api test_core typed_data web_socket_channel webkit_inspection_protocol yaml] transitive dependencies: - _fe_analyzer_shared 72.0.0 [meta] - _flutterfire_internals 1.3.40 [collection firebase_core firebase_core_platform_interface flutter meta] - _macros 0.3.2 - analyzer 6.7.0 [_fe_analyzer_shared collection convert crypto glob macros meta package_config path pub_semver source_span watcher yaml] - args 2.5.0 - async 2.11.0 [collection meta] - boolean_selector 2.1.1 [source_span string_scanner] - build 2.4.1 [analyzer async convert crypto glob logging meta package_config path] - build_config 1.1.1 [checked_yaml json_annotation path pubspec_parse yaml] - build_daemon 4.0.2 [built_collection built_value crypto http_multi_server logging path pool shelf shelf_web_socket stream_transform watcher web_socket_channel] - build_resolvers 2.4.2 [analyzer async build collection convert crypto graphs logging package_config path pool pub_semver stream_transform yaml] - build_runner_core 7.3.2 [async build build_config build_resolvers collection convert crypto glob graphs json_annotation logging meta package_config path pool timing watcher yaml] - built_collection 5.1.1 - built_value 8.9.2 [built_collection collection fixnum meta] - characters 1.3.0 - checked_yaml 2.0.3 [json_annotation source_span yaml] - clock 1.1.1 - cloud_firestore_web 4.1.1 [_flutterfire_internals cloud_firestore_platform_interface collection firebase_core firebase_core_web flutter flutter_web_plugins] - code_builder 4.10.0 [built_collection built_value collection matcher meta] - collection 1.18.0 - convert 3.1.1 [typed_data] - coverage 1.9.0 [args glob logging package_config path source_maps stack_trace vm_service] - dart_style 2.3.6 [analyzer args collection path pub_semver source_span] - dots_indicator 2.1.2 [flutter] - fake_async 1.3.1 [clock collection] - ffi 2.1.2 - file 7.0.0 [meta path] - firebase_analytics_web 0.5.9+1 [_flutterfire_internals firebase_analytics_platform_interface firebase_core firebase_core_web flutter flutter_web_plugins] - firebase_auth_web 5.12.5 [firebase_auth_platform_interface firebase_core firebase_core_web flutter flutter_web_plugins http_parser meta web] - firebase_core_platform_interface 5.2.0 [collection flutter flutter_test meta plugin_platform_interface] - firebase_core_web 2.17.4 [firebase_core_platform_interface flutter flutter_web_plugins meta web] - firebase_crashlytics_platform_interface 3.6.40 [_flutterfire_internals collection firebase_core flutter meta plugin_platform_interface] - firebase_database_platform_interface 0.2.5+40 [_flutterfire_internals collection firebase_core flutter meta plugin_platform_interface] - firebase_database_web 0.2.5+12 [collection firebase_core firebase_core_web firebase_database_platform_interface flutter flutter_web_plugins] - fixnum 1.1.0 - flutter_driver 0.0.0 [file flutter flutter_test fuchsia_remote_debug_protocol path meta vm_service webdriver async boolean_selector characters clock collection leak_tracker leak_tracker_flutter_testing leak_tracker_testing matcher material_color_utilities platform process source_span stack_trace stream_channel string_scanner sync_http term_glyph test_api vector_math] - flutter_keyboard_visibility_linux 1.0.0 [flutter_keyboard_visibility_platform_interface flutter] - flutter_keyboard_visibility_macos 1.0.0 [flutter_keyboard_visibility_platform_interface flutter] - flutter_keyboard_visibility_platform_interface 2.0.0 [flutter meta plugin_platform_interface] - flutter_keyboard_visibility_web 2.0.0 [flutter_keyboard_visibility_platform_interface flutter_web_plugins flutter] - flutter_keyboard_visibility_windows 1.0.0 [flutter_keyboard_visibility_platform_interface flutter] - flutter_modular_annotations 0.0.2 [flutter] - flutter_web_plugins 0.0.0 [flutter characters collection material_color_utilities meta vector_math] - frontend_server_client 4.0.0 [async path] - fuchsia_remote_debug_protocol 0.0.0 [process vm_service file meta path platform] - glob 2.1.2 [async collection file path string_scanner] - google_identity_services_web 0.3.1+4 [meta web] - google_sign_in_android 6.1.28 [flutter google_sign_in_platform_interface] - google_sign_in_ios 5.7.6 [flutter google_sign_in_platform_interface] - google_sign_in_platform_interface 2.4.5 [flutter plugin_platform_interface] - google_sign_in_web 0.12.4+2 [flutter flutter_web_plugins google_identity_services_web google_sign_in_platform_interface http web] - graphs 2.3.2 [collection] - http_multi_server 3.2.1 [async] - http_parser 4.0.2 [collection source_span string_scanner typed_data] - intl 0.19.0 [clock meta path] - io 1.0.4 [meta path string_scanner] - js 0.7.1 - leak_tracker 10.0.5 [clock collection meta path vm_service] - leak_tracker_flutter_testing 3.0.5 [flutter leak_tracker leak_tracker_testing matcher meta] - leak_tracker_testing 3.0.1 [leak_tracker matcher meta] - logging 1.2.0 - macros 0.1.2-main.4 [_macros] - matcher 0.12.16+1 [async meta stack_trace term_glyph test_api] - material_color_utilities 0.11.1 [collection] - meta 1.15.0 - mime 1.0.5 - modular_core 2.0.3+1 [characters meta modular_interfaces] - modular_interfaces 2.0.2 - node_preamble 2.0.2 - package_config 2.1.0 [path] - path_drawing 1.0.1 [vector_math meta path_parsing flutter] - path_parsing 1.0.1 [vector_math meta] - path_provider_android 2.2.9 [flutter path_provider_platform_interface] - path_provider_foundation 2.4.0 [flutter path_provider_platform_interface] - path_provider_linux 2.2.1 [ffi flutter path path_provider_platform_interface xdg_directories] - path_provider_platform_interface 2.1.2 [flutter platform plugin_platform_interface] - path_provider_windows 2.3.0 [ffi flutter path path_provider_platform_interface] - platform 3.1.5 - plugin_platform_interface 2.1.8 [meta] - pool 1.5.1 [async stack_trace] - process 5.0.2 [file path platform] - pub_semver 2.1.4 [collection meta] - pubspec_parse 1.3.0 [checked_yaml collection json_annotation pub_semver yaml] - redux 5.0.0 - shared_preferences_android 2.3.0 [flutter shared_preferences_platform_interface] - shared_preferences_foundation 2.5.0 [flutter shared_preferences_platform_interface] - shared_preferences_linux 2.4.0 [file flutter path path_provider_linux path_provider_platform_interface shared_preferences_platform_interface] - shared_preferences_platform_interface 2.4.1 [flutter plugin_platform_interface] - shared_preferences_web 2.4.1 [flutter flutter_web_plugins shared_preferences_platform_interface web] - shared_preferences_windows 2.4.0 [file flutter path path_provider_platform_interface path_provider_windows shared_preferences_platform_interface] - shelf 1.4.1 [async collection http_parser path stack_trace stream_channel] - shelf_packages_handler 3.0.2 [path shelf shelf_static] - shelf_static 1.1.2 [convert http_parser mime path shelf] - shelf_web_socket 2.0.0 [shelf stream_channel web_socket_channel] - sky_engine 0.0.99 - source_gen 1.5.0 [analyzer async build dart_style glob path source_span yaml] - source_helper 1.3.4 [analyzer collection source_gen] - source_map_stack_trace 2.1.1 [path source_maps stack_trace] - source_maps 0.10.12 [source_span] - source_span 1.10.0 [collection path term_glyph] - stack_trace 1.11.1 [path] - stream_channel 2.1.2 [async] - stream_transform 2.1.0 - string_scanner 1.2.0 [source_span] - supercharged_dart 2.1.1 - sync_http 0.3.1 - term_glyph 1.2.1 - test_api 0.7.2 [async boolean_selector collection meta source_span stack_trace stream_channel string_scanner term_glyph] - test_core 0.6.4 [analyzer args async boolean_selector collection coverage frontend_server_client glob io meta package_config path pool source_map_stack_trace source_maps source_span stack_trace stream_channel test_api vm_service yaml] - timing 1.0.1 [json_annotation] - typed_data 1.3.2 [collection] - universal_io 2.2.2 [collection meta typed_data] - vector_math 2.1.4 - vm_service 14.2.4 - watcher 1.1.0 [async path] - web 0.5.1 - web_socket 0.1.6 [web] - web_socket_channel 3.0.1 [async crypto stream_channel web web_socket] - webdriver 3.0.3 [matcher path stack_trace sync_http] - webkit_inspection_protocol 1.2.1 [logging] - xdg_directories 1.0.4 [meta path] - yaml 3.1.2 [collection source_span string_scanner] ```

Additional context and comments

No response

muzzah commented 3 weeks ago

Should note, I was using the firebase emulator version 13.5.1 and not production.

muzzah commented 3 weeks ago

I thought it could be an emulator upgrade but i downgraded the emulator to a previous version where this was working and I can confirm that the problem persists still. So it seems its possibly a library issue.

SelaseKay commented 3 weeks ago

Hi @muzzah . I'm not entirely sure on what you're trying to achieve. onDisconnect.update won't necessarily trigger on Hot restart of your application. It's only called when your device loses connection(no internet, something internal happened causing connection to be lost, etc...). Kindly refer to the docs to see if it helps you achieve your intended experience. You can also provide a full sample github repo reproducing this issue.

muzzah commented 3 weeks ago

@SelaseKay I am precisely trying to achieve what the documentation states as I have a chat like app. The trigger use to fire on hot restarts as any connection the client had would be reset. I have a chat-like app where I use the onDIsconnect trigger to update presence information stored in the Firebase DB. Prior to a firebase library & flutter SDK upgrade, when I did a hot restart while the client was connected to the emulator the connection was disrupted and the presence would be updated accordingly in the DB. After upgrading the core libraries, when I now do a hot restart the trigger does not fire anymore and thus the presence of the user is now in an invalid state.

This also happens now when I put the app in the background. As soon as I put the client into the background the network connection is severed and the client disconnects but the DB is not updated.

Ive attached a screen recording where I demonstrate the app and show the client connecting disconnecting (The other app in the background show the presence of the other client named james) and then a hot restart where the presence information is not updated. I then also show how when I background the app the client disconnects but the presence again is not updated. This is also validated by checking the firebase db through the emulator UI.

https://github.com/user-attachments/assets/374362da-f941-4b7d-9b06-2c33525a16dc

muzzah commented 3 weeks ago

Im providing a simple test case. Has a connect/disconnect button, when connected it will set a online=true at /test path and set an ondisconnect trigger.

When you connect you will see that the value has been set. If you hot restart the app the connection to the db is disrupted but the trigger does not fire. and the value for online is still true.

You will need to provide your own firebase options file.

import 'dart:developer';

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:testflutter/firebase_options.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home:  Scaffold(body: TestWidget()),
    );
  }
}

// ignore: must_be_immutable
class TestWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => TestWidgetState();
}

class TestWidgetState extends State<TestWidget> {
  bool connected = false;
  late FirebaseDatabase db;

  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      key: GlobalKey(),
      padding: EdgeInsets.only(top: 30, left: 10, right: 10, bottom: 30),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        children: [
          TextButton(onPressed: pressed, child: Text(connected ? "Disconnect" : "Connect"))
        ],
      ),
    );
  }

  void pressed() {
    connected ? disconnect() : connect();
  }

  Future<void> connect() async {
    db = await fbapp();
    db.ref(".info/connected").onValue.listen(_connectionHandler, onError: _connectionErrorHandler, cancelOnError: true);
    log("Going online");
    await db.goOnline().onError((error, stackTrace) {
      log("Error when going online : $error");
    });

  }
  void _connectionHandler(DatabaseEvent event) {
    bool dbIsConnected = event.snapshot.value as bool;
    log("DB Connection state : $dbIsConnected");

    if (dbIsConnected) {
      db.ref("/test").update({"online" : true}).then((_) async {
        await db.ref("/test").onDisconnect().update({"online" : false});
      }).then((_) {
        log("Updated to online");
        setState(() {
          connected = true;
        });
      });

    } else  {
      setState(() {
        connected = false;
      });
    }
  }

  Future<FirebaseDatabase> fbapp() async {
    FirebaseApp app;
    bool isInitialised = Firebase.apps.any((firebaseApp) => firebaseApp.name == defaultFirebaseAppName);

    if (isInitialised) {
      log("Returning existing Firebase instance");
      app = Firebase.app();
    } else {
      log("Creating new Firebase instance");
      app = await Firebase.initializeApp(options: DefaultFirebaseOptions().currentPlatform)
          .then((value) => value..setAutomaticDataCollectionEnabled(false))
          .catchError((_) => Firebase.app(), test: (ex) => ex is FirebaseException && ex.code == "duplicate-app");

    }

    FirebaseDatabase db = FirebaseDatabase.instanceFor(app: app, databaseURL: DefaultFirebaseOptions.DATABASE_HOST);

      db.setLoggingEnabled(true);
      int port = int.tryParse(DefaultFirebaseOptions.FDB_EMULATOR_PORT)!;
      log("DBEmulator host=${DefaultFirebaseOptions.FB_HOST} port=$port");
      db.useDatabaseEmulator(DefaultFirebaseOptions.FB_HOST, port);

    db.setPersistenceEnabled(true);
    return db;
  }

  Future<void> disconnect() async {
    await db.goOffline();
    log("Offline");
  }

  void _connectionErrorHandler(Object error, StackTrace st) {
    log("Error on connection handler : $error",  stackTrace: st);
  }
}
SelaseKay commented 3 weeks ago

Hi @muzzah . I think this is the intended behaviour. Hot restart of your app does not reset the connection. Also, your app running in the background will not trigger onDisconnect. onDisconnect is only called in cases where you kill your app or lose internet connection. Here's another reference: docs. Kindly check it out.

muzzah commented 3 weeks ago

This use to work before, that is a hot restart would kill the connection and the presence would be updated. Im aware of the docs but I dont think this is intended behaviour. If you hot restart a flutter app, everything gets reset, why wouldnt eh firebase db connection? If the intended behavior is to keep the connection alive and Im developing a chat app, that would mean I would need to account for everything resetting in the app except the firebase db connection and thus the presence trigger would not fire. This is really odd and makes programming with firebase db difficult.

Again this use to work on a previous database library version an now it does not so the behavior keeps changing. I strongly think it makes sense that on a hot reload the firebase database library also resets and shutsdown connections so that any triggers get fired and presence gets updated accordingly. Urge you to think about what kind of difficulties this introduces to programming with the firebase db library.

Also Further more to the fact that a connection is possibly being leaked by the plugin or not shutdown on hot restart if you add the following to the main method before runApp in the previous example and do a hot restart, the trigger fires and the online value is set to fale

 WidgetsFlutterBinding.ensureInitialized();
  await fbapp()
      .then((db) => db.goOffline())
      .then((_) => log("Main offline"))
      .catchError((error) => log("Main Error $error"));

This means the connection stays open on a hot restart while all other connections get reset. Again, now I need to detect if the connection to the db is open still just for firebase db and then reset all of my UI accordingly.

SelaseKay commented 3 weeks ago

Hi @muzzah . So sorry for the inconvenience. We will further investigate this issue

SelaseKay commented 3 weeks ago

Hi @muzzah . What version of firebase_database was it working for previously?

muzzah commented 3 weeks ago

The following version were in use before upgrading to the current latest version.

firebase_core: ^2.25.4 firebase_database: ^10.4.5

SelaseKay commented 3 weeks ago

Does it work as expected when you revert to these versions?

muzzah commented 3 weeks ago

I have not tested by reverting since it would require a bit of work on my end with other dependencies and flutter SDK, emulator downgrades etc which I dont really want to go through. The upgrade itself was painful. But I can assure you it was working previously before I went through a firebase, flutter sdk and emulator upgrade

muzzah commented 3 weeks ago

@SelaseKay I was also using Flutter SDK 3.19.1 before upgrading to 3.24.0 incase this is also related to the flutter SDK in someway

Lyokone commented 1 week ago

Hello @muzzah, after extensive testing, we concluded that the current behavior of onDisconnect is what we expect. It keeps active across hot restarts but will not be launched since it's a not a real "disconnect". Closing the app is triggering the disconnect as expected.

muzzah commented 1 week ago

Just want to say this is a bit wrong IMO. It is a real disconnect since we restart the app, all network connections should be reset. I guess you guys choose to not make it a real disconnect which is your choice. All other flutter libraries like firestore shutdown the connection on hot restart, and this behavior is unique to database (and this use to not be the case on a previous version of the library as well so it seems you guys keep chopping and choosing what the behavior is). The behavior of hot restart is to reset the app, so not shutting down the connection for database makes programming with it difficult. I now have to account for the hot restart in my main method only for the firebase database library. ALL other network connections get shutdown so I have no idea why you would maintain this behavior. Makes me want to move away from database to be honest. @Lyokone @SelaseKay