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.6k stars 3.95k forks source link

🐛 [REALTIME_DATABASE] Awaits fail on Firebase #9578

Closed dlondonog closed 3 months ago

dlondonog commented 1 year ago

Bug report

Describe the bug I am currently using Firebase RTDB version 8.2 and every time I update to version 9.1 I have serious problems with queries that use await in my app. After looking for different causes I was able to find that the origin is that since version 9 the awaits do not work in a predictable way.

Steps to reproduce

Steps to reproduce the behavior:

I made the following code to check it...

The code has a query button that when pressed (_submit()) brings up a list of articles in the database and returns the result of the sum of the quantities:

 Future<void> _submit() async {
   List<ItemModel> itemsList = [];
   itemsList = await loadItems();
   int _sumQty = itemsList.fold(0, (a, b) => a + b.quantity!);
   print('$_sumQty was the sum of quantities');
 }

Expected behavior

If this code is used with version 8.2.0 of Firebase on the web the result is as expected:

{item: iPhone, quantity: 5} {item: Samsung, quantity: 10} {item: LG, quantity: 15} {item: NTC, quantity: 20} {item: Zenith, quantity: 25} {item: Sony, quantity: 30} {item: JVC, quantity: 40}

7 Items were loaded

145 was the sum of quantities

And if the same code is used with the latest version of Firebase (9.1.4) on the Web the result is completely wrong:

{item: iPhone, quantity: 5}

1 Items were loaded

5 was the sum of quantities

{item: Samsung, quantity: 10} {item: LG, quantity: 15} {item: NTC, quantity: 20} {item: Zenith, quantity: 25} {item: Sony, quantity: 30} {item: JVC, quantity: 40}

Note that in the new version the execution does not wait for the complete list and only fetches the first of the components causing a mess if you are using awaits in some parts of your code.

Sample project

For Firebase 8.2.0

import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_database/ui/firebase_list.dart';
import 'package:flutter/material.dart';

class Prueba extends StatefulWidget {
 const Prueba({Key? key}) : super(key: key);

 @override
 State<Prueba> createState() => _PruebaState();
}

class ItemModel {
   String? item;
   int? quantity;

   ItemModel({
       this.item,
       this.quantity,
   });
}

class _PruebaState extends State<Prueba> {

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

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

 @override
 Widget build(BuildContext context) {

   return Scaffold(
     appBar: AppBar(
       title: const Text('Async InitState Test'),
     ),
     body: createButton('Query'),

   );
 }

   Widget createButton(String texto) {
   return Container(
     width: double.infinity,
     padding: const EdgeInsets.only(top:30.0, left:10.0, right:10.0),
     child: ElevatedButton(
       onPressed: _submit,
       child: Text(texto, style: Theme.of(context).textTheme.headline6),
     ),
   );
 }

 Future<void> _submit() async {
   List<ItemModel> itemsList = [];
   itemsList = await loadItems();
   int _sumQty = itemsList.fold(0, (a, b) => a + b.quantity!);
   print('$_sumQty was the sum of quantities');
 }

 Future<List<ItemModel>> loadItems() async {

   final List<ItemModel> items = [];

   String path = '/items';
   Query resp = FirebaseDatabase.instance.reference().child(path);

   FirebaseList(
     query: resp,
     onChildAdded: (i, element) {
       print(element.value);
       ItemModel temp = ItemModel()
         ..item = element.value["item"]
         ..quantity = element.value["quantity"];
       items.add(temp);
     },
     onError: (e) => print(e.message)
   );

   await resp.once().then((snapshot) {
     print("${items.length} Items were loaded");
   });

   return items;
 }
}

To apply it on Firebase 9.1.4, your need to do some changes on loadItems:

  Future<List<ItemModel>> loadItems() async {

    final List<ItemModel> items = []; 

    String path = '/empresas/-ME9qZY5k8RxpCymZlV2/items';
    Query resp = FirebaseDatabase.instance.ref().child(path);

    FirebaseList(
      query: resp,
      onChildAdded: (i, element) {
        print(element.value);
        Map<dynamic, dynamic> map = element.value as dynamic; 
        ItemModel temp = ItemModel()
          ..item = map["item"]
          ..quantity = map["quantity"];
        items.add(temp);
      },
      onError: (e) => print(e.message)
    );

    await resp.once().then((snapshot) {
      print("${items.length} Items were loaded");
    });

    return items;
  }
}

In both cases I prefer to use FirebaseList instead .once queries for two reasons:

  1. I can receive the ordered data, which is an advantage when graphing.
  2. Queries work much better when there are changes. In practice, using .once tends to fetch data from cache and often doesn't reflect recent changes.

Additional context

Although the error occurs 100% of the time on the Web, this situation also occurs sometimes on Mobile (Android), which makes it unpredictable.


Flutter doctor

Run flutter doctor and paste the output below:

Click To Expand ``` [✓] Flutter (Channel stable, 3.3.2, on macOS 12.6 21G115 darwin-x64, locale es-419) • Flutter version 3.3.2 on channel stable at /Users/dlondono/development/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision e3c29ec00c (6 days ago), 2022-09-14 08:46:55 -0500 • Engine revision a4ff2c53d8 • Dart version 2.18.1 • DevTools version 2.15.0 [✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2) • Android SDK at /Users/dlondono/Library/Android/sdk • Platform android-33, build-tools 30.0.2 • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7281165) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 14.0) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 14A309 • CocoaPods version 1.11.3 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2020.3) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7281165) [✓] VS Code (version 1.71.2) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.48.0 [✓] Connected device (3 available) • iPhone 14 Pro Max (mobile) • 3FC36180-5816-44D6-9517-C46C03DBAC80 • ios • com.apple.CoreSimulator.SimRuntime.iOS-16-0 (simulator) • macOS (desktop) • macos • darwin-x64 • macOS 12.6 21G115 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 105.0.5195.125 [✓] HTTP Host Availability • All required HTTP hosts are available • No issues found! ```

Flutter dependencies

Run flutter pub deps -- --style=compact and paste the output below:

Click To Expand ``` Dart SDK 2.18.1 Flutter SDK 3.3.2 dependencies: - animate_do 2.1.0 [flutter] - charts_flutter 0.12.0 [charts_common collection flutter intl logging meta] - country_codes 2.2.0 [flutter] - country_pickers 2.0.0 [flutter] - cupertino_icons 1.0.5 - datetime_picker_formfield 2.0.1 [flutter intl] - device_info_plus 4.1.2 [flutter device_info_plus_platform_interface device_info_plus_macos device_info_plus_linux device_info_plus_web device_info_plus_windows] - dots_indicator 2.1.0 [flutter] - excel 2.0.0-null-safety-3 [archive xml equatable] - firebase_auth 3.9.0 [firebase_auth_platform_interface firebase_auth_web firebase_core firebase_core_platform_interface flutter meta] - firebase_core 1.22.0 [firebase_core_platform_interface firebase_core_web flutter meta] - firebase_database 9.1.4 [firebase_core firebase_core_platform_interface firebase_database_platform_interface firebase_database_web flutter] - firebase_messaging 13.0.2 [firebase_core firebase_core_platform_interface firebase_messaging_platform_interface firebase_messaging_web flutter meta] - flutter 0.0.0 [characters collection material_color_utilities meta vector_math sky_engine] - flutter_localizations 0.0.0 [flutter intl characters clock collection material_color_utilities meta path vector_math] - flutter_slidable 2.0.0 [flutter] - font_awesome_flutter 10.2.1 [flutter] - horizontal_data_table 4.1.2+2 [flutter provider] - http 0.13.5 [async http_parser meta path] - intl 0.17.0 [clock path] - path_provider 2.0.11 [flutter path_provider_android path_provider_ios path_provider_linux path_provider_macos path_provider_platform_interface path_provider_windows] - qr_flutter 4.0.0 [flutter qr] - rxdart 0.27.5 - share_plus 4.2.0 [meta mime flutter share_plus_platform_interface share_plus_linux share_plus_macos share_plus_windows share_plus_web] - shared_preferences 2.0.15 [flutter shared_preferences_android shared_preferences_ios shared_preferences_linux shared_preferences_macos shared_preferences_platform_interface shared_preferences_web shared_preferences_windows] - sleek_circular_slider 2.0.1 [flutter] dev dependencies: - flutter_launcher_icons 0.10.0 [args checked_yaml cli_util image json_annotation path yaml] - flutter_lints 2.0.1 [lints] - flutter_test 0.0.0 [flutter test_api path fake_async clock stack_trace vector_math async boolean_selector characters collection matcher material_color_utilities meta source_span stream_channel string_scanner term_glyph] transitive dependencies: - archive 3.3.1 [crypto path] - args 2.3.1 - async 2.9.0 [collection meta] - boolean_selector 2.1.0 [source_span string_scanner] - characters 1.2.1 - charts_common 0.12.0 [collection intl logging meta vector_math] - checked_yaml 2.0.1 [json_annotation source_span yaml] - cli_util 0.3.5 [meta path] - clock 1.1.1 - collection 1.16.0 - crypto 3.0.2 [typed_data] - device_info_plus_linux 3.0.0 [device_info_plus_platform_interface file flutter meta] - device_info_plus_macos 3.0.0 [device_info_plus_platform_interface flutter] - device_info_plus_platform_interface 3.0.0 [flutter meta plugin_platform_interface] - device_info_plus_web 3.0.0 [device_info_plus_platform_interface flutter_web_plugins flutter] - device_info_plus_windows 4.1.0 [device_info_plus_platform_interface ffi flutter win32] - equatable 2.0.5 [collection meta] - fake_async 1.3.1 [clock collection] - ffi 2.0.1 - file 6.1.4 [meta path] - firebase_auth_platform_interface 6.8.0 [collection firebase_core flutter meta plugin_platform_interface] - firebase_auth_web 4.4.1 [firebase_auth_platform_interface firebase_core firebase_core_web flutter flutter_web_plugins http_parser intl js meta] - firebase_core_platform_interface 4.5.1 [collection flutter flutter_test meta plugin_platform_interface] - firebase_core_web 1.7.2 [firebase_core_platform_interface flutter flutter_web_plugins js meta] - firebase_database_platform_interface 0.2.2+4 [collection firebase_core flutter meta plugin_platform_interface] - firebase_database_web 0.2.1+6 [firebase_core firebase_core_web firebase_database_platform_interface flutter flutter_web_plugins js] - firebase_messaging_platform_interface 4.1.4 [firebase_core flutter meta plugin_platform_interface] - firebase_messaging_web 3.1.4 [firebase_core firebase_core_web firebase_messaging_platform_interface flutter flutter_web_plugins js meta] - flutter_web_plugins 0.0.0 [flutter js characters collection material_color_utilities meta vector_math] - http_parser 4.0.1 [collection source_span string_scanner typed_data] - image 3.1.3 [archive meta xml] - js 0.6.4 - json_annotation 4.6.0 [meta] - lints 2.0.0 - logging 1.0.2 - matcher 0.12.12 [stack_trace] - material_color_utilities 0.1.5 - meta 1.8.0 - mime 1.0.2 - nested 1.0.0 [flutter] - path 1.8.2 - path_provider_android 2.0.20 [flutter path_provider_platform_interface] - path_provider_ios 2.0.11 [flutter path_provider_platform_interface] - path_provider_linux 2.1.7 [ffi flutter path path_provider_platform_interface xdg_directories] - path_provider_macos 2.0.6 [flutter path_provider_platform_interface] - path_provider_platform_interface 2.0.4 [flutter platform plugin_platform_interface] - path_provider_windows 2.1.3 [ffi flutter path path_provider_platform_interface win32] - petitparser 5.0.0 [meta] - platform 3.1.0 - plugin_platform_interface 2.1.3 [meta] - process 4.2.4 [file path platform] - provider 6.0.3 [collection flutter nested] - qr 2.1.0 [meta] - share_plus_linux 3.0.0 [share_plus_platform_interface file flutter meta url_launcher] - share_plus_macos 3.0.1 [share_plus_platform_interface flutter] - share_plus_platform_interface 3.0.3 [flutter meta mime plugin_platform_interface] - share_plus_web 3.0.1 [share_plus_platform_interface url_launcher flutter flutter_web_plugins meta] - share_plus_windows 3.0.1 [share_plus_platform_interface flutter meta url_launcher] - shared_preferences_android 2.0.13 [flutter shared_preferences_platform_interface] - shared_preferences_ios 2.1.1 [flutter shared_preferences_platform_interface] - shared_preferences_linux 2.1.1 [file flutter path path_provider_linux path_provider_platform_interface shared_preferences_platform_interface] - shared_preferences_macos 2.0.4 [flutter shared_preferences_platform_interface] - shared_preferences_platform_interface 2.1.0 [flutter plugin_platform_interface] - shared_preferences_web 2.0.4 [flutter flutter_web_plugins shared_preferences_platform_interface] - shared_preferences_windows 2.1.1 [file flutter path path_provider_platform_interface path_provider_windows shared_preferences_platform_interface] - sky_engine 0.0.99 - source_span 1.9.0 [collection path term_glyph] - stack_trace 1.10.0 [path] - stream_channel 2.1.0 [async] - string_scanner 1.1.1 [source_span] - term_glyph 1.2.1 - test_api 0.4.12 [async boolean_selector collection meta source_span stack_trace stream_channel string_scanner term_glyph matcher] - typed_data 1.3.1 [collection] - url_launcher 6.1.5 [flutter url_launcher_android url_launcher_ios url_launcher_linux url_launcher_macos url_launcher_platform_interface url_launcher_web url_launcher_windows] - url_launcher_android 6.0.19 [flutter url_launcher_platform_interface] - url_launcher_ios 6.0.17 [flutter url_launcher_platform_interface] - url_launcher_linux 3.0.1 [flutter url_launcher_platform_interface] - url_launcher_macos 3.0.1 [flutter url_launcher_platform_interface] - url_launcher_platform_interface 2.1.0 [flutter plugin_platform_interface] - url_launcher_web 2.0.13 [flutter flutter_web_plugins url_launcher_platform_interface] - url_launcher_windows 3.0.1 [flutter url_launcher_platform_interface] - vector_math 2.1.2 - win32 3.0.0 [ffi] - xdg_directories 0.2.0+2 [meta path process] - xml 5.4.1 [collection meta petitparser] - yaml 3.1.1 [collection source_span string_scanner]```

darshankawar commented 1 year ago

Thanks for the report @darshankawar Does the behavior remain same if you use setPersistenceEnabled(true); specially while running on web ?

dlondonog commented 1 year ago

Thanks for the report @darshankawar Does the behavior remain same if you use setPersistenceEnabled(true); specially while running on web ?

I only use setPersistenceEnabled(true) on IOS and Android. I don't have it enabled on the web (It's not a supported operation... Error: Unsupported operation: setPersistenceEnabled() is not supported for web

The bad behavior occurs 100% of the time on the Web, but on IOS and Android it is also common despite having setPersistenceEnabled(true)enabled

darshankawar commented 1 year ago

I tried the code sample you shared earlier, but it is giving me compile errors:

Screenshot 2022-09-22 at 12 55 29 PM

Also, can you try the official plugin example with your data and see if using it, you get same behavior ?

dlondonog commented 1 year ago

@darshankawar, If you are the last version of Firebase, you need to change loadItems as follows:

  Future<List<ItemModel>> loadItems() async {

    final List<ItemModel> items = []; 

    String path = '/items';
    Query resp = FirebaseDatabase.instance.ref().child(path);

    FirebaseList(
      query: resp,
      onChildAdded: (i, element) {
        print(element.value);
        Map<dynamic, dynamic> map = element.value as dynamic; 
        ItemModel temp = ItemModel()
          ..item = map["item"]
          ..quantity = map["quantity"];
        items.add(temp);
      },
      onError: (e) => print(e.message)
    );

    await resp.once().then((snapshot) {
      print("${items.length} Items were loaded");
    });

    return items;
  }
}
darshankawar commented 1 year ago

Thanks for the update. Using updated code sample, running on web with latest plugin version, I observed that the order of data is not correct as compared to on mobile:

  1. On web:

database snasphot:

Screenshot 2022-09-23 at 5 17 25 PM

Console log:

{msg: 0.3479517955989533, timestamp: 1633599666269}
1 Items were loaded
1633599666269 was the sum of quantities
{msg: 0.062175964365492664, timestamp: 1633599813864}
{msg: 0.456464, timestamp: 35435346535353}
{msg: 0.34535345, timestamp: 345353535}
{msg: 0.3545654, timestamp: 23424242424}

While, running same code on mobile (iOS), we see correct order of the data:

flutter: {msg: 0.3479517955989533, timestamp: 1633599666269}
flutter: {msg: 0.062175964365492664, timestamp: 1633599813864}
flutter: {msg: 0.456464, timestamp: 35435346535353}
flutter: {msg: 0.34535345, timestamp: 345353535}
flutter: {msg: 0.3545654, timestamp: 23424242424}
flutter: 5 Items were loaded
flutter: 38726315611445 was the sum of quantities
dlondonog commented 1 year ago

Yes, in Web the complete list is not loaded before continuing. In Mobile it is possible to load the complete list, although sometimes it also fails.

TarekkMA commented 3 months ago

Hi @dlondonog ,

Thank you for your report! Since this issue was opened, the implementation details have changed significantly. If you feel that this issue is still relevant, please feel free to open a new issue with updated context. We appreciate your understanding and support.