google / built_value.dart

Immutable value types, enum classes, and serialization.
https://pub.dev/packages/built_value
BSD 3-Clause "New" or "Revised" License
861 stars 183 forks source link

Can't deserialize DateTime from Firestore, but locally it works. Both is a DateTime. #454

Closed Jonas-Sander closed 6 years ago

Jonas-Sander commented 6 years ago

I am trying to deserialize a document from Firestore into my homework object, but I get this error message:

E/flutter ( 7596): [ERROR:topaz/lib/tonic/logging/dart_error.cc(16)] Unhandled exception:
E/flutter ( 7596): Deserializing '[subject, Geschichte, todoUntil, 2018-07-16 04:00:00.000, forUsers, {userID1:...' to 'Homework' failed due to: Deserializing '2018-07-16 04:00:00.000' to 'DateTime' failed due to: type 'DateTime' is not a subtype of type 'int' in type cast
E/flutter ( 7596): #0      BuiltJsonSerializers._deserialize (package:built_value/src/built_json_serializers.dart:151:11)
E/flutter ( 7596): #1      BuiltJsonSerializers.deserialize (package:built_value/src/built_json_serializers.dart:102:18)
E/flutter ( 7596): #2      BuiltJsonSerializers.deserializeWith (package:built_value/src/built_json_serializers.dart:32:12)
E/flutter ( 7596): #3      parseHomework (package:sharezone/models/homework.dart:34:60)
E/flutter ( 7596): #4      main (file:///C:/Users/jonas/AndroidStudioProjects/sharezone/lib/main.dart:19:17)
E/flutter ( 7596): <asynchronous suspension>
E/flutter ( 7596): #5      _startIsolate.<anonymous closure> (dart:isolate/runtime/libisolate_patch.dart:279:19)
E/flutter ( 7596): #6      _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:165:12)

The thing is that it works locally with DateTime.now but doesn't with the DateTime in the Firestore Document. Here is the Firestore Document returned: firestore homework document

//THIS WORKS:
  Homework localHomework = new Homework((b) => b
    ..documentID = "ABC"
    ..subject = "Geschichte"
    ..subjectAbbreviation = "GE"
    ..title = "Geschichtshausaufgabe"
    ..todoUntil = DateTime.now() // <- Works
    ..forUsers = MapBuilder({"UserID" : true})
  );

  //THIS DOES NOT WORK:
  DocumentSnapshot docSnap = await Firestore.instance.collection("Homework").document("unitTestDocument_DONT_DELETE").get();
  Homework firestoreHomework = parseHomework(docSnap);

homework.dart

import 'package:built_value/built_value.dart';
import 'package:built_collection/built_collection.dart';
import 'package:built_value/serializer.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'serializers.dart';

part 'homework.g.dart';

//TO RUN: flutter packages pub run build_runner build
//OR: flutter packages pub run build_runner watch

abstract class Homework implements Built<Homework, HomeworkBuilder> {
  @nullable
  String get documentID;
  String get subject;
  String get subjectAbbreviation;
  String get title;
  DateTime get todoUntil;
  @nullable
  String get description;
  BuiltMap<String, bool> get forUsers;

  static Serializer<Homework> get serializer => _$homeworkSerializer;

  Homework._();
  factory Homework([updates(HomeworkBuilder b)]) = _$Homework;
}

Homework parseHomework(DocumentSnapshot homeworkDocument) {
  Homework homeworkWithoutDocumentID = standardSerializers.deserializeWith<Homework>(Homework.serializer, homeworkDocument.data);
  //As the document ID is not in the Map, but an attribute of the Document I'll have to add it manually. Any way to fix this?
  var homeworkWithDocID = homeworkWithoutDocumentID.rebuild((b) => b
    ..documentID = homeworkDocument.documentID);

  assert(homeworkWithDocID.documentID != null);

  return homeworkWithDocID;
}

serializers.dart

library serializers;

import 'package:built_collection/built_collection.dart';
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
import 'homework.dart';

part 'serializers.g.dart';

@SerializersFor(const [
  Homework,
])
final Serializers serializers = _$serializers;

final standardSerializers =
(serializers.toBuilder()..addPlugin(new StandardJsonPlugin())).build();

I hope it's not too much or litte... This is my first issue ;), so if I can improve something writing these let me know.

dave26199 commented 6 years ago

There's already an alternative serializer--just need to install it:

https://github.com/google/built_value.dart/blob/master/built_value/lib/iso_8601_date_time_serializer.dart

Apologies for brevity, posted from phone.

Jonas-Sander commented 6 years ago

@dave26199 Thank you very much for answering, unfortunately I still get an error message. It is now:

E/flutter ( 8649): Deserializing '[subject, Geschichte, todoUntil, 2018-07-16 04:00:00.000, forUsers, {userID1:...' to 'Homework' failed due to: Deserializing '2018-07-16 04:00:00.000' to 'DateTime' failed due to: type 'DateTime' is not a subtype of type 'String' in type cast
E/flutter ( 8649): #0      BuiltJsonSerializers._deserialize (package:built_value/src/built_json_serializers.dart:151:11)
E/flutter ( 8649): #1      BuiltJsonSerializers.deserialize (package:built_value/src/built_json_serializers.dart:102:18)
E/flutter ( 8649): #2      BuiltJsonSerializers.deserializeWith (package:built_value/src/built_json_serializers.dart:32:12)
E/flutter ( 8649): #3      parseHomework (package:sharezone/models/homework.dart:34:60)
E/flutter ( 8649): #4      main (file:///C:/Users/jonas/AndroidStudioProjects/sharezone/lib/main.dart:31:32)
E/flutter ( 8649): <asynchronous suspension>
E/flutter ( 8649): #5      _startIsolate.<anonymous closure> (dart:isolate/runtime/libisolate_patch.dart:279:19)
E/flutter ( 8649): #6      _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:165:12)

So now instead of saying type 'DateTime' is not a subtype of type 'int' in type cast it say's type 'DateTime' is not a subtype of type 'String' in type cast

Ich changed the line:

final standardSerializers =
(serializers.toBuilder()..addPlugin(new StandardJsonPlugin())).build();

to

final standardSerializers =
(serializers.toBuilder()..addPlugin(new StandardJsonPlugin())..add(Iso8601DateTimeSerializer())).build();

Did I do something wrong?

dave26199 commented 6 years ago

Ah, I understand now. The problem is that it actually does not need serializing at all, it's already a DateTime. You can do that following the instructions for GeoPoint here:

https://github.com/google/built_value.dart/issues/417

I.e. add your own "serializer" that does nothing.

Apologies for brevity, sent from phone...

Jonas-Sander commented 6 years ago

@dave26199 Thank you very much for your help! Everything working now.

For anybody who has the same issue, here's the solution:

1. Make an own serializer, which does nothing

In my case:
import 'package:built_collection/built_collection.dart';
import 'package:built_value/serializer.dart';

///A Serializer which does basically "nothing".
/// It is used, as a DateTime returned by Firestore is ALREADY a DateTime Object
/// and not just "data" which has to be deserialized into a DateTime.
class DateTimeSerializer implements PrimitiveSerializer<DateTime> {
  final bool structured = false;
  @override
  final Iterable<Type> types = new BuiltList<Type>([DateTime]);
  @override
  final String wireName = 'GeoPoint';

  @override
  Object serialize(Serializers serializers, DateTime geoPoint,
      {FullType specifiedType: FullType.unspecified}) {
    return geoPoint;
  }

  @override
  DateTime deserialize(Serializers serializers, Object serialized,
      {FullType specifiedType: FullType.unspecified}) {
    return serialized as DateTime;
  }
}

2. Add the DateTimeSerializer to your serializers

final serializers =
(_$serializers.toBuilder()..add(DateTimeSerializer())..addPlugin(new StandardJsonPlugin())).build();