Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 [firebase_database] get() return incorrect results #10145

Open
bonnybun opened this issue Dec 20, 2022 · 11 comments
Open

🐛 [firebase_database] get() return incorrect results #10145

bonnybun opened this issue Dec 20, 2022 · 11 comments
Assignees
Labels
blocked: firebase-sdk platform: ios Issues / PRs which are specifically for iOS. plugin: database type: bug Something isn't working

Comments

@bonnybun
Copy link

Bug report

This report relate to issue #9067
If I subscribe to a database reference, call get() function on a child reference will return wrong value in iOS.
On Android it works as expected.
A workaround exist using once() but it hasn't exactly the same behavior as get().

Steps to reproduce

  1. Create new firebase project and put this in the root database
{
  "organizations": [
    {
      "city": "Lyon",
      "country": "France",
      "data": [
        "a",
        "b",
        "c",
        "d",
        "e",
        "f",
        "g",
        "h"
      ],
      "foo": "bar",
      "id": 0,
      "name": "my organization"
    }
  ]
}
  1. Create new Flutter project and configure it with your firebase project
  2. Paste this in your main.dart
import 'dart:async';

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

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Object? _subscriptionResponse;
  Object? _response;
  final DatabaseReference _organizationRef =
      FirebaseDatabase.instance.ref("organizations/0");
  final DatabaseReference _dataRef =
      FirebaseDatabase.instance.ref("organizations/0/data");

  StreamSubscription<DatabaseEvent>? _subscription;

  @override
  void dispose() {
    super.dispose();
    _subscription?.cancel();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: _toggleSubscription,
              child: Text(
                  "${_subscription == null ? "Subscribe" : "Unsubscribe"}  '${_organizationRef.path}'"),
            ),
            Text(
              'Subscription Response : $_subscriptionResponse',
              maxLines: 30,
            ),
            ElevatedButton(
              onPressed: _getData,
              child: Text("Get   '${_dataRef.path}'"),
            ),
            ElevatedButton(
              onPressed: _onceData,
              child: Text("Once  '${_dataRef.path}'"),
            ),
            Text(
              '$_response',
              maxLines: 30,
            ),
          ],
        ),
      ),
    );
  }

  void _onOrganizationsUpdated(DatabaseEvent event) {
    setState(() {
      _subscriptionResponse = event.snapshot.value;
    });
  }

  void _toggleSubscription() {
    if (_subscription == null) {
      setState(() {
        _subscription =
            _organizationRef.onValue.listen(_onOrganizationsUpdated);
      });
    } else {
      _subscription?.cancel();
      setState(() {
        _response = null;
        _subscriptionResponse = null;
        _subscription = null;
      });
    }
  }

  void _getData() async {
    final value = (await _dataRef.get()).value;
    setState(() {
      _response = "get : $value";
    });
  }

  void _onceData() async {
    final value = (await _dataRef.once()).snapshot.value;
    setState(() {
      _response = "once : $value";
    });
  }
}

  1. Run on iOS device
  2. Press Get and Once button without subscribe
  3. Now Press Subscribe button, and press again Get and Once Button

Expected behavior

I want to get the value of the key 'data'.
The expected value is
["a","b","c","d","e","f","g","h"]
Calling get() or once() should return the same value.

But if a suscription is made on the parent, the get() function will return the suscription's value.
And once() return the correct value

This problem doesn't exist when I run on an android device

Sample project

sample_project


Flutter doctor

Run flutter doctor and paste the output below:

Click To Expand
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.3.10, on macOS 13.1 22C65 darwin-arm, locale fr-FR)
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.1)
[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.3)
[✓] VS Code (version 1.74.1)
[✓] Connected device (4 available)
[✓] HTTP Host Availability

Flutter dependencies

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

Click To Expand
Dart SDK 2.18.6
Flutter SDK 3.3.10
test_firebase 1.0.0+1

dependencies:
- cupertino_icons 1.0.5
- firebase_core 2.4.0 [firebase_core_platform_interface firebase_core_web flutter meta]
- firebase_database 10.0.7 [firebase_core firebase_core_platform_interface firebase_database_platform_interface firebase_database_web flutter]
- flutter 0.0.0 [characters collection material_color_utilities meta vector_math sky_engine]

dev dependencies:
- 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:
- _flutterfire_internals 1.0.10 [cloud_firestore_platform_interface cloud_firestore_web collection firebase_core firebase_core_platform_interface flutter meta]
- async 2.9.0 [collection meta]
- boolean_selector 2.1.0 [source_span string_scanner]
- characters 1.2.1
- clock 1.1.1
- cloud_firestore_platform_interface 5.9.2 [_flutterfire_internals collection firebase_core flutter meta plugin_platform_interface]
- cloud_firestore_web 3.1.1 [_flutterfire_internals cloud_firestore_platform_interface collection firebase_core firebase_core_web flutter flutter_web_plugins js]
- collection 1.16.0
- fake_async 1.3.1 [clock collection]
- firebase_core_platform_interface 4.5.2 [collection flutter flutter_test meta plugin_platform_interface]
- firebase_core_web 2.0.2 [firebase_core_platform_interface flutter flutter_web_plugins js meta]
- firebase_database_platform_interface 0.2.2+15 [_flutterfire_internals collection firebase_core flutter meta plugin_platform_interface]
- firebase_database_web 0.2.1+17 [firebase_core firebase_core_web firebase_database_platform_interface flutter flutter_web_plugins js]
- flutter_web_plugins 0.0.0 [flutter js characters collection material_color_utilities meta vector_math]
- js 0.6.4
- lints 2.0.1
- matcher 0.12.12 [stack_trace]
- material_color_utilities 0.1.5
- meta 1.8.0
- path 1.8.2
- plugin_platform_interface 2.1.3 [meta]
- 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]
- vector_math 2.1.2

@bonnybun bonnybun added Needs Attention This issue needs maintainer attention. type: bug Something isn't working labels Dec 20, 2022
@darshankawar darshankawar added the triage Issue is currently being triaged. label Dec 20, 2022
@darshankawar
Copy link

Thanks for the detailed report @bonnybun
I created the sample db and ran the code sample, but have been getting null upon following steps to replicate. Did I miss something to properly replicate ?

Screenshot 2022-12-20 at 6 19 51 PM

@darshankawar darshankawar added blocked: customer-response Waiting for customer response, e.g. more information was requested. and removed Needs Attention This issue needs maintainer attention. labels Dec 20, 2022
@bonnybun
Copy link
Author

Did you set the rules in your db @darshankawar ?

{
  "rules": {
    ".read": "true",
    ".write": "true"
  }
}

@google-oss-bot google-oss-bot added Needs Attention This issue needs maintainer attention. and removed blocked: customer-response Waiting for customer response, e.g. more information was requested. labels Dec 20, 2022
@darshankawar
Copy link

Yes, I have the rules properly set. I figured that the database instance reference isn't correct as mentioned in your code sample. Instead of organizations/0 and organizations/0/data, it should be organizations/data and organizations/data/0. After fixing this, I was able to run and see the behavior as below:

10145.mov

According to my findings, tapping get gives ["a","b","c","d","e","f","g","h"], but with once, it gives only a. Is this correct interpretation of your issue ? Because in your expected results, you mentioned that The expected value is ["a","b","c","d","e","f","g","h"]

@darshankawar darshankawar added blocked: customer-response Waiting for customer response, e.g. more information was requested. and removed Needs Attention This issue needs maintainer attention. labels Dec 21, 2022
@bonnybun
Copy link
Author

Your problem is that "organizations" should be an array, not an object. It's like a list of companies.
Instead, you should to have something like this :

Capture d’écran 2022-12-21 à 14 38 40

You can also import this json in your root database
example_root_firebase.json.zip

Get() and Once() must always return the same value :
["a","b","c","d","e","f","g","h"]

but after a subscription on a parent, the result of Get() becomes :
{"city":"Lyon","country":"France","data":["a","b","c","d","e","f","g","h"],"foo":"bar","id":0,"name":"my organization"}

@google-oss-bot google-oss-bot added Needs Attention This issue needs maintainer attention. and removed blocked: customer-response Waiting for customer response, e.g. more information was requested. labels Dec 21, 2022
@darshankawar
Copy link

I see, thanks. I updated the db to have the root node to be an array and then ran the code sample again which gives me same results as you mentioned above:

{"city":"Lyon","country":"France","data":["a","b","c","d","e","f","g","h"],"foo":"bar","id":0,"name":"my organization"}

@darshankawar darshankawar added plugin: database platform: ios Issues / PRs which are specifically for iOS. and removed Needs Attention This issue needs maintainer attention. triage Issue is currently being triaged. labels Dec 22, 2022
@JoolsMcFly
Copy link

Hi @darshankawar
I've added a comment on @Lyokone 's PR : #10182 (comment)

Is there anything else we can do on our end regarding this issue?
Thanks.

@darshankawar
Copy link

Is there anything else we can do on our end regarding this issue?

I think Guillaume should be aware of your comment in that PR and since you have tagged him in it, he'll probably let you know if there's anything else required from your end.

@JoolsMcFly
Copy link

Sounds good, we'll wait :)

@mrares
Copy link

mrares commented Dec 1, 2023

Hello, I don't know why OP hasn't back but I am having the same exact issue.

In my case I'm attaching a listener using

ref.child("a/path/to/data").onValue.listen((event) {
    /// Then here I execute a callback function that redirects the user to another page
 }

then a subsequent read from a child path such as:
ref.child("a/path/to/data/child/node").get()

Will return the wrong data, at the parent path "a/path/to/data"

If I run the following:

StreamSubscription<DatabaseEvent>? sub;
sub = ref.child("a/path/to/data").onValue.listen((event) {
    sub?.cancel();
    /// Then here I execute a callback function that redirects the user to another page
 }

then a subsequent read from a child path such as:
ref.child("a/path/to/data/child/node").get()

will return the correct data at the parent path "a/path/to/data/child/node"

This is the behaviour running on an iPhone 15 Pro simulator with:

environment:
  sdk: ">=3.1.1 <4.0.0"

dependencies:
  firebase_database: ^10.2.7

@russellwheatley
Copy link
Member

Thank you for the report, I have opened an issue on the Firebase iOS SDK: firebase/firebase-ios-sdk#12225

@Paitomax
Copy link

The same problem happens to me when using _organizationRef.keepSynced(true)
In fact, the SET method also writes to the wrong path.

Removing keepSynced the problem does not occur.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blocked: firebase-sdk platform: ios Issues / PRs which are specifically for iOS. plugin: database type: bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants