Skip to content

Commit

Permalink
Timestamps firestore (flutter#765)
Browse files Browse the repository at this point in the history
* Adding support for settings and timestamp
  • Loading branch information
DennisAlund authored and kroikie committed Oct 18, 2018
1 parent 50a283b commit 6cd8c67
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 3 deletions.
5 changes: 5 additions & 0 deletions packages/cloud_firestore/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.8.2

* Added `Firestore.settings`
* Added `Timestamp` class

## 0.8.1+1

* Bump Android dependencies to latest.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.google.android.gms.tasks.TaskCompletionSource;
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.FirebaseApp;
import com.google.firebase.Timestamp;
import com.google.firebase.firestore.Blob;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.DocumentChange;
Expand Down Expand Up @@ -571,6 +572,33 @@ public void onFailure(@NonNull Exception e) {
Boolean enable = (Boolean) arguments.get("enable");
FirebaseFirestoreSettings.Builder builder = new FirebaseFirestoreSettings.Builder();
builder.setPersistenceEnabled(enable);
FirebaseFirestoreSettings settings = builder.build();
getFirestore(arguments).setFirestoreSettings(settings);
result.success(null);
break;
}
case "Firestore#settings":
{
final Map<String, Object> arguments = call.arguments();
final FirebaseFirestoreSettings.Builder builder = new FirebaseFirestoreSettings.Builder();

if (arguments.get("persistenceEnabled") != null) {
builder.setPersistenceEnabled((Boolean) arguments.get("persistenceEnabled"));
}

if (arguments.get("host") != null) {
builder.setHost((String) arguments.get("host"));
}

if (arguments.get("sslEnabled") != null) {
builder.setSslEnabled((Boolean) arguments.get("sslEnabled"));
}

if (arguments.get("timestampsInSnapshotsEnabled") != null) {
builder.setTimestampsInSnapshotsEnabled(
(Boolean) arguments.get("timestampsInSnapshotsEnabled"));
}

FirebaseFirestoreSettings settings = builder.build();
getFirestore(arguments).setFirestoreSettings(settings);
result.success(null);
Expand All @@ -596,12 +624,17 @@ final class FirestoreMessageCodec extends StandardMessageCodec {
private static final byte ARRAY_REMOVE = (byte) 133;
private static final byte DELETE = (byte) 134;
private static final byte SERVER_TIMESTAMP = (byte) 135;
private static final byte TIMESTAMP = (byte) 136;

@Override
protected void writeValue(ByteArrayOutputStream stream, Object value) {
if (value instanceof Date) {
stream.write(DATE_TIME);
writeLong(stream, ((Date) value).getTime());
} else if (value instanceof Timestamp) {
stream.write(TIMESTAMP);
writeLong(stream, ((Timestamp) value).getSeconds());
writeInt(stream, ((Timestamp) value).getNanoseconds());
} else if (value instanceof GeoPoint) {
stream.write(GEO_POINT);
writeAlignment(stream, 8);
Expand All @@ -625,6 +658,8 @@ protected Object readValueOfType(byte type, ByteBuffer buffer) {
switch (type) {
case DATE_TIME:
return new Date(buffer.getLong());
case TIMESTAMP:
return new Timestamp(buffer.getLong(), buffer.getInt());
case GEO_POINT:
readAlignment(buffer, 8);
return new GeoPoint(buffer.getDouble(), buffer.getDouble());
Expand Down
5 changes: 3 additions & 2 deletions packages/cloud_firestore/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Future<void> main() async {
),
);
final Firestore firestore = Firestore(app: app);
await firestore.settings(timestampsInSnapshotsEnabled: true);

runApp(MaterialApp(
title: 'Firestore Example', home: MyHomePage(firestore: firestore)));
Expand Down Expand Up @@ -57,9 +58,9 @@ class MyHomePage extends StatelessWidget {
CollectionReference get messages => firestore.collection('messages');

Future<Null> _addMessage() async {
final DocumentReference document = messages.document();
document.setData(<String, dynamic>{
await messages.add(<String, dynamic>{
'message': 'Hello world!',
'created_at': FieldValue.serverTimestamp(),
});
}

Expand Down
32 changes: 32 additions & 0 deletions packages/cloud_firestore/ios/Classes/CloudFirestorePlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ - (FlutterError *)flutterError {
const UInt8 ARRAY_REMOVE = 133;
const UInt8 DELETE = 134;
const UInt8 SERVER_TIMESTAMP = 135;
const UInt8 TIMESTAMP = 136;

@interface FirestoreWriter : FlutterStandardWriter
- (void)writeValue:(id)value;
Expand All @@ -146,6 +147,13 @@ - (void)writeValue:(id)value {
NSTimeInterval time = date.timeIntervalSince1970;
SInt64 ms = (SInt64)(time * 1000.0);
[self writeBytes:&ms length:8];
} else if ([value isKindOfClass:[FIRTimestamp class]]) {
FIRTimestamp *timestamp = value;
SInt64 seconds = timestamp.seconds;
int nanoseconds = timestamp.nanoseconds;
[self writeByte:TIMESTAMP];
[self writeBytes:(UInt8 *)&seconds length:8];
[self writeBytes:(UInt8 *)&nanoseconds length:4];
} else if ([value isKindOfClass:[FIRGeoPoint class]]) {
FIRGeoPoint *geoPoint = value;
Float64 latitude = geoPoint.latitude;
Expand Down Expand Up @@ -184,6 +192,13 @@ - (id)readValueOfType:(UInt8)type {
NSTimeInterval time = [NSNumber numberWithLong:value].doubleValue / 1000.0;
return [NSDate dateWithTimeIntervalSince1970:time];
}
case TIMESTAMP: {
SInt64 seconds;
int nanoseconds;
[self readBytes:&seconds length:8];
[self readBytes:&nanoseconds length:4];
return [[FIRTimestamp alloc] initWithSeconds:seconds nanoseconds:nanoseconds];
}
case GEO_POINT: {
Float64 latitude;
Float64 longitude;
Expand Down Expand Up @@ -482,6 +497,23 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
FIRFirestore *db = getFirestore(call.arguments);
db.settings = settings;
result(nil);
} else if ([@"Firestore#settings" isEqualToString:call.method]) {
FIRFirestoreSettings *settings = [[FIRFirestoreSettings alloc] init];
if (![call.arguments[@"persistenceEnabled"] isEqual:[NSNull null]]) {
settings.persistenceEnabled = (bool)call.arguments[@"persistenceEnabled"];
}
if (![call.arguments[@"host"] isEqual:[NSNull null]]) {
settings.host = (NSString *)call.arguments[@"host"];
}
if (![call.arguments[@"sslEnabled"] isEqual:[NSNull null]]) {
settings.sslEnabled = (bool)call.arguments[@"sslEnabled"];
}
if (![call.arguments[@"timestampsInSnapshotsEnabled"] isEqual:[NSNull null]]) {
settings.timestampsInSnapshotsEnabled = (bool)call.arguments[@"timestampsInSnapshotsEnabled"];
}
FIRFirestore *db = getFirestore(call.arguments);
db.settings = settings;
result(nil);
} else {
result(FlutterMethodNotImplemented);
}
Expand Down
1 change: 1 addition & 0 deletions packages/cloud_firestore/lib/cloud_firestore.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ part 'src/geo_point.dart';
part 'src/query.dart';
part 'src/query_snapshot.dart';
part 'src/snapshot_metadata.dart';
part 'src/timestamp.dart';
part 'src/transaction.dart';
part 'src/write_batch.dart';
15 changes: 15 additions & 0 deletions packages/cloud_firestore/lib/src/firestore.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,26 @@ class Firestore {
return result?.cast<String, dynamic>() ?? <String, dynamic>{};
}

@deprecated
Future<void> enablePersistence(bool enable) async {
assert(enable != null);
await channel.invokeMethod('Firestore#enablePersistence', <String, dynamic>{
'app': app.name,
'enable': enable,
});
}

Future<void> settings(
{bool persistenceEnabled,
String host,
bool sslEnabled,
bool timestampsInSnapshotsEnabled}) async {
await channel.invokeMethod('Firestore#settings', <String, dynamic>{
'app': app.name,
'persistenceEnabled': persistenceEnabled,
'host': host,
'sslEnabled': sslEnabled,
'timestampsInSnapshotsEnabled': timestampsInSnapshotsEnabled,
});
}
}
7 changes: 7 additions & 0 deletions packages/cloud_firestore/lib/src/firestore_message_codec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class FirestoreMessageCodec extends StandardMessageCodec {
static const int _kArrayRemove = 133;
static const int _kDelete = 134;
static const int _kServerTimestamp = 135;
static const int _kTimestamp = 136;

static const Map<FieldValueType, int> _kFieldValueCodes =
<FieldValueType, int>{
Expand All @@ -30,6 +31,10 @@ class FirestoreMessageCodec extends StandardMessageCodec {
if (value is DateTime) {
buffer.putUint8(_kDateTime);
buffer.putInt64(value.millisecondsSinceEpoch);
} else if (value is Timestamp) {
buffer.putUint8(_kTimestamp);
buffer.putInt64(value.seconds);
buffer.putInt32(value.nanoseconds);
} else if (value is GeoPoint) {
buffer.putUint8(_kGeoPoint);
buffer.putFloat64(value.latitude);
Expand Down Expand Up @@ -61,6 +66,8 @@ class FirestoreMessageCodec extends StandardMessageCodec {
switch (type) {
case _kDateTime:
return DateTime.fromMillisecondsSinceEpoch(buffer.getInt64());
case _kTimestamp:
return Timestamp(buffer.getInt64(), buffer.getInt32());
case _kGeoPoint:
return GeoPoint(buffer.getFloat64(), buffer.getFloat64());
case _kDocumentReference:
Expand Down
98 changes: 98 additions & 0 deletions packages/cloud_firestore/lib/src/timestamp.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2018, the Chromium project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

part of cloud_firestore;

const int _kThousand = 1000;
const int _kMillion = 1000000;
const int _kBillion = 1000000000;

void _check(bool expr, String name, int value) {
if (!expr) {
throw ArgumentError("Timestamp $name out of range: $value");
}
}

/// A Timestamp represents a point in time independent of any time zone or calendar,
/// represented as seconds and fractions of seconds at nanosecond resolution in UTC
/// Epoch time. It is encoded using the Proleptic Gregorian Calendar which extends
/// the Gregorian calendar backwards to year one. It is encoded assuming all minutes
/// are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second table
/// is needed for interpretation. Range is from 0001-01-01T00:00:00Z to
/// 9999-12-31T23:59:59.999999999Z. By restricting to that range, we ensure that we
/// can convert to and from RFC 3339 date strings.
///
/// For more information, see [the reference timestamp definition](https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto)
class Timestamp implements Comparable<Timestamp> {
Timestamp(this._seconds, this._nanoseconds) {
_validateRange(_seconds, _nanoseconds);
}

factory Timestamp.fromMillisecondsSinceEpoch(int milliseconds) {
final int seconds = (milliseconds / _kThousand).floor();
final int nanoseconds = (milliseconds - seconds * _kThousand) * _kMillion;
return Timestamp(seconds, nanoseconds);
}

factory Timestamp.fromMicrosecondsSinceEpoch(int microseconds) {
final int seconds = (microseconds / _kMillion).floor();
final int nanoseconds = (microseconds - seconds * _kMillion) * _kThousand;
return Timestamp(seconds, nanoseconds);
}

factory Timestamp.fromDate(DateTime date) {
return Timestamp.fromMicrosecondsSinceEpoch(date.microsecondsSinceEpoch);
}

factory Timestamp.now() {
return Timestamp.fromMicrosecondsSinceEpoch(
DateTime.now().microsecondsSinceEpoch);
}

final int _seconds;
final int _nanoseconds;

static const int _kStartOfTime = -62135596800;
static const int _kEndOfTime = 253402300800;

int get seconds => _seconds;

int get nanoseconds => _nanoseconds;

int get millisecondsSinceEpoch =>
(seconds * _kThousand + nanoseconds / _kMillion).floor();

int get microsecondsSinceEpoch =>
(seconds * _kMillion + nanoseconds / _kThousand).floor();

DateTime toDate() {
return DateTime.fromMicrosecondsSinceEpoch(microsecondsSinceEpoch);
}

@override
int get hashCode => hashValues(seconds, nanoseconds);
@override
bool operator ==(dynamic o) =>
o is Timestamp && o.seconds == seconds && o.nanoseconds == nanoseconds;
@override
int compareTo(Timestamp other) {
if (seconds == other.seconds) {
return nanoseconds.compareTo(other.nanoseconds);
}

return seconds.compareTo(other.seconds);
}

@override
String toString() {
return "Timestamp(seconds=$seconds, nanoseconds=$nanoseconds)";
}

static void _validateRange(int seconds, int nanoseconds) {
_check(nanoseconds >= 0, 'nanoseconds', nanoseconds);
_check(nanoseconds < _kBillion, 'nanoseconds', nanoseconds);
_check(seconds >= _kStartOfTime, 'seconds', seconds);
_check(seconds < _kEndOfTime, 'seconds', seconds);
}
}
2 changes: 1 addition & 1 deletion packages/cloud_firestore/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Flutter plugin for Cloud Firestore, a cloud-hosted, noSQL database
live synchronization and offline support on Android and iOS.
author: Flutter Team <[email protected]>
homepage: https://github.com/flutter/plugins/tree/master/packages/cloud_firestore
version: 0.8.1+1
version: 0.8.2

flutter:
plugin:
Expand Down
Loading

0 comments on commit 6cd8c67

Please sign in to comment.