DocumentReference+Combine.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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 referred
  92. /// by this `DocumentReference`. If no document exists, it is created. If a document already
  93. /// 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 referred
  121. /// by this `DocumentReference`. If no document exists, it is created. If a document already
  122. /// exists, it is overwritten. If you pass `merge: true`, the provided Encodable will be merged
  123. /// into any existing document.
  124. ///
  125. /// - Parameters:
  126. /// - value: An instance of `Encodable` to be encoded to a document.
  127. /// - merge: Whether to merge the provided data into any existing document.
  128. /// - encoder: An encoder instance to use to run the encoding.
  129. /// - Returns: A publisher emitting a `Void` value once the document has been successfully
  130. /// written to the server. This publisher will not emit while the client is offline, though
  131. /// local changes will be visible immediately.
  132. func setData<T: Encodable>(from value: T, merge: Bool,
  133. encoder: Firestore.Encoder = Firestore.Encoder()) -> Future<
  134. Void,
  135. Error
  136. > {
  137. Future { promise in
  138. do {
  139. try self.setData(from: value, merge: merge, encoder: encoder) { error in
  140. if let error = error {
  141. promise(.failure(error))
  142. } else {
  143. promise(.success(()))
  144. }
  145. }
  146. } catch {
  147. promise(.failure(error))
  148. }
  149. }
  150. }
  151. /// Encodes an instance of `Encodable` and writes the encoded data to the document referred by
  152. /// this `DocumentReference` by only replacing the fields specified under mergeFields. Any field
  153. /// that is not specified in mergeFields is ignored and remains untouched. If the document
  154. /// doesn’t yet exist, this method creates it and then sets the data.
  155. ///
  156. /// - Parameters:
  157. /// - value: An instance of `Encodable` to be encoded to a document.
  158. /// - mergeFields: An `Array` that contains a list of `String` or `FieldPath` elements
  159. /// specifying which fields to merge. Fields can contain dots to reference nested fields
  160. /// within the document.
  161. /// - encoder: An encoder instance to use to run the encoding.
  162. /// - Returns: A publisher emitting a `Void` value once the document has been successfully
  163. /// written to the server. This publisher will not emit while the client is offline, though
  164. /// local changes will be visible immediately.
  165. func setData<T: Encodable>(from value: T, mergeFields: [Any],
  166. encoder: Firestore.Encoder = Firestore.Encoder()) -> Future<
  167. Void,
  168. Error
  169. > {
  170. Future { promise in
  171. do {
  172. try self.setData(from: value, mergeFields: mergeFields, encoder: encoder) { error in
  173. if let error = error {
  174. promise(.failure(error))
  175. } else {
  176. promise(.success(()))
  177. }
  178. }
  179. } catch {
  180. promise(.failure(error))
  181. }
  182. }
  183. }
  184. #endif
  185. // MARK: - Update Data
  186. /// Updates fields in the document referred to by this `DocumentReference`. If the document does
  187. /// not exist, the update fails and the specified completion block receives an error.
  188. ///
  189. /// - Parameter documentData: A `Dictionary` containing the fields (expressed as a `String` or
  190. /// `FieldPath`) and values with which to update the document.
  191. /// - Returns: A publisher emitting a `Void` when the update is complete. This block will only
  192. /// execute when the client is online and the commit has completed against the server. This
  193. /// publisher will not emit while the device is offline, though local changes will be visible
  194. /// immediately.
  195. func updateData(_ documentData: [String: Any]) -> Future<Void, Error> {
  196. Future { promise in
  197. self.updateData(documentData) { error in
  198. if let error = error {
  199. promise(.failure(error))
  200. } else {
  201. promise(.success(()))
  202. }
  203. }
  204. }
  205. }
  206. // MARK: - Delete
  207. /// Deletes the document referred to by this `DocumentReference`.
  208. ///
  209. /// - Returns: A publisher emitting a `Void` when the document has been deleted from the server.
  210. /// This publisher will not emit while the device is offline, though local changes will be
  211. /// visible immediately.
  212. func delete() -> Future<Void, Error> {
  213. Future { promise in
  214. self.delete { error in
  215. if let error = error {
  216. promise(.failure(error))
  217. } else {
  218. promise(.success(()))
  219. }
  220. }
  221. }
  222. }
  223. // MARK: - Get Document
  224. /// Reads the document referenced by this `DocumentReference`.
  225. ///
  226. /// - Parameter source: Indicates whether the results should be fetched from the cache only
  227. /// (`Source.cache`), the server only (`Source.server`), or to attempt the server and fall back
  228. /// to the cache (`Source.default`).
  229. /// - Returns: A publisher emitting a `DocumentSnapshot` instance.
  230. func getDocument(source: FirestoreSource = .default)
  231. -> Future<DocumentSnapshot, Error> {
  232. Future { promise in
  233. self.getDocument(source: source) { snapshot, error in
  234. if let error = error {
  235. promise(.failure(error))
  236. } else if let snapshot = snapshot {
  237. promise(.success(snapshot))
  238. }
  239. }
  240. }
  241. }
  242. // MARK: - Snapshot Publisher
  243. /// Registers a publisher that publishes document snapshot changes.
  244. ///
  245. /// - Parameter includeMetadataChanges: Whether metadata-only changes (i.e. only
  246. /// `DocumentSnapshot.metadata` changed) should trigger snapshot events.
  247. /// - Returns: A publisher emitting `DocumentSnapshot` instances.
  248. func snapshotPublisher(includeMetadataChanges: Bool = false)
  249. -> AnyPublisher<DocumentSnapshot, Error> {
  250. let subject = PassthroughSubject<DocumentSnapshot, Error>()
  251. let listenerHandle =
  252. addSnapshotListener(includeMetadataChanges: includeMetadataChanges) { snapshot, error in
  253. if let error = error {
  254. subject.send(completion: .failure(error))
  255. } else if let snapshot = snapshot {
  256. subject.send(snapshot)
  257. }
  258. }
  259. return subject
  260. .handleEvents(receiveCancel: listenerHandle.remove)
  261. .eraseToAnyPublisher()
  262. }
  263. }
  264. #endif