Skip to content

Commit c83e01b

Browse files
Merge pull request firebase#553 from firebase/mrschmidt-nullable
Making DocumentSnapshot.data() nullable
2 parents 3c90716 + cc695ee commit c83e01b

File tree

8 files changed

+128
-64
lines changed

8 files changed

+128
-64
lines changed

Firestore/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
- [feature] Queries can now be created from an NSPredicate.
66
- [added] Added SnapshotOptions API to control how DocumentSnapshots return unresolved
77
server timestamps.
8+
- [changed] For non-existing documents, DocumentSnapshot.data() now returns `nil`
9+
instead of throwing an exception. A non-nullable QueryDocumentSnapshot is
10+
introduced for Queries to reduce the number of nil-checks in your code.
811

912
# v0.9.4
1013
- [changed] Firestore no longer has a direct dependency on FirebaseAuth.

Firestore/Example/Tests/Integration/API/FIRDatabaseTests.m

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ - (void)testDeleteDocument {
8383
XCTAssertFalse(result.exists);
8484
}
8585

86+
- (void)testCanRetrieveDocumentThatDoesNotExist {
87+
FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
88+
FIRDocumentSnapshot *result = [self readDocumentForRef:doc];
89+
XCTAssertNil(result.data);
90+
XCTAssertNil(result[@"foo"]);
91+
}
92+
8693
- (void)testCannotUpdateNonexistentDocument {
8794
FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
8895

Firestore/Source/API/FIRDocumentChange.m

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ + (FIRDocumentChangeType)documentChangeTypeForChange:(FSTDocumentViewChange *)ch
5757
NSUInteger index = 0;
5858
NSMutableArray<FIRDocumentChange *> *changes = [NSMutableArray array];
5959
for (FSTDocumentViewChange *change in snapshot.documentChanges) {
60-
FIRDocumentSnapshot *document =
61-
[FIRDocumentSnapshot snapshotWithFirestore:firestore
62-
documentKey:change.document.key
63-
document:change.document
64-
fromCache:snapshot.isFromCache];
60+
FIRQueryDocumentSnapshot *document =
61+
[FIRQueryDocumentSnapshot snapshotWithFirestore:firestore
62+
documentKey:change.document.key
63+
document:change.document
64+
fromCache:snapshot.isFromCache];
6565
FSTAssert(change.type == FSTDocumentViewChangeTypeAdded,
6666
@"Invalid event type for first snapshot");
6767
FSTAssert(!lastDocument ||
@@ -79,11 +79,11 @@ + (FIRDocumentChangeType)documentChangeTypeForChange:(FSTDocumentViewChange *)ch
7979
FSTDocumentSet *indexTracker = snapshot.oldDocuments;
8080
NSMutableArray<FIRDocumentChange *> *changes = [NSMutableArray array];
8181
for (FSTDocumentViewChange *change in snapshot.documentChanges) {
82-
FIRDocumentSnapshot *document =
83-
[FIRDocumentSnapshot snapshotWithFirestore:firestore
84-
documentKey:change.document.key
85-
document:change.document
86-
fromCache:snapshot.isFromCache];
82+
FIRQueryDocumentSnapshot *document =
83+
[FIRQueryDocumentSnapshot snapshotWithFirestore:firestore
84+
documentKey:change.document.key
85+
document:change.document
86+
fromCache:snapshot.isFromCache];
8787

8888
NSUInteger oldIndex = NSNotFound;
8989
NSUInteger newIndex = NSNotFound;
@@ -112,7 +112,7 @@ + (FIRDocumentChangeType)documentChangeTypeForChange:(FSTDocumentViewChange *)ch
112112
@implementation FIRDocumentChange
113113

114114
- (instancetype)initWithType:(FIRDocumentChangeType)type
115-
document:(FIRDocumentSnapshot *)document
115+
document:(FIRQueryDocumentSnapshot *)document
116116
oldIndex:(NSUInteger)oldIndex
117117
newIndex:(NSUInteger)newIndex {
118118
if (self = [super init]) {

Firestore/Source/API/FIRDocumentSnapshot.m

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#import "Firestore/Source/Model/FSTDocumentKey.h"
2727
#import "Firestore/Source/Model/FSTFieldValue.h"
2828
#import "Firestore/Source/Model/FSTPath.h"
29+
#import "Firestore/Source/Util/FSTAssert.h"
2930
#import "Firestore/Source/Util/FSTUsageValidation.h"
3031

3132
NS_ASSUME_NONNULL_BEGIN
@@ -100,23 +101,15 @@ - (FIRSnapshotMetadata *)metadata {
100101
return _cachedMetadata;
101102
}
102103

103-
- (NSDictionary<NSString *, id> *)data {
104+
- (nullable NSDictionary<NSString *, id> *)data {
104105
return [self dataWithOptions:[FIRSnapshotOptions defaultOptions]];
105106
}
106107

107-
- (NSDictionary<NSString *, id> *)dataWithOptions:(FIRSnapshotOptions *)options {
108-
FSTDocument *document = self.internalDocument;
109-
110-
if (!document) {
111-
FSTThrowInvalidUsage(
112-
@"NonExistentDocumentException",
113-
@"Document '%@' doesn't exist. "
114-
@"Check document.exists to make sure the document exists before calling document.data.",
115-
self.internalKey);
116-
}
117-
118-
return [self convertedObject:[self.internalDocument data]
119-
options:[FSTFieldValueOptions optionsForSnapshotOptions:options]];
108+
- (nullable NSDictionary<NSString *, id> *)dataWithOptions:(FIRSnapshotOptions *)options {
109+
return self.internalDocument == nil
110+
? nil
111+
: [self convertedObject:[self.internalDocument data]
112+
options:[FSTFieldValueOptions optionsForSnapshotOptions:options]];
120113
}
121114

122115
- (nullable id)valueForField:(id)field {
@@ -135,8 +128,10 @@ - (nullable id)valueForField:(id)field options:(FIRSnapshotOptions *)options {
135128
}
136129

137130
FSTFieldValue *fieldValue = [[self.internalDocument data] valueForPath:fieldPath.internalValue];
138-
return [self convertedValue:fieldValue
139-
options:[FSTFieldValueOptions optionsForSnapshotOptions:options]];
131+
return fieldValue == nil
132+
? nil
133+
: [self convertedValue:fieldValue
134+
options:[FSTFieldValueOptions optionsForSnapshotOptions:options]];
140135
}
141136

142137
- (nullable id)objectForKeyedSubscript:(id)key {
@@ -190,4 +185,34 @@ - (id)convertedValue:(FSTFieldValue *)value options:(FSTFieldValueOptions *)opti
190185

191186
@end
192187

188+
@interface FIRQueryDocumentSnapshot ()
189+
190+
- (instancetype)initWithFirestore:(FIRFirestore *)firestore
191+
documentKey:(FSTDocumentKey *)documentKey
192+
document:(FSTDocument *)document
193+
fromCache:(BOOL)fromCache NS_DESIGNATED_INITIALIZER;
194+
195+
@end
196+
197+
@implementation FIRQueryDocumentSnapshot
198+
199+
- (instancetype)initWithFirestore:(FIRFirestore *)firestore
200+
documentKey:(FSTDocumentKey *)documentKey
201+
document:(FSTDocument *)document
202+
fromCache:(BOOL)fromCache {
203+
self = [super initWithFirestore:firestore
204+
documentKey:documentKey
205+
document:document
206+
fromCache:fromCache];
207+
return self;
208+
}
209+
210+
- (NSDictionary<NSString *, id> *)data {
211+
NSDictionary<NSString *, id> *data = [super data];
212+
FSTAssert(data, @"Document in a QueryDocumentSnapshot should exist");
213+
return data;
214+
}
215+
216+
@end
217+
193218
NS_ASSUME_NONNULL_END

Firestore/Source/API/FIRQuerySnapshot.m

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ + (instancetype)snapshotWithFirestore:(FIRFirestore *)firestore
5757

5858
@implementation FIRQuerySnapshot {
5959
// Cached value of the documents property.
60-
NSArray<FIRDocumentSnapshot *> *_documents;
60+
NSArray<FIRQueryDocumentSnapshot *> *_documents;
6161

6262
// Cached value of the documentChanges property.
6363
NSArray<FIRDocumentChange *> *_documentChanges;
@@ -93,18 +93,18 @@ - (NSInteger)count {
9393
return self.snapshot.documents.count;
9494
}
9595

96-
- (NSArray<FIRDocumentSnapshot *> *)documents {
96+
- (NSArray<FIRQueryDocumentSnapshot *> *)documents {
9797
if (!_documents) {
9898
FSTDocumentSet *documentSet = self.snapshot.documents;
9999
FIRFirestore *firestore = self.firestore;
100100
BOOL fromCache = self.metadata.fromCache;
101101

102-
NSMutableArray<FIRDocumentSnapshot *> *result = [NSMutableArray array];
102+
NSMutableArray<FIRQueryDocumentSnapshot *> *result = [NSMutableArray array];
103103
for (FSTDocument *document in documentSet.documentEnumerator) {
104-
[result addObject:[FIRDocumentSnapshot snapshotWithFirestore:firestore
105-
documentKey:document.key
106-
document:document
107-
fromCache:fromCache]];
104+
[result addObject:[FIRQueryDocumentSnapshot snapshotWithFirestore:firestore
105+
documentKey:document.key
106+
document:document
107+
fromCache:fromCache]];
108108
}
109109

110110
_documents = result;

Firestore/Source/Public/FIRDocumentChange.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
NS_ASSUME_NONNULL_BEGIN
2020

21-
@class FIRDocumentSnapshot;
21+
@class FIRQueryDocumentSnapshot;
2222

2323
/** An enumeration of document change types. */
2424
typedef NS_ENUM(NSInteger, FIRDocumentChangeType) {
@@ -47,7 +47,7 @@ NS_SWIFT_NAME(DocumentChange)
4747
@property(nonatomic, readonly) FIRDocumentChangeType type;
4848

4949
/** The document affected by this change. */
50-
@property(nonatomic, strong, readonly) FIRDocumentSnapshot *document;
50+
@property(nonatomic, strong, readonly) FIRQueryDocumentSnapshot *document;
5151

5252
/**
5353
* The index of the changed document in the result set immediately prior to this FIRDocumentChange

Firestore/Source/Public/FIRDocumentSnapshot.h

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ NS_SWIFT_NAME(SnapshotOptions)
7474
* A `FIRDocumentSnapshot` contains data read from a document in your Firestore database. The data
7575
* can be extracted with the `data` property or by using subscript syntax to access a specific
7676
* field.
77+
*
78+
* For a `FIRDocumentSnapshot` that points to non-existing document, any data access will return
79+
* `nil`. You can use the `exists` property to explicitly verify a documents existence.
7780
*/
7881
NS_SWIFT_NAME(DocumentSnapshot)
7982
@interface FIRDocumentSnapshot : NSObject
@@ -95,50 +98,51 @@ NS_SWIFT_NAME(DocumentSnapshot)
9598
@property(nonatomic, strong, readonly) FIRSnapshotMetadata *metadata;
9699

97100
/**
98-
* Retrieves all fields in the document as an `NSDictionary`.
101+
* Retrieves all fields in the document as an `NSDictionary`. Returns `nil` if the document doesn't
102+
* exist.
99103
*
100-
* Server-provided timestamps that have not yet been set to their final value
101-
* will be returned as `NSNull`. You can use `dataWithOptions()` to configure this
102-
* behavior.
104+
* Server-provided timestamps that have not yet been set to their final value will be returned as
105+
* `NSNull`. You can use `dataWithOptions()` to configure this behavior.
103106
*
104-
* @return An `NSDictionary` containing all fields in the document.
107+
* @return An `NSDictionary` containing all fields in the document or `nil` if the document doesn't
108+
* exist.
105109
*/
106-
- (NSDictionary<NSString *, id> *)data;
110+
- (nullable NSDictionary<NSString *, id> *)data;
107111

108112
/**
109-
* Retrieves all fields in the document as a `Dictionary`.
113+
* Retrieves all fields in the document as a `Dictionary`. Returns `nil` if the document doesn't
114+
* exist.
110115
*
111-
* @param options `SnapshotOptions` to configure how data is returned from the
112-
* snapshot (e.g. the desired behavior for server timestamps that have not
113-
* yet been set to their final value).
114-
* @return A `Dictionary` containing all fields in the document.
116+
* @param options `SnapshotOptions` to configure how data is returned from the snapshot (e.g. the
117+
* desired behavior for server timestamps that have not yet been set to their final value).
118+
* @return A `Dictionary` containing all fields in the document or `nil` if the document doesn't
119+
* exist.
115120
*/
116-
- (NSDictionary<NSString *, id> *)dataWithOptions:(FIRSnapshotOptions *)options;
121+
- (nullable NSDictionary<NSString *, id> *)dataWithOptions:(FIRSnapshotOptions *)options;
117122

118123
/**
119-
* Retrieves a specific field from the document.
124+
* Retrieves a specific field from the document. Returns `nil` if the document or the field doesn't
125+
* exist.
120126
*
121-
* The timestamps that have not yet been set to their final value
122-
* will be returned as `NSNull`. The can use `get(_:options:)` to
123-
* configure this behavior.
127+
* The timestamps that have not yet been set to their final value will be returned as `NSNull`. The
128+
* can use `get(_:options:)` to configure this behavior.
124129
*
125130
* @param field The field to retrieve.
126-
* @return The value contained in the field or `nil` if the field doesn't exist.
131+
* @return The value contained in the field or `nil` if the document or field doesn't exist.
127132
*/
128133
- (nullable id)valueForField:(id)field NS_SWIFT_NAME(get(_:));
129134

130135
/**
131-
* Retrieves a specific field from the document.
136+
* Retrieves a specific field from the document. Returns `nil` if the document or the field doesn't
137+
* exist.
132138
*
133-
* The timestamps that have not yet been set to their final value
134-
* will be returned as `NSNull`. The can use `get(_:options:)` to
135-
* configure this behavior.
139+
* The timestamps that have not yet been set to their final value will be returned as `NSNull`. The
140+
* can use `get(_:options:)` to configure this behavior.
136141
*
137142
* @param field The field to retrieve.
138-
* @param options `SnapshotOptions` to configure how data is returned from the
139-
* snapshot (e.g. the desired behavior for server timestamps that have not
140-
* yet been set to their final value).
141-
* @return The value contained in the field or `nil` if the field doesn't exist.
143+
* @param options `SnapshotOptions` to configure how data is returned from the snapshot (e.g. the
144+
* desired behavior for server timestamps that have not yet been set to their final value).
145+
* @return The value contained in the field or `nil` if the document or field doesn't exist.
142146
*/
143147
// clang-format off
144148
- (nullable id)valueForField:(id)field
@@ -151,10 +155,35 @@ NS_SWIFT_NAME(DocumentSnapshot)
151155
*
152156
* @param key The field to retrieve.
153157
*
154-
* @return The value contained in the field or `nil` if the field doesn't exist.
158+
* @return The value contained in the field or `nil` if the document or field doesn't exist.
155159
*/
156160
- (nullable id)objectForKeyedSubscript:(id)key;
157161

158162
@end
159163

164+
/**
165+
* A `FIRQueryDocumentSnapshot` contains data read from a document in your Firestore database as
166+
* part of a query. The document is guaranteed to exist and its data can be extracted with the
167+
* `data` property or by using subscript syntax to access a specific field.
168+
*
169+
* A `FIRQueryDocumentSnapshot` offers the same API surface as a `FIRDocumentSnapshot`. As
170+
* deleted documents are not returned from queries, its `exists` property will always be true and
171+
* `data:` will never return `nil`.
172+
*/
173+
NS_SWIFT_NAME(QueryDocumentSnapshot)
174+
@interface FIRQueryDocumentSnapshot : FIRDocumentSnapshot
175+
176+
/** */
177+
- (instancetype)init
178+
__attribute__((unavailable("FIRQueryDocumentSnapshot cannot be created directly.")));
179+
180+
/**
181+
* Retrieves all fields in the document as an `NSDictionary`.
182+
*
183+
* @return An `NSDictionary` containing all fields in the document.
184+
*/
185+
- (NSDictionary<NSString *, id> *)data;
186+
187+
@end
188+
160189
NS_ASSUME_NONNULL_END

Firestore/Source/Public/FIRQuerySnapshot.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
NS_ASSUME_NONNULL_BEGIN
2020

2121
@class FIRDocumentChange;
22-
@class FIRDocumentSnapshot;
2322
@class FIRQuery;
23+
@class FIRQueryDocumentSnapshot;
2424
@class FIRSnapshotMetadata;
2525

2626
/**
@@ -50,7 +50,7 @@ NS_SWIFT_NAME(QuerySnapshot)
5050
@property(nonatomic, readonly) NSInteger count;
5151

5252
/** An Array of the `FIRDocumentSnapshots` that make up this document set. */
53-
@property(nonatomic, strong, readonly) NSArray<FIRDocumentSnapshot *> *documents;
53+
@property(nonatomic, strong, readonly) NSArray<FIRQueryDocumentSnapshot *> *documents;
5454

5555
/**
5656
* An array of the documents that changed since the last snapshot. If this is the first snapshot,

0 commit comments

Comments
 (0)