DocumentReference+Combine.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. // Copyright 2021 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Combine
  15. import FirebaseFirestore
  16. @available(swift 5.0)
  17. @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *)
  18. public extension DocumentReference {
  19. // MARK: - Set Data
  20. /// Overwrites the document referred to by this `DocumentReference`. If no document exists, it
  21. /// is created. If a document already exists, it is overwritten.
  22. ///
  23. /// - Parameter documentData: A `Dictionary` containing the fields that make up the document to
  24. /// be written.
  25. /// - Returns: A publisher emitting a `Void` value once the document has been successfully
  26. /// written to the server. This publisher will not emit while the client is offline, though
  27. /// local changes will be visible immediately.
  28. func setData(_ documentData: [String: Any]) -> Future<Void, Error> {
  29. Future { promise in
  30. self.setData(documentData) { error in
  31. if let error {
  32. promise(.failure(error))
  33. } else {
  34. promise(.success(()))
  35. }
  36. }
  37. }
  38. }
  39. /// Writes to the document referred to by this `DocumentReference`. If the document does not yet
  40. /// exist, it will be created. If you pass `merge: true`, the provided data will be merged into
  41. /// any existing document.
  42. ///
  43. /// - Parameters:
  44. /// - documentData: A `Dictionary` containing the fields that make up the document to be
  45. /// written.
  46. /// - merge: Whether to merge the provided data into any existing document.
  47. /// - Returns: A publisher emitting a `Void` value once the document has been successfully
  48. /// written to the server. This publisher will not emit while the client is offline, though
  49. /// local changes will be visible immediately.
  50. func setData(_ documentData: [String: Any], merge: Bool) -> Future<Void, Error> {
  51. Future { promise in
  52. self.setData(documentData, merge: merge) { error in
  53. if let error {
  54. promise(.failure(error))
  55. } else {
  56. promise(.success(()))
  57. }
  58. }
  59. }
  60. }
  61. /// Writes to the document referred to by this `DocumentReference` and only replace the fields
  62. /// specified under `mergeFields`. Any field that is not specified in `mergeFields` is ignored
  63. /// and remains untouched. If the document doesn't yet exist, this method creates it and then
  64. /// sets the data.
  65. ///
  66. /// - Parameters:
  67. /// - documentData: A `Dictionary` containing the fields that make up the document to be
  68. /// written.
  69. /// - mergeFields: An `Array` that contains a list of `String` or `FieldPath` elements
  70. /// specifying which fields to merge. Fields can contain dots to reference nested fields
  71. /// within the document.
  72. /// - Returns: A publisher emitting a `Void` value once the document has been successfully
  73. /// written to the server. This publisher will not emit while the client is offline, though
  74. /// local changes will be visible immediately.
  75. func setData(_ documentData: [String: Any], mergeFields: [Any]) -> Future<Void, Error> {
  76. Future { promise in
  77. self.setData(documentData, mergeFields: mergeFields) { error in
  78. if let error {
  79. promise(.failure(error))
  80. } else {
  81. promise(.success(()))
  82. }
  83. }
  84. }
  85. }
  86. /// Encodes an instance of `Encodable` and overwrites the encoded data to the document
  87. /// referred by this `DocumentReference`. If no document exists, it is created. If a
  88. /// document already exists, it is overwritten.
  89. ///
  90. /// - Parameters:
  91. /// - value: An instance of `Encodable` to be encoded to a document.
  92. /// - encoder: An encoder instance to use to run the encoding.
  93. /// - Returns: A publisher emitting a `Void` value once the document has been successfully
  94. /// written to the server. This publisher will not emit while the client is offline, though
  95. /// local changes will be visible immediately.
  96. func setData<T: Encodable>(from value: T,
  97. encoder: Firestore.Encoder = Firestore.Encoder()) -> Future<
  98. Void,
  99. Error
  100. > {
  101. Future { promise in
  102. do {
  103. try self.setData(from: value, encoder: encoder) { error in
  104. if let error {
  105. promise(.failure(error))
  106. } else {
  107. promise(.success(()))
  108. }
  109. }
  110. } catch {
  111. promise(.failure(error))
  112. }
  113. }
  114. }
  115. /// Encodes an instance of `Encodable` and overwrites the encoded data to the document
  116. /// referred
  117. /// by this `DocumentReference`. If no document exists, it is created. If a document already
  118. /// exists, it is overwritten. If you pass `merge: true`, the provided Encodable will be
  119. /// merged
  120. /// into any existing document.
  121. ///
  122. /// - Parameters:
  123. /// - value: An instance of `Encodable` to be encoded to a document.
  124. /// - merge: Whether to merge the provided data into any existing document.
  125. /// - encoder: An encoder instance to use to run the encoding.
  126. /// - Returns: A publisher emitting a `Void` value once the document has been successfully
  127. /// written to the server. This publisher will not emit while the client is offline, though
  128. /// local changes will be visible immediately.
  129. func setData<T: Encodable>(from value: T, merge: Bool,
  130. encoder: Firestore.Encoder = Firestore.Encoder()) -> Future<
  131. Void,
  132. Error
  133. > {
  134. Future { promise in
  135. do {
  136. try self.setData(from: value, merge: merge, encoder: encoder) { error in
  137. if let error {
  138. promise(.failure(error))
  139. } else {
  140. promise(.success(()))
  141. }
  142. }
  143. } catch {
  144. promise(.failure(error))
  145. }
  146. }
  147. }
  148. /// Encodes an instance of `Encodable` and writes the encoded data to the document referred by
  149. /// this `DocumentReference` by only replacing the fields specified under mergeFields. Any
  150. /// field
  151. /// that is not specified in mergeFields is ignored and remains untouched. If the document
  152. /// doesn’t yet exist, this method creates it and then sets the data.
  153. ///
  154. /// - Parameters:
  155. /// - value: An instance of `Encodable` to be encoded to a document.
  156. /// - mergeFields: An `Array` that contains a list of `String` or `FieldPath` elements
  157. /// specifying which fields to merge. Fields can contain dots to reference nested fields
  158. /// within the document.
  159. /// - encoder: An encoder instance to use to run the encoding.
  160. /// - Returns: A publisher emitting a `Void` value once the document has been successfully
  161. /// written to the server. This publisher will not emit while the client is offline, though
  162. /// local changes will be visible immediately.
  163. func setData<T: Encodable>(from value: T, mergeFields: [Any],
  164. encoder: Firestore.Encoder = Firestore.Encoder()) -> Future<
  165. Void,
  166. Error
  167. > {
  168. Future { promise in
  169. do {
  170. try self.setData(from: value, mergeFields: mergeFields, encoder: encoder) { error in
  171. if let error {
  172. promise(.failure(error))
  173. } else {
  174. promise(.success(()))
  175. }
  176. }
  177. } catch {
  178. promise(.failure(error))
  179. }
  180. }
  181. }
  182. // MARK: - Update Data
  183. /// Updates fields in the document referred to by this `DocumentReference`. If the document does
  184. /// not exist, the update fails and the specified completion block receives an error.
  185. ///
  186. /// - Parameter documentData: A `Dictionary` containing the fields (expressed as a `String` or
  187. /// `FieldPath`) and values with which to update the document.
  188. /// - Returns: A publisher emitting a `Void` when the update is complete. This block will only
  189. /// execute when the client is online and the commit has completed against the server. This
  190. /// publisher will not emit while the device is offline, though local changes will be visible
  191. /// immediately.
  192. func updateData(_ documentData: [String: Any]) -> Future<Void, Error> {
  193. Future { promise in
  194. self.updateData(documentData) { error in
  195. if let error {
  196. promise(.failure(error))
  197. } else {
  198. promise(.success(()))
  199. }
  200. }
  201. }
  202. }
  203. // MARK: - Delete
  204. /// Deletes the document referred to by this `DocumentReference`.
  205. ///
  206. /// - Returns: A publisher emitting a `Void` when the document has been deleted from the server.
  207. /// This publisher will not emit while the device is offline, though local changes will be
  208. /// visible immediately.
  209. func delete() -> Future<Void, Error> {
  210. Future { promise in
  211. self.delete { error in
  212. if let error {
  213. promise(.failure(error))
  214. } else {
  215. promise(.success(()))
  216. }
  217. }
  218. }
  219. }
  220. // MARK: - Get Document
  221. /// Reads the document referenced by this `DocumentReference`.
  222. ///
  223. /// - Parameter source: Indicates whether the results should be fetched from the cache only
  224. /// (`Source.cache`), the server only (`Source.server`), or to attempt the server and fall back
  225. /// to the cache (`Source.default`).
  226. /// - Returns: A publisher emitting a `DocumentSnapshot` instance.
  227. func getDocument(source: FirestoreSource = .default)
  228. -> Future<DocumentSnapshot, Error> {
  229. Future { promise in
  230. self.getDocument(source: source) { snapshot, error in
  231. if let error {
  232. promise(.failure(error))
  233. } else if let snapshot {
  234. promise(.success(snapshot))
  235. }
  236. }
  237. }
  238. }
  239. // MARK: - Snapshot Publisher
  240. /// Registers a publisher that publishes document snapshot changes.
  241. ///
  242. /// - Parameter includeMetadataChanges: Whether metadata-only changes (i.e. only
  243. /// `DocumentSnapshot.metadata` changed) should trigger snapshot events.
  244. /// - Returns: A publisher emitting `DocumentSnapshot` instances.
  245. func snapshotPublisher(includeMetadataChanges: Bool = false)
  246. -> AnyPublisher<DocumentSnapshot, Error> {
  247. let subject = PassthroughSubject<DocumentSnapshot, Error>()
  248. let listenerHandle =
  249. addSnapshotListener(includeMetadataChanges: includeMetadataChanges) { snapshot, error in
  250. if let error {
  251. subject.send(completion: .failure(error))
  252. } else if let snapshot {
  253. subject.send(snapshot)
  254. }
  255. }
  256. return subject
  257. .handleEvents(receiveCancel: listenerHandle.remove)
  258. .eraseToAnyPublisher()
  259. }
  260. }