DocumentReference+Combine.swift 12 KB

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