Memetakan data Cloud Firestore dengan Swift Codable

Dengan Codable API Swift yang diperkenalkan di Swift 4, kita dapat memanfaatkan keandalan memanfaatkan keandalan compiler untuk mempermudah pemetaan data dari format serial ke jenis Swift.

Anda mungkin telah menggunakan Codable untuk memetakan data dari API web ke model data aplikasi Anda (dan sebaliknya). Namun, cara ini jauh lebih fleksibel.

Dalam panduan ini, kita akan melihat cara penggunaan Codable untuk memetakan data dari jenis Cloud Firestore ke Swift, dan sebaliknya.

Saat mengambil dokumen dari Cloud Firestore, aplikasi Anda akan menerima kamus key-value pair (atau array kamus, jika Anda menggunakan salah satu operasi yang menampilkan beberapa dokumen).

Kini, Anda dapat terus menggunakan kamus di Swift secara langsung, dan kamus tersebut menawarkan beberapa fleksibilitas tinggi yang mungkin sesuai dengan kasus penggunaan Anda. Namun, pendekatan ini tidak aman dan berisiko menciptakan bug yang sulit dilacak karena salah mengeja nama atribut, atau karena lupa memetakan atribut baru yang ditambahkan tim Anda saat mereka mengirimkan fitur baru yang menarik minggu lalu.

Sebelumnya, banyak developer yang mengatasi kekurangan ini dengan menerapkan lapisan pemetaan sederhana yang memungkinkan mereka memetakan kamus ke jenis Swift. Namun, sekali lagi, sebagian besar penerapannya didasarkan pada cara menentukan pemetaan antara dokumen Cloud Firestore secara manual dan jenis model data aplikasi yang sesuai.

Dengan dukungan Cloud Firestore untuk Codable API Swift, hal ini menjadi jauh lebih mudah:

  • Anda tidak perlu lagi menerapkan kode pemetaan secara manual.
  • Sangat mudah untuk menentukan cara memetakan atribut dengan nama yang berbeda.
  • Swift Studio memiliki dukungan bawaan untuk banyak jenis Swift.
  • Selain itu, mudah untuk menambahkan dukungan bagi pemetaan jenis kustom.
  • Yang paling penting: untuk model data sederhana, Anda tidak perlu menulis kode pemetaan sama sekali.

Memetakan data

Cloud Firestore menyimpan data dalam dokumen yang memetakan kunci ke nilai. Untuk mengambil data dari masing-masing dokumen, kita dapat memanggil DocumentSnapshot.data() yang menampilkan kamus yang memetakan nama kolom ke Any: func data() -> [String : Any]?,

Ini berarti kita dapat menggunakan sintaksis subskrip Swift untuk mengakses setiap kolom.

import FirebaseFirestore

#warning("DO NOT MAP YOUR DOCUMENTS MANUALLY. USE CODABLE INSTEAD.")
func fetchBook(documentId: String) {
  let docRef = db.collection("books").document(documentId)

  docRef.getDocument { document, error in
    if let error = error as NSError? {
      self.errorMessage = "Error getting document: \(error.localizedDescription)"
    }
    else {
      if let document = document {
        let id = document.documentID
        let data = document.data()
        let title = data?["title"] as? String ?? ""
        let numberOfPages = data?["numberOfPages"] as? Int ?? 0
        let author = data?["author"] as? String ?? ""
        self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author)
      }
    }
  }
}

Meskipun tampaknya sederhana dan mudah diterapkan, kode ini rapuh, sulit dikelola, dan rentan terhadap error.

Seperti yang Anda lihat, kita membuat asumsi tentang jenis data dari kolom dokumen. Jawaban ini bisa jadi tepat atau tidak.

Ingat, karena tidak ada skema, Anda dapat dengan mudah menambahkan dokumen baru ke koleksi dan memilih jenis kolom yang berbeda. Anda mungkin tidak sengaja memilih string untuk kolom numberOfPages, yang akan menyebabkan masalah pemetaan yang sulit ditemukan. Selain itu, Anda harus memperbarui kode pemetaan setiap kali kolom baru ditambahkan, yang bukan merupakan hal yang praktis.

Jangan lupa bahwa kita tidak memanfaatkan sistem jenis Swift yang kuat, yang mengetahui jenis yang tepat untuk setiap properti Book.

Apa itu Codable?

Menurut dokumentasi Apple, Codable adalah "jenis yang dapat mengonversi dirinya sendiri ke dalam dan ke luar representasi eksternal." Kenyataannya, Codable adalah alias jenis untuk protokol Encodable dan Decodable. Dengan menyesuaikan jenis Swift dengan protokol ini, compiler akan menyintesis kode yang diperlukan untuk mengenkode/mendekode instance jenis ini dari format serial, seperti JSON.

Jenis penyimpanan data buku yang sederhana mungkin terlihat seperti ini:

struct Book: Codable {
  var title: String
  var numberOfPages: Int
  var author: String
}

Seperti yang dapat Anda lihat, menyesuaikan jenisnya dengan Codable bersifat minimal invasif. Kita hanya harus menambahkan kesesuaian ke protokol; tidak ada perubahan lain yang diperlukan.

Dengan penerapan ini, kini kita dapat mengenkode buku ke objek JSON dengan mudah:

do {
  let book = Book(title: "The Hitchhiker's Guide to the Galaxy",
                  numberOfPages: 816,
                  author: "Douglas Adams")
  let encoder = JSONEncoder()
  let data = try encoder.encode(book)
} 
catch {
  print("Error when trying to encode book: \(error)")
}

Mendecode objek JSON ke instance Book berfungsi sebagai berikut:

let decoder = JSONDecoder()
let data = /* fetch data from the network */
let decodedBook = try decoder.decode(Book.self, from: data)

Melakukan pemetaan ke dan dari jenis sederhana di dokumen Cloud Firestore
menggunakan Codable

Cloud Firestore mendukung berbagai jenis data, mulai dari string sederhana hingga peta bertingkat. Sebagian besar properti ini berhubungan langsung dengan jenis bawaan Swift. Mari kita lihat pemetaan beberapa jenis data sederhana terlebih dahulu sebelum mempelajari data yang lebih kompleks.

Untuk memetakan dokumen Cloud Firestore ke jenis Swift, ikuti langkah-langkah berikut:

  1. Pastikan Anda telah menambahkan framework FirebaseFirestore ke project Anda. Anda dapat menggunakan Swift Package Manager atau CocoaPods untuk melakukannya.
  2. Impor FirebaseFirestore ke file Swift Anda.
  3. Ubah jenis Anda menjadi Codable.
  4. (Opsional, jika Anda ingin menggunakan jenis di tampilan List) Tambahkan properti id ke jenis Anda, dan gunakan @DocumentID untuk memberi tahu Cloud Firestore untuk memetakan ini ke ID dokumen. Kita akan membahasnya lebih mendetail di bawah.
  5. Gunakan documentReference.data(as: ) untuk memetakan referensi dokumen ke jenis Swift.
  6. Gunakan documentReference.setData(from: ) untuk memetakan data dari jenis Swift ke dokumen Cloud Firestore.
  7. (Opsional, tetapi sangat direkomendasikan) Terapkan penanganan error yang tepat.

Mari kita perbarui jenis Book untuk menyesuaikan:

struct Book: Codable {
  @DocumentID var id: String?
  var title: String
  var numberOfPages: Int
  var author: String
}

Karena jenis ini sifatnya sudah codable, kita hanya perlu menambahkan properti id dan menganotasinya dengan wrapper properti @DocumentID.

Dengan mengambil cuplikan kode sebelumnya untuk mengambil dan memetakan dokumen, kita dapat mengganti semua kode pemetaan manual dengan satu baris:

func fetchBook(documentId: String) {
  let docRef = db.collection("books").document(documentId)

  docRef.getDocument { document, error in
    if let error = error as NSError? {
      self.errorMessage = "Error getting document: \(error.localizedDescription)"
    }
    else {
      if let document = document {
        do {
          self.book = try document.data(as: Book.self)
        }
        catch {
          print(error)
        }
      }
    }
  }
}

Anda dapat menulisnya secara lebih ringkas dengan menentukan jenis dokumen saat memanggil getDocument(as:). Tindakan ini akan melakukan pemetaan untuk Anda, dan menampilkan jenis Result yang berisi dokumen yang dipetakan, atau error jika dekode gagal:

private func fetchBook(documentId: String) {
  let docRef = db.collection("books").document(documentId)
  
  docRef.getDocument(as: Book.self) { result in
    switch result {
    case .success(let book):
      // A Book value was successfully initialized from the DocumentSnapshot.
      self.book = book
      self.errorMessage = nil
    case .failure(let error):
      // A Book value could not be initialized from the DocumentSnapshot.
      self.errorMessage = "Error decoding document: \(error.localizedDescription)"
    }
  }
}

Memperbarui dokumen yang ada semudah memanggil documentReference.setData(from: ). Menyertakan beberapa penanganan error dasar, berikut adalah kode untuk menyimpan instance Book:

func updateBook(book: Book) {
  if let id = book.id {
    let docRef = db.collection("books").document(id)
    do {
      try docRef.setData(from: book)
    }
    catch {
      print(error)
    }
  }
}

Saat menambahkan dokumen baru, Cloud Firestore akan secara otomatis menetapkan ID dokumen baru ke dokumen. Fitur ini bahkan berfungsi saat aplikasi offline.

func addBook(book: Book) {
  let collectionRef = db.collection("books")
  do {
    let newDocReference = try collectionRef.addDocument(from: self.book)
    print("Book stored with new document reference: \(newDocReference)")
  }
  catch {
    print(error)
  }
}

Selain memetakan jenis data sederhana, Cloud Firestore mendukung sejumlah jenis data lainnya, beberapa di antaranya adalah jenis terstruktur yang dapat Anda gunakan untuk membuat objek bertingkat di dalam dokumen.

Jenis kustom bertingkat

Sebagian besar atribut yang ingin dipetakan dalam dokumen kami adalah nilai sederhana, seperti judul buku atau nama penulis. Namun, bagaimana saat kita perlu menyimpan objek yang lebih kompleks? Misalnya, kita mungkin ingin menyimpan URL ke sampul buku dalam resolusi yang berbeda.

Cara termudah untuk melakukannya di Cloud Firestore adalah dengan menggunakan peta:

Menyimpan jenis kustom bertingkat di dokumen Firestore

Ketika menulis struct Swift yang sesuai, kita dapat memanfaatkan fakta bahwa Cloud Firestore mendukung URL — saat menyimpan kolom yang berisi URL, kolom tersebut akan dikonversi menjadi string dan sebaliknya:

struct CoverImages: Codable {
  var small: URL
  var medium: URL
  var large: URL
}

struct BookWithCoverImages: Codable {
  @DocumentID var id: String?
  var title: String
  var numberOfPages: Int
  var author: String
  var cover: CoverImages?
}

Perhatikan cara kami dalam menentukan struct, CoverImages, untuk peta cakupan dalam dokumen Cloud Firestore. Dengan menandai properti cakupan di BookWithCoverImages sebagai opsional, kami dapat menangani fakta bahwa beberapa dokumen mungkin tidak berisi atribut cakupan.

Cuplikan kode tidak tersedia saat pengambilan atau pembaruan data karena kode tidak perlu disesuaikan agar dapat membaca atau menulis dari/ke Cloud Firestore: semuanya dapat dilakukan dengan kode yang telah kita tulis di bagian awal.

Array

Terkadang, kita ingin menyimpan kumpulan nilai dalam dokumen. Genre buku adalah contoh yang bagus. Buku seperti The Hitchhiker's Guide to the Galaxy mungkin termasuk dalam beberapa kategori genre, yaitu "Sci-Fi" dan "Comedy":

Menyimpan array dalam dokumen Firestore

Di Cloud Firestore, kita dapat membuat model ini menggunakan array nilai. Ini didukung untuk semua jenis codable (seperti String, Int, dll.). Berikut ini cara menambahkan array genre ke model Book:

public struct BookWithGenre: Codable {
  @DocumentID var id: String?
  var title: String
  var numberOfPages: Int
  var author: String
  var genres: [String]
}

Karena ini berfungsi untuk jenis codable apa pun, kita juga dapat menggunakan jenis kustom. Bayangkan kita ingin menyimpan daftar tag untuk setiap buku. Bersama dengan nama tag, kita juga ingin menyimpan warna tag, seperti ini:

Menyimpan array jenis kustom dalam dokumen Firestore

Untuk menyimpan tag dengan cara ini, yang perlu kita lakukan hanyalah menerapkan struct Tag untuk mewakili tag dan membuatnya bersifat codable:

struct Tag: Codable, Hashable {
  var title: String
  var color: String
}

Begitulah caranya untuk menyimpan array Tags dalam dokumen Book.

struct BookWithTags: Codable {
  @DocumentID var id: String?
  var title: String
  var numberOfPages: Int
  var author: String
  var tags: [Tag]
}

Penjelasan singkat tentang pemetaan ID dokumen

Sebelum lanjut untuk memetakan jenis lainnya, mari kita bahas pemetaan ID dokumen.

Kita menggunakan wrapper properti @DocumentID di beberapa contoh sebelumnya untuk memetakan ID dokumen dari dokumen Cloud Firestore ke properti id jenis Swift. Hal ini penting karena sejumlah alasan:

  • Hal ini membantu kami mengetahui dokumen mana yang akan diperbarui jika pengguna membuat perubahan lokal.
  • List SwiftUI mengharuskan Identifiable sebagai elemennya agar elemen tidak berpindah saat elemen disisipkan.

Perlu diperhatikan bahwa atribut yang ditandai sebagai @DocumentID tidak akan dienkode oleh encoder Cloud Firestore saat menulis dokumen kembali. Hal ini karena ID dokumen bukan merupakan atribut dokumen itu sendiri. Oleh karena itu, menulisnya ke dokumen akan menjadi kesalahan.

Saat menangani jenis bertingkat (seperti array tag pada Book dalam contoh sebelumnya di panduan ini), Anda tidak perlu menambahkan properti @DocumentID: properti bertingkat adalah bagian dari dokumen Cloud Firestore, dan bukan merupakan dokumen terpisah. Oleh karena itu, ID tersebut tidak memerlukan ID dokumen.

Tanggal dan waktu

Cloud Firestore memiliki jenis data bawaan untuk menangani tanggal dan waktu, dan Anda dapat menggunakannya dengan mudah berkat dukungan Cloud Firestore untuk Codable.

Mari kita lihat dokumen ini yang merupakan induk dari semua bahasa pemrograman, yaitu Ada, yang ditemukan pada tahun 1843:

Menyimpan tanggal di dokumen Firestore

Jenis Swift untuk memetakan dokumen ini mungkin terlihat seperti ini:

struct ProgrammingLanguage: Codable {
  @DocumentID var id: String?
  var name: String
  var year: Date
}

Kita tidak dapat meninggalkan bagian tentang tanggal dan waktu tanpa mendiskusikan @ServerTimestamp. Wrapper properti ini sangat andal dalam hal menangani stempel waktu di aplikasi Anda.

Dalam setiap sistem yang terdistribusi, jam di sistem individual kemungkinan tidak sepenuhnya disinkronkan setiap saat. Anda mungkin menganggap ini bukan masalah besar, tetapi bayangkan implikasi dari jam yang sedikit tidak sinkron untuk sistem perdagangan saham: bahkan perbedaan dalam milidetik saja bisa membuat perbedaan jutaan dolar pada saat perdagangan dijalankan.

Cloud Firestore menangani atribut yang ditandai dengan @ServerTimestamp sebagai berikut: jika atribut berupa nil saat Anda menyimpannya (misalnya, addDocument()), Cloud Firestore akan mengisi kolom dengan stempel waktu server saat ini pada saat menulisnya ke dalam database. Jika kolom ini bukan nil saat Anda memanggil addDocument() atau updateData(), nilai atribut tidak akan terpengaruh Cloud Firestore. Dengan cara ini, sangat mudah untuk mengimplementasikan kolom seperti createdAt dan lastUpdatedAt.

Titik geografis

Geolokasi ada di mana-mana di aplikasi kami. Banyak fitur menarik yang dapat diwujudkan dengan menyimpannya. Misalnya, mungkin ada baiknya menyimpan lokasi untuk sebuah tugas sehingga aplikasi dapat mengingatkan Anda tentang tugas saat Anda mencapai tujuan.

Cloud Firestore memiliki jenis data bawaan, GeoPoint, yang dapat menyimpan titik bujur dan lintang lokasi mana pun. Untuk memetakan lokasi dari/ke dokumen Cloud Firestore, kita dapat menggunakan jenis GeoPoint:

struct Office: Codable {
  @DocumentID var id: String?
  var name: String
  var location: GeoPoint
}

Jenis yang terkait di Swift adalah CLLocationCoordinate2D, dan kita dapat memetakan antara dua jenis tersebut dengan operasi berikut:

CLLocationCoordinate2D(latitude: office.location.latitude,
                      longitude: office.location.longitude)

Untuk mempelajari lebih lanjut cara membuat kueri dokumen berdasarkan lokasi fisik, lihat panduan solusi ini.

Enum

Enum mungkin adalah salah satu fitur bahasa yang paling diremehkan di Swift; padahal ada banyak hal menarik yang belum terekspos. Kasus penggunaan umum untuk enum adalah memodelkan status diskret sesuatu. Misalnya, kita mungkin menulis aplikasi untuk mengelola artikel. Untuk melacak status artikel, kita mungkin ingin menggunakan enum Status:

enum Status: String, Codable {
  case draft
  case inReview
  case approved
  case published
}

Cloud Firestore tidak mendukung enum secara native (yaitu tidak dapat menerapkan serangkaian nilai), tetapi kita masih dapat memanfaatkan enum yang dapat diketik, dan memilih jenis codeable. Dalam contoh ini, kami memilih String, yang berarti semua nilai enum akan dipetakan ke/dari string saat disimpan di dokumen Cloud Firestore.

Dan, karena Swift mendukung nilai mentah kustom, kita bahkan dapat menyesuaikan nilai mana yang merujuk pada kasus enum tertentu. Jadi, misalnya, jika kita memutuskan untuk menyimpan kasus Status.inReview sebagai "sedang ditinjau", kita hanya dapat memperbarui enum di atas sebagai berikut:

enum Status: String, Codable {
  case draft
  case inReview = "in review"
  case approved
  case published
}

Menyesuaikan pemetaan

Terkadang, nama atribut dokumen Cloud Firestore yang ingin kami petakan tidak cocok dengan nama properti di model data kami di Swift. Misalnya, salah satu rekan kerja kami mungkin adalah developer Python, dan memutuskan untuk memilih snake_case untuk semua nama atributnya.

Jangan khawatir: Codable dapat membantu kami.

Untuk kasus seperti ini, kita dapat menggunakan CodingKeys. Ini adalah enum yang dapat kita tambahkan ke codable struct untuk menentukan cara pemetaan atribut tertentu.

Pertimbangkan dokumen ini:

Dokumen Firestore dengan nama atribut snake_cased

Untuk memetakan dokumen ini ke struct yang memiliki properti nama jenis String, kita harus menambahkan enum CodingKeys ke struct ProgrammingLanguage, dan menentukan nama atribut tersebut dalam dokumen:

struct ProgrammingLanguage: Codable {
  @DocumentID var id: String?
  var name: String
  var year: Date
  
  enum CodingKeys: String, CodingKey {
    case id
    case name = "language_name"
    case year
  }
}

Secara default, Codable API akan menggunakan nama properti dari jenis Swift kami untuk menentukan nama atribut pada dokumen Cloud Firestore yang ingin kami petakan. Jadi, selama nama atribut cocok, Anda tidak perlu menambahkan CodingKeys ke jenis codable. Namun, setelah menggunakan CodingKeys untuk jenis tertentu, kita perlu menambahkan semua nama properti yang ingin kita petakan.

Dalam cuplikan kode di atas, kita telah menentukan properti id yang mungkin ingin kita gunakan sebagai ID dalam tampilan List SwiftUI. Jika kita tidak menentukannya di CodingKeys, ID tidak akan dipetakan saat pengambilan data, sehingga menjadi nil. Hal ini akan menyebabkan tampilan List diisi dengan dokumen pertama.

Setiap properti yang tidak tercantum sebagai kasus pada enum CodingKeys masing-masing akan diabaikan selama proses pemetaan. Hal ini sebenarnya dapat dibuat praktis jika kita ingin secara khusus mengecualikan beberapa properti agar tidak dipetakan.

Jadi, misalnya, jika kita ingin mengecualikan properti reasonWhyILoveThis agar tidak dipetakan, yang perlu kita lakukan adalah menghapusnya dari enum CodingKeys:

struct ProgrammingLanguage: Identifiable, Codable {
  @DocumentID var id: String?
  var name: String
  var year: Date
  var reasonWhyILoveThis: String = ""
  
  enum CodingKeys: String, CodingKey {
    case id
    case name = "language_name"
    case year
  }
}

Terkadang, kita mungkin ingin menulis kembali atribut kosong ke dokumen Cloud Firestore. Swift memiliki gagasan opsional untuk menunjukkan tidak adanya nilai, dan Cloud Firestore juga mendukung nilai null. Namun, perilaku default untuk encoding opsional yang memiliki nilai nil adalah menghilangkannya saja. @ExplicitNull memberi kita kontrol atas cara penanganan opsional Swift saat mengenkode Swift tersebut: dengan menandai properti opsional sebagai @ExplicitNull, kita dapat memberi tahu Cloud Firestore untuk menulis properti ini ke dokumen dengan nilai null jika berisi nilai nil.

Menggunakan encoder dan decoder kustom untuk memetakan warna

Sebagai topik terakhir dalam cakupan data pemetaan dengan Codable, mari kita perkenalkan encoder dan dekoder kustom. Bagian ini tidak membahas jenis data Cloud Firestore native, tetapi encoder dan decoder kustom sangat berguna di aplikasi Cloud Firestore Anda.

"Bagaimana cara memetakan warna" adalah salah satu pertanyaan developer yang paling umum, tidak hanya untuk Cloud Firestore, tetapi juga untuk pemetaan antara Swift dan JSON. Ada banyak solusi di luar sana, tetapi sebagian besar berfokus pada JSON, dan hampir semuanya memetakan warna sebagai kamus bertingkat yang terdiri dari komponen RGB.

Sepertinya akan ada solusi yang lebih baik dan sederhana. Mengapa kita tidak menggunakan warna web (atau lebih khusus lagi, notasi warna hex CSS) — itu mudah digunakan (pada dasarnya hanya string), dan bahkan mendukung transparansi.

Agar dapat memetakan Swift Color ke nilai hex-nya, kita perlu membuat ekstensi Swift yang menambahkan Codable ke Color.

extension Color {

 init(hex: String) {
    let rgba = hex.toRGBA()

    self.init(.sRGB,
              red: Double(rgba.r),
              green: Double(rgba.g),
              blue: Double(rgba.b),
              opacity: Double(rgba.alpha))
    }

    //... (code for translating between hex and RGBA omitted for brevity)

}

extension Color: Codable {
  
  public init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    let hex = try container.decode(String.self)

    self.init(hex: hex)
  }
  
  public func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    try container.encode(toHex)
  }

}

Dengan menggunakan decoder.singleValueContainer(), kita dapat mendekode String ke Color yang setara, tanpa harus menyusun komponen RGBA secara bertingkat. Selain itu, Anda dapat menggunakan nilai ini di UI web aplikasi, tanpa harus mengonversinya terlebih dahulu.

Dengan melakukan ini, kita dapat memperbarui kode untuk tag pemetaan, sehingga lebih mudah untuk menangani warna tag secara langsung, dan tidak perlu memetakannya secara manual di kode UI aplikasi:

struct Tag: Codable, Hashable {
  var title: String
  var color: Color
}

struct BookWithTags: Codable {
  @DocumentID var id: String?
  var title: String
  var numberOfPages: Int
  var author: String
  var tags: [Tag]
}

Menangani error

Dalam cuplikan kode di atas, kita sengaja meminimalkan penanganan error, tetapi dalam aplikasi produksi, Anda tentu ingin memastikan untuk menangani error dengan baik.

Berikut ini cuplikan kode yang menunjukkan cara menangani situasi error yang mungkin Anda alami:

class MappingSimpleTypesViewModel: ObservableObject {
  @Published var book: Book = .empty
  @Published var errorMessage: String?
  
  private var db = Firestore.firestore()
  
  func fetchAndMap() {
    fetchBook(documentId: "hitchhiker")
  }
  
  func fetchAndMapNonExisting() {
    fetchBook(documentId: "does-not-exist")
  }
  
  func fetchAndTryMappingInvalidData() {
    fetchBook(documentId: "invalid-data")
  }
  
  private func fetchBook(documentId: String) {
    let docRef = db.collection("books").document(documentId)
    
    docRef.getDocument(as: Book.self) { result in
      switch result {
      case .success(let book):
        // A Book value was successfully initialized from the DocumentSnapshot.
        self.book = book
        self.errorMessage = nil
      case .failure(let error):
        // A Book value could not be initialized from the DocumentSnapshot.
        switch error {
        case DecodingError.typeMismatch(_, let context):
          self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
        case DecodingError.valueNotFound(_, let context):
          self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
        case DecodingError.keyNotFound(_, let context):
          self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
        case DecodingError.dataCorrupted(let key):
          self.errorMessage = "\(error.localizedDescription): \(key)"
        default:
          self.errorMessage = "Error decoding document: \(error.localizedDescription)"
        }
      }
    }
  }
}

Menangani error dalam update yang sedang berlangsung

Cuplikan kode sebelumnya menunjukkan cara menangani error saat mengambil satu dokumen. Selain mengambil data satu kali, Cloud Firestore juga mendukung pengiriman update ke aplikasi Anda saat terjadi, dengan menggunakan apa yang disebut pemroses snapshot: kita dapat mendaftarkan pemroses snapshot pada koleksi (atau kueri), dan Cloud Firestore akan memanggil pemroses setiap kali ada pembaruan.

Berikut ini adalah cuplikan kode yang menunjukkan cara mendaftarkan pemroses snapshot, memetakan data menggunakan Codable, dan menangani error yang mungkin terjadi. Contoh ini juga menunjukkan cara menambahkan dokumen baru ke koleksi. Seperti yang akan Anda lihat, Anda tidak perlu memperbarui array lokal yang menyimpan dokumen yang dipetakan, karena ini ditangani oleh kode dalam pemroses snapshot.

class MappingColorsViewModel: ObservableObject {
  @Published var colorEntries = [ColorEntry]()
  @Published var newColor = ColorEntry.empty
  @Published var errorMessage: String?
  
  private var db = Firestore.firestore()
  private var listenerRegistration: ListenerRegistration?
  
  public func unsubscribe() {
    if listenerRegistration != nil {
      listenerRegistration?.remove()
      listenerRegistration = nil
    }
  }
  
  func subscribe() {
    if listenerRegistration == nil {
      listenerRegistration = db.collection("colors")
        .addSnapshotListener { [weak self] (querySnapshot, error) in
          guard let documents = querySnapshot?.documents else {
            self?.errorMessage = "No documents in 'colors' collection"
            return
          }
          
          self?.colorEntries = documents.compactMap { queryDocumentSnapshot in
            let result = Result { try queryDocumentSnapshot.data(as: ColorEntry.self) }
            
            switch result {
            case .success(let colorEntry):
              if let colorEntry = colorEntry {
                // A ColorEntry value was successfully initialized from the DocumentSnapshot.
                self?.errorMessage = nil
                return colorEntry
              }
              else {
                // A nil value was successfully initialized from the DocumentSnapshot,
                // or the DocumentSnapshot was nil.
                self?.errorMessage = "Document doesn't exist."
                return nil
              }
            case .failure(let error):
              // A ColorEntry value could not be initialized from the DocumentSnapshot.
              switch error {
              case DecodingError.typeMismatch(_, let context):
                self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
              case DecodingError.valueNotFound(_, let context):
                self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
              case DecodingError.keyNotFound(_, let context):
                self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
              case DecodingError.dataCorrupted(let key):
                self?.errorMessage = "\(error.localizedDescription): \(key)"
              default:
                self?.errorMessage = "Error decoding document: \(error.localizedDescription)"
              }
              return nil
            }
          }
        }
    }
  }
  
  func addColorEntry() {
    let collectionRef = db.collection("colors")
    do {
      let newDocReference = try collectionRef.addDocument(from: newColor)
      print("ColorEntry stored with new document reference: \(newDocReference)")
    }
    catch {
      print(error)
    }
  }
}

Semua cuplikan kode yang digunakan dalam postingan ini adalah bagian dari aplikasi contoh yang dapat Anda download dari repositori GitHub ini.

Teruskan dan gunakan Codable.

Codable API Swift memberikan cara yang andal dan fleksibel untuk memetakan data dari format serial ke dan dari model data aplikasi Anda. Dalam panduan ini, Anda melihat betapa mudahnya menggunakan aplikasi yang menggunakan Cloud Firestore sebagai datastore-nya.

Dimulai dari contoh dasar dengan jenis data sederhana, kami secara bertahap meningkatkan kompleksitas model data, sambil tetap dapat mengandalkan implementasi Codable dan Firebase untuk melakukan pemetaan bagi kami.

Untuk detail selengkapnya tentang Codable, sebaiknya baca referensi berikut:

Meskipun kami telah berupaya sebaik mungkin untuk mengompilasi panduan yang komprehensif untuk memetakan dokumen Cloud Firestore, panduan ini tidak lengkap dan Anda mungkin perlu menggunakan strategi lain untuk memetakan jenis Anda. Dengan menggunakan tombol Kirim masukan di bawah ini, beri tahu kami strategi apa yang Anda gunakan untuk memetakan jenis data Cloud Firestore lainnya atau mewakili data di Swift.

Tidak ada alasan untuk tidak menggunakan dukungan Codable Cloud Firestore.