How to handle when a Firestore document may or may not include a field with Flutter

Question

Asked by edn on November 03, 2021 (source).

I have an existing Firestore database for a project. In a given collection (say "users"), I have the user profile information in each document.

In some documents, I have the field "signupMethod" in the collection while the same field does not exist in some other documents in the same collection.

I need to try to read the signupMethod for each user session in my application. If the document does have the field, the value of the field needs to be used. If the field for the corresponding users document does not exist, I would like to set it to the default value of "Email".

I try the following approach:

try {
  if (snapshot.data().containsKey("signupMethod")) {
    wpUserProfile.signupMethod = snapshot.data["signupMethod"];
  } else {
    wpUserProfile.signupMethod = "Email";
  }
} on Exception catch (_) {
  wpUserProfile.signupMethod = "Email";
}

But it gives the following error:

Exception has occurred.
NoSuchMethodError (NoSuchMethodError: Class '_JsonDocumentSnapshot' has no instance method 'call'.
Receiver: Instance of '_JsonDocumentSnapshot'
Tried calling: call())

And I try the following instead:

try {
   wpUserProfile.signupMethod = snapshot.data["signupMethod"];
} on Exception catch (_) {
  wpUserProfile.signupMethod = "Email";
}

And I get the following error this time:

Exception has occurred.
StateError (Bad state: field does not exist within the DocumentSnapshotPlatform)

I do not understand why it does not run the code in the Exception block and set it to "Email".

Does anyone know how I should handle this situation when reading a field from Firestore which may or may not exist in a given document with flutter?


ADDENDUM: (Answer to Peter's question below)

If I print out snapshot.data, I get the following output:

flutter: Instance of '_JsonDocumentSnapshot'

If I print out snapshot.data(), I get the following error:

[VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception: NoSuchMethodError: Class '_JsonDocumentSnapshot' has no instance method 'call'.
Receiver: Instance of '_JsonDocumentSnapshot'
Tried calling: call()
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:63:5)
#1      _UserDataProviderState._setUserProfileData
package:wp/widgets/user_data_provider.dart:76
#2      _UserDataProviderState.build.<anonymous closure>
package:wp/widgets/user_data_provider.dart:135
#3      _FutureBuilderState.build
package:flutter/…/widgets/async.dart:782
#4      StatefulElement.build
package:flutter/…/widgets/framework.dart:4782
#5      ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4665
#6      StatefulElement.performRebuild
package:flutter/…/widgets/framework.dart:4840
#7      Element.rebuild
package:flutter/…/widgets/framework.dart:4355
#8      BuildOwner.buildScope
package:flutter/…/widgets/framework.dart:2620
#9      WidgetsBinding.drawFrame
package:flutter/…/widgets/binding.dart:882
#10     RendererBinding._handlePersistentFrameCallback
package:flutter/…/rendering/binding.dart:319
#11     SchedulerBinding._invokeFrameCallback
package:flutter/…/scheduler/binding.dart:1143
#12     SchedulerBinding.handleDrawFrame
package:flutter/…/scheduler/binding.dart:1080
#13     SchedulerBinding._handleDrawFrame
package:flutter/…/scheduler/binding.dart:996
#14     _rootRun (dart:async/zone.dart:1428:13)
#15     _CustomZone.run (dart:async/zone.dart:1328:19)
#16     _CustomZone.runGuarded (dart:async/zone.dart:1236:7)
#17     _invoke (dart:ui/hooks.dart:166:10)
#18     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:270:5)
#19     _drawFrame (dart:ui/hooks.dart:129:31)

If I just print out snapshot.data["email"], I get the following output:

[email protected]

Answer

Question answered by Peter K (source).

In your case I assume that snapshot contains an AsyncSnapshot object. According to the documentation it has a data property. This can be null, so it is always recommended to check hasData / hasError properties before processing the results.

So once you made sure it is not null, snapshot.data! will contain the result of your AsyncSnapshot, in this case this is a DocumentSnapshot.

Looking at documentation, DocumentSnapshot has a data() method that will hold the document itself. So finally snapshot.data!.data() will be your document, and you can get the fields from it like:

  • snapshot.data!.data()["signupMethod"], or
  • snapshot.data!.data().get("signupMethod")
  • (if you need the id of the document, it will be in snapshot.data!.id)

In both cases you will get null if the field does not exists, so you don't need containsKey.

Important: make sure to check if snapshot.data!.exists is true, because Firestore will give you the document snapshot even if the data you are looking for can't be found. If exists is true, you are good to go with the above.

DART FIREBASE FLUTTER GOOGLE-CLOUD-FIRESTORE
SHARE: