Skip to content
Permalink
Browse files
Support for update and merge writes (#298)
  • Loading branch information
collinjackson committed Dec 5, 2017
1 parent dd45c33 commit 2b89c20924da181dd53f8ca4bdb79bca27823962
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 9 deletions.
@@ -1,3 +1,8 @@
## 0.1.2

* Support for `DocumentReference` update and merge writes
* Suppress unchecked warnings and package name warnings on Android

## 0.1.1

* Added FLT prefix to iOS types.
@@ -2,9 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.firebase.cloud_firestore;
package io.flutter.plugins.firebase.cloudfirestore;

import android.support.annotation.NonNull;
import android.util.SparseArray;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.DocumentChange;
import com.google.firebase.firestore.DocumentReference;
@@ -15,6 +19,7 @@
import com.google.firebase.firestore.ListenerRegistration;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QuerySnapshot;
import com.google.firebase.firestore.SetOptions;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
@@ -27,7 +32,6 @@

public class CloudFirestorePlugin implements MethodCallHandler {

public static final String TAG = "FirestorePlugin";
private final MethodChannel channel;

// Handles are ints used as indexes into the sparse array of active observers
@@ -61,6 +65,7 @@ private Query getQuery(Map<String, Object> arguments) {
@SuppressWarnings("unchecked")
Map<String, Object> parameters = (Map<String, Object>) arguments.get("parameters");
if (parameters == null) return query;
@SuppressWarnings("unchecked")
List<List<Object>> whereConditions = (List<List<Object>>) parameters.get("where");
for (List<Object> condition : whereConditions) {
String fieldName = (String) condition.get(0);
@@ -80,6 +85,7 @@ private Query getQuery(Map<String, Object> arguments) {
// Invalid operator.
}
}
@SuppressWarnings("unchecked")
List<Object> orderBy = (List<Object>) parameters.get("orderBy");
if (orderBy == null) return query;
String orderByFieldName = (String) orderBy.get(0);
@@ -163,6 +169,23 @@ public void onEvent(QuerySnapshot querySnapshot, FirebaseFirestoreException e) {
}
}

private void addDefaultListeners(final String description, Task<Void> task, final Result result) {
task.addOnSuccessListener(
new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void ignored) {
result.success(null);
}
});
task.addOnFailureListener(
new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
result.error("Error performing " + description, e.getMessage(), null);
}
});
}

@Override
public void onMethodCall(MethodCall call, final Result result) {
switch (call.method) {
@@ -201,16 +224,33 @@ public void onMethodCall(MethodCall call, final Result result) {
{
Map<String, Object> arguments = call.arguments();
DocumentReference documentReference = getDocumentReference(arguments);
documentReference.set(arguments.get("data"));
result.success(null);
@SuppressWarnings("unchecked")
Map<String, Object> options = (Map<String, Object>) arguments.get("options");
Task<Void> task;
if (options != null && (Boolean) options.get("merge")) {
task = documentReference.set(arguments.get("data"), SetOptions.merge());
} else {
task = documentReference.set(arguments.get("data"));
}
addDefaultListeners("setData", task, result);
break;
}
case "DocumentReference#updateData":
{
Map<String, Object> arguments = call.arguments();
DocumentReference documentReference = getDocumentReference(arguments);
@SuppressWarnings("unchecked")
Map<String, Object> data = (Map<String, Object>) arguments.get("data");
Task<Void> task = documentReference.update(data);
addDefaultListeners("updateData", task, result);
break;
}
case "DocumentReference#delete":
{
Map<String, Object> arguments = call.arguments();
DocumentReference documentReference = getDocumentReference(arguments);
documentReference.delete();
result.success(null);
Task<Void> task = documentReference.delete();
addDefaultListeners("delete", task, result);
break;
}
default:
@@ -87,8 +87,20 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
};
if ([@"DocumentReference#setData" isEqualToString:call.method]) {
NSString *path = call.arguments[@"path"];
NSDictionary *options = call.arguments[@"options"];
FIRDocumentReference *reference = [[FIRFirestore firestore] documentWithPath:path];
[reference setData:call.arguments[@"data"] completion:defaultCompletionBlock];
if (![options isEqual:[NSNull null]] &&
[options[@"merge"] isEqual:[NSNumber numberWithBool:YES]]) {
[reference setData:call.arguments[@"data"]
options:[FIRSetOptions merge]
completion:defaultCompletionBlock];
} else {
[reference setData:call.arguments[@"data"] completion:defaultCompletionBlock];
}
} else if ([@"DocumentReference#updateData" isEqualToString:call.method]) {
NSString *path = call.arguments[@"path"];
FIRDocumentReference *reference = [[FIRFirestore firestore] documentWithPath:path];
[reference updateData:call.arguments[@"data"] completion:defaultCompletionBlock];
} else if ([@"DocumentReference#delete" isEqualToString:call.method]) {
NSString *path = call.arguments[@"path"];
FIRDocumentReference *reference = [[FIRFirestore firestore] documentWithPath:path];
@@ -19,3 +19,4 @@ part 'src/document_reference.dart';
part 'src/firestore.dart';
part 'src/query.dart';
part 'src/query_snapshot.dart';
part 'src/set_options.dart';
@@ -22,13 +22,27 @@ class DocumentReference {
/// Slash-delimited path representing the database location of this query.
String get path => _pathComponents.join('/');

Future<Null> setData(Map<String, dynamic> data) {
/// Writes to the document referred to by this [DocumentReference]. If the
/// document does not yet exist, it will be created. If you pass [SetOptions],
/// the provided data will be merged into an existing document.
Future<Null> setData(Map<String, dynamic> data, [SetOptions options]) {
return Firestore.channel.invokeMethod(
'DocumentReference#setData',
<String, dynamic>{'path': path, 'data': data, 'options': options?._data},
);
}

/// Updates fields in the document referred to by this [DocumentReference].
///
/// If no document exists yet, the update will fail.
Future<Null> updateData(Map<String, dynamic> data) {
return Firestore.channel.invokeMethod(
'DocumentReference#updateData',
<String, dynamic>{'path': path, 'data': data},
);
}

/// Deletes the document referred to by this [DocumentReference].
Future<Null> delete() {
return Firestore.channel.invokeMethod(
'DocumentReference#delete',
@@ -0,0 +1,24 @@
// Copyright 2017, 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;

/// An options object that configures the behavior of [setData()] calls.
///
/// By providing the [SetOptions] objects returned by [merge], the [setData()]
/// calls on [DocumentReference] can be configured to perform granular merges
/// instead of overwriting the target documents in their entirety.
class SetOptions {
const SetOptions._(this._data);
final Map<String, dynamic> _data;

/// Changes the behavior of set() calls to only replace the values specified
/// in its data argument.
static const SetOptions merge = const SetOptions._(
const <String, dynamic>{'merge': true},
);

// TODO(jackson): The Android Firestore SDK supports `mergeFieldPaths` and
// `mergeFields`, but these options don't seem to be available yet on iOS.
}
@@ -7,7 +7,7 @@ version: 0.1.1

flutter:
plugin:
androidPackage: io.flutter.plugins.firebase.cloud_firestore
androidPackage: io.flutter.plugins.firebase.cloudfirestore
iosPrefix: FLT
pluginClass: CloudFirestorePlugin

@@ -219,6 +219,43 @@ void main() {
<Matcher>[
isMethodCall(
'DocumentReference#setData',
arguments: <String, dynamic>{
'path': 'foo/bar',
'data': <String, String>{'bazKey': 'quxValue'},
'options': null,
},
),
],
);
});
test('merge set', () async {
await collectionReference
.document('bar')
.setData(<String, String>{'bazKey': 'quxValue'}, SetOptions.merge);
expect(SetOptions.merge, isNotNull);
expect(
log,
<Matcher>[
isMethodCall(
'DocumentReference#setData',
arguments: <String, dynamic>{
'path': 'foo/bar',
'data': <String, String>{'bazKey': 'quxValue'},
'options': <String, bool>{'merge': true},
},
),
],
);
});
test('update', () async {
await collectionReference
.document('bar')
.updateData(<String, String>{'bazKey': 'quxValue'});
expect(
log,
<Matcher>[
isMethodCall(
'DocumentReference#updateData',
arguments: <String, dynamic>{
'path': 'foo/bar',
'data': <String, String>{'bazKey': 'quxValue'},

0 comments on commit 2b89c20

Please sign in to comment.