Flutterfire: [cloud_firestore] 0.14.0 Crash when StreamBuilder cancels subscription ( NullPointerException Attempt to invoke ListenerRegistration.remove() on a null object reference / Invalid argument: Instance of 'DocumentReference' )

Created on 19 Aug 2020  ·  15Comments  ·  Source: FirebaseExtended/flutterfire

Describe the bug
I have some StreamBuilders that listen for Firestore streams events, when the widget receives a didUpdateWidget callback the cloud_firestore plugin crashes:

E/flutter (25570): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: PlatformException(error, Attempt to invoke interface method 'void com.google.firebase.firestore.ListenerRegistration.remove()' on a null object reference, null)
E/flutter (25570): #0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:572:7)
E/flutter (25570): #1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:161:18)
E/flutter (25570): <asynchronous suspension>
E/flutter (25570): #2      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:334:12)
E/flutter (25570): #3      MethodChannelQuery.snapshots.<anonymous closure> (package:cloud_firestore_platform_interface/src/method_channel/method_channel_query.dart:145:48)
E/flutter (25570): #4      _runGuarded (dart:async/stream_controller.dart:847:24)
E/flutter (25570): #5      _BroadcastStreamController._callOnCancel (dart:async/broadcast_stream_controller.dart:358:5)
E/flutter (25570): #6      _BroadcastStreamController._recordCancel (dart:async/broadcast_stream_controller.dart:229:9)
E/flutter (25570): #7      _ControllerSubscription._onCancel (dart:async/stream_controller.dart:883:24)
E/flutter (25570): #8      _BufferingStreamSubscription._cancel (dart:async/stream_impl.dart:264:21)
E/flutter (25570): #9      _BufferingStreamSubscription.cancel (dart:async/stream_impl.dart:210:7)
E/flutter (25570): #10     _ForwardingStreamSubscription._onCancel (dart:async/stream_pipe.dart:149:27)
E/flutter (25570): #11     _BufferingStreamSubscription._cancel (dart:async/stream_impl.dart:264:21)
E/flutter (25570): #12     _BufferingStreamSubscription.cancel (dart:async/stream_impl.dart:210:7)
E/flutter (25570): #13     _ForwardingStreamSubscription._onCancel (dart:async/stream_pipe.dart:149:27)
E/flutter (25570): #14     _BufferingStreamSubscription._cancel (dart:async/stream_impl.dart:264:21)
E/flutter (25570): #15     _BufferingStreamSubscription.cancel (dart:async/stream_impl.dart:210:7)
E/flutter (25570): #16     _FlatMapStreamSink.onCancel.<anonymous closure> (package:rxdart/src/transformers/flat_map.dart:52:56)
E/flutter (25570): #17     MappedListIterable.elementAt (dart:_internal/iterable.dart:417:31)
E/flutter (25570): #18     ListIterator.moveNext (dart:_internal/iterable.dart:343:26)
E/flutter (25570): #19     Future.wait (dart:async/future.dart:402:26)
E/flutter (25570): #20     _FlatMapStreamSink.onCancel (package:rxdart/src/transformers/flat_map.dart:52:14)
E/flutter (25570): #21     forwardStream.<anonymous closure> (package:rxdart/src/utils/forwarding_stream.dart:43:51)
E/flutter (25570): #22     _runGuarded (dart:async/stream_controller.dart:847:24)
E/flutter (25570): #23     _BroadcastStreamController._callOnCancel (dart:async/broadcast_stream_controller.dart:358:5)
E/flutter (25570): #24     _BroadcastStreamController._recordCancel (dart:async/broadcast_stream_controller.dart:229:9)
E/flutter (25570): #25     _ControllerSubscription._onCancel (dart:async/stream_controller.dart:883:24)
E/flutter (25570): #26     _BufferingStreamSubscription._cancel (dart:async/stream_impl.dart:264:21)
E/flutter (25570): #27     _BufferingStreamSubscription.cancel (dart:async/stream_impl.dart:210:7)
E/flutter (25570): #28     _StreamBuilderBaseState._unsubscribe (package:flutter/src/widgets/async.dart:158:21)
E/flutter (25570): #29     _StreamBuilderBaseState.didUpdateWidget (package:flutter/src/widgets/async.dart:121:9)
E/flutter (25570): #30     StatefulElement.update (package:flutter/src/widgets/framework.dart:4734:58)
E/flutter (25570): #31     Element.updateChild (package:flutter/src/widgets/framework.dart:3245:15)
E/flutter (25570): #32     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4571:16)
E/flutter (25570): #33     Element.rebuild (package:flutter/src/widgets/framework.dart:4262:5)
E/flutter (25570): #34     StatelessElement.update (package:flutter/src/widgets/framework.dart:4627:5)
E/flutter (25570): #35     Element.updateChild (package:flutter/src/widgets/framework.dart:3245:15)
E/flutter (25570): #36     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:5881:14)
E/flutter (25570): #37     Element.updateChild (package:flutter/src/widgets/framework.dart:3245:15)
E/flutter (25570): #38     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:5881:14)
E/flutter (25570): #39     Element.updateChild (package:flutter/src/widgets/framework.dart:3245:15)
E/flutter (25570): #40     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4571:16)
E/flutter (25570): #41     Element.rebuild (package:flutter/src/widgets/framework.dart:4262:5)
E/flutter (25570): #42     StatelessElement.update (package:flutter/src/widgets/framework.dart:4627:5)
E/flutter (25570): #43     Element.updateChild (package:flutter/src/

It may be caused by another error:

E/flutter (25570): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Invalid argument: Instance of 'DocumentReference'
E/flutter (25570): #0      StandardMessageCodec.writeValue (package:flutter/src/services/message_codecs.dart:395:7)
E/flutter (25570): #1      FirestoreMessageCodec.writeValue (package:cloud_firestore_platform_interface/src/method_channel/utils/firestore_message_codec.dart:114:13)
E/flutter (25570): #2      StandardMessageCodec.writeValue (package:flutter/src/services/message_codecs.dart:385:9)
E/flutter (25570): #3      FirestoreMessageCodec.writeValue (package:cloud_firestore_platform_interface/src/method_channel/utils/firestore_message_codec.dart:114:13)
E/flutter (25570): #4      StandardMessageCodec.writeValue (package:flutter/src/services/message_codecs.dart:385:9)
E/flutter (25570): #5      FirestoreMessageCodec.writeValue (package:cloud_firestore_platform_interface/src/method_channel/utils/firestore_message_codec.dart:114:13)
E/flutter (25570): #6      StandardMessageCodec.writeValue.<anonymous closure> (package:flutter/src/services/message_codecs.dart:392:9)
E/flutter (25570): #7      _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:377:8)
E/flutter (25570): #8      MapView.forEach (dart:collection/maps.dart:345:10)
E/flutter (25570): #9      StandardMessageCodec.writeValue (package:flutter/src/services/message_codecs.dart:390:13)
E/flutter (25570): #10     FirestoreMessageCodec.writeValue (package:cloud_firestore_platform_interface/src/method_channel/utils/firestore_message_codec.dart:114:13)
E/flutter (25570): #11     StandardMessageCodec.writeValue.<anonymous closure> (package:flutter/src/services/message_codecs.dart:392:9)
E/flutter (25570): #12     _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:377:8)
E/flutter (25570): #13     StandardMessageCodec.writeValue (package:flutter/src/services/message_codecs.dart:390:13)
E/flutter (25570): #14     FirestoreMessageCodec.writeValue (package:cloud_firestore_platform_interface/src/method_channel/utils/firestore_message_codec.dart:114:13)
E/flutter (25570): #15     FirestoreMessageCodec.writeValue (package:cloud_firestore_platform_interface/src/method_channel/utils/firestore_message_codec.dart:98:7)
E/flutter (25570): #16     StandardMessageCodec.writeValue.<anonymous closure> (package:flutter/src/services/message_codecs.dart:392:9)
E/flutter (25570): #17     _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:377:8)
E/flutter (25570): #18     StandardMessageCodec.writeValue (package:flutter/src/services/message_codecs.dart:390:13)
E/flutter (25570): #19     FirestoreMessageCodec.writeValue (package:cloud_firestore_platform_interface/src/method_channel/utils/firestore_message_codec.dart:114:13)
E/flutter (25570): #20     StandardMethodCodec.encodeMethodCall (package:flutter/src/services/message_codecs.dart:527:18)
E/flutter (25570): #21     MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:153:13)
E/flutter (25570): #22     MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:334:12)
E/flutter (25570): #23     MethodChannelQuery.snapshots.<anonymous closure> (package:cloud_firestore_platform_interface/src/method_channel/method_channel_query.dart:133:48)
E/flutter (25570): #24     _runGuarded (dart:async/stream_controller.dart:847:24)
E/flutter (25570): #25     _BroadcastStreamController._subscribe (dart:async/broadcast_stream_controller.dart:213:7)
E/flutter (25570): #26     _ControllerStream._createSubscription (dart:async/stream_controller.dart:860:19)
E/flutter (25570): #27     _StreamImpl.listen (dart:async/stream_impl.dart:493:9)
E/flutter (25570): #28     new _ForwardingStreamSubscription (dart:async/stream_pipe.dart:118:10)
E/flutter (25570): #29     _ForwardingStream._createSubscription (dart:async/stream_pipe.dart:88:16)
E/flutter (25570): #30     _ForwardingStream.listen (dart:async/stream_pipe.dart:83:12)
E/flutter (25570): #31     new _ForwardingStreamSubscription (dart:async/stream_pipe.dart:118:10)
E/flutter (25570): #32     _ForwardingStream._createSubscription (dart:async/stream_pipe.dart:88:16)
E/flutter (25570): #33     _ForwardingStream.listen (dart:async/stream_pipe.dart:83:12)
E/flutter (25570): #34     _FlatMapStreamSink.add (package:rxdart/src/transformers/flat_map.dart:22:33)
E/flutter (25570): #35     forwardStream.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:rxdart/src/utils/forwarding_stream.dart:34:49)
E/flutter (25570): #36     forwardStream.runCatching (package:rxdart/src/utils/forwarding_stream.dart:24:12)
E/flutter (25570): #37     forwardStream.<anonymous closure>.<anonymous closure> (package:rxdart/src/utils/forwarding_stream.dart:34:28)
E/flutter (25570): #38     _rootRun

To Reproduce
Using a complex Firestore stream (a query stream that is RxDart flatMapped into another query stream) with a StreamBuilder.

Expected behavior
The plugin shouldn't crash when it tries to remove a ListenerRegistration that doesn't exist (null).
Or it shouldn't try to remove it at all.
See listenerRegistrations.get(handle) that returns null at FlutterFirebaseFirestorePlugin.java:496.

Flutter doctor

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 1.20.2, on Mac OS X 10.15.6 19G73, locale it-IT)

[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[✓] Xcode - develop for iOS and macOS (Xcode 11.6)
[✓] Android Studio (version 4.0)
[✓] VS Code (version 1.47.3)
[✓] Connected device (2 available)

• No issues found!
Needs Attention

All 15 comments

Looks like the plugin crashes when method_channel_query.dart:133 calls:

        MethodChannelFirebaseFirestore.channel.invokeMethod<void>(
          'Query#addSnapshotListener',
          <String, dynamic>{
            'query': this,
            'handle': handle,
            'firestore': firestore,
            'includeMetadataChanges': includeMetadataChanges,
          },
        );

When query contains a DocumentReference as parameter:
image

I fear this might require an urgent fix...

Hi @thearaks
Can you please provide your pubpsec.yaml and a complete reproducible minimal code sample
Thank you

@TahaTesser found the error and a workaround.
Apparently you can't use a DocumentReference instance in queries since 0.14.0.

While in previous versions of the plugin this worked fine:

FirebaseFirestore.instance
          .collection('projects')
          .where('ownerRef', isEqualTo: FirebaseFirestore.instance.collection('users').doc(uid))
          .where('deletedAt', isNull: true)
          .snapshots();

Now causes this crash and needs to be replaced with a string representation of the DocumentReference:

FirebaseFirestore.instance
          .collection('projects')
          .where('ownerRef', isEqualTo: 'users/$uid')
          .where('deletedAt', isNull: true)
          .snapshots()

Is this the expected behavior or is it a regression?

pubspec.yaml for completeness:

name: test
description: The Test.

version: 0.1.1+1

environment:
  sdk: ">=2.5.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  flutter_localizations:
    sdk: flutter

  cupertino_icons: ^0.1.2

  flutter_inappwebview: ^4.0.0+4
  package_info: ^0.4.1
  shared_preferences: ^0.5.8
  provider: ^4.3.1
  rxdart: ^0.24.1
  tuple: ^1.0.3
  email_validator: ^1.0.5
  url_launcher: ^5.5.0
  store_redirect: ^1.0.2
  image_picker: ^0.6.7+4

  notification_permissions: ^0.4.6
  flutter_auth_buttons: ^0.9.0
  google_sign_in: ^4.5.1
  firebase_auth: ^0.18.0
  firebase_messaging: ^7.0.0
  firebase_analytics: ^6.0.0
  firebase_crashlytics: ^0.1.4+1
  firebase_remote_config: ^0.4.0
  cloud_firestore: ^0.14.0

dependency_overrides:
  flutter_svg: ^0.18.0

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

flutter_intl:
  enabled: true

I'll pick this one up - this may be unintended regression.

I had to revert to cloud_firestore: ^0.13.7 because i need queries on DocumentReferences values and because the 0.14.0 version has a lot of problems keeping track of the stream controllers so when it tries to cancel streams or add data to a stream it crashes.

Thank you @Ehesp!

@thearaks Just to confirm, is the ownerRef key/value a Reference within the database?

because the 0.14.0 version has a lot of problems keeping track of the stream controllers so when it tries to cancel streams or add data to a stream it crashes.

Is this another issue?

@Ehesp yes, In the example i pasted above ownerRef is a Reference inside the database.
Unfortunately the workaround seems to have worked with some entities but not with others (?!) so I had to revert to the previous version.

Then, beside that, I fear there's another issue related to a mis-handling of the stream controllers so when the plugin tries to close a stream or tries to add data from Firestore to the stream it crashes with an NPE or with an error on an assert controller != null (see the first stacktrace i posted).

@thearaks On the first issue, I think this is a simple fix. On this line, this needs adding:

value = _CodecUtility.valueEncode(value);

Could you quickly add that locally and check it works? I'll get PR up, but I'm going to add some more tests around this at the same time.

@Ehesp I can confirm that your change solves the first issue with the DocumentReference!

Sweet 👍 I'll push up a fix.

Could you send over a simplified pseudo code of your Widget which includes the Stream? I assume the didUpdateWidget is because some native handler is triggering a removal of the native listeners.

@thearaks on the second issue, how fast are you updating your widget with the Stream in it? The only way I can see this happening is that:

  1. A widget builds and subscribes (async call)
  2. The widget rebuilds (e.g. state change?) pretty much instantly, and the call to remove the listener is triggered before the listener has been setup = error.

Ok, found the second issue too! PR linked.

@Ehesp great!
Thank you very much for your fixes!

Release with a fix is now published. Latest versions can be seen here.

Thanks again for the bug report :)

Was this page helpful?
0 / 5 - 0 ratings