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)")
}
Mendekode 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, 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:
- Pastikan Anda telah menambahkan framework
FirebaseFirestore
ke project Anda. Anda dapat menggunakan Swift Package Manager atau CocoaPods untuk melakukannya. - Impor
FirebaseFirestore
ke file Swift Anda. - Ubah jenis Anda menjadi
Codable
. - (Opsional, jika Anda ingin menggunakan jenis di tampilan
List
) Tambahkan propertiid
ke jenis Anda, dan gunakan@DocumentID
untuk memberi tahu Cloud Firestore agar memetakannya ke ID dokumen. Kita akan membahasnya lebih mendetail di bawah. - Gunakan
documentReference.data(as: )
untuk memetakan referensi dokumen ke jenis Swift. - Gunakan
documentReference.setData(from: )
untuk memetakan data dari jenis Swift ke dokumen Cloud Firestore. - (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:
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 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":
Di Cloud Firestore, kita dapat membuat model ini menggunakan array nilai. Langkah 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:
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 mengharuskanIdentifiable
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 berkat dukungan Cloud Firestore untuk Codable, Anda dapat dengan mudah menggunakannya.
Mari kita lihat dokumen ini yang merupakan induk dari semua bahasa pemrograman, yaitu Ada, yang ditemukan pada tahun 1843:
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 menggunakan
addDocument()
), Cloud Firestore akan mengisi kolom dengan stempel waktu server
saat ini pada saat menulisnya ke dalam database. Jika kolom bukan nil
saat Anda memanggil addDocument()
atau updateData()
, Cloud Firestore akan membiarkan
nilai atribut tidak tersentuh. 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:
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:
- John Sundell memiliki artikel yang bagus tentang Dasar-Dasar Codable.
- Jika Anda lebih suka buku, lihat Panduan Sekolah Penerbangan untuk Swift Codable dari Mattt.
- Dan terakhir, Donny Wals memiliki keseluruhan serial tentang Codable.
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.