1. Genel Bakış
Hedefler
Bu kod laboratuvarında, iOS'ta Swift'de Firestore destekli bir restoran tavsiye uygulaması oluşturacaksınız. Şunları nasıl yapacağınızı öğreneceksiniz:
- Bir iOS uygulamasından Firestore'a veri okuma ve yazma
- Firestore verilerindeki değişiklikleri gerçek zamanlı olarak dinleyin
- Firestore verilerinin güvenliğini sağlamak için Firebase Kimlik Doğrulaması ve güvenlik kurallarını kullanın
- Karmaşık Firestore sorguları yazın
Önkoşullar
Bu codelab'i başlatmadan önce yüklediğinizden emin olun:
- Xcode sürüm 13.0 (veya üstü)
- CocoaPods 1.11.0 (veya üstü)
2. Firebase konsol projesi oluşturun
Projeye Firebase ekleyin
- Firebase konsoluna gidin.
- Yeni Proje Oluştur'u seçin ve projenize "Firestore iOS Codelab" adını verin.
3. Örnek Projeyi Alın
Kodu İndir
Örnek projeyi klonlayarak ve proje dizininde pod update
çalıştırarak başlayın:
git clone https://github.com/firebase/friendlyeats-ios cd friendlyeats-ios pod update
FriendlyEats.xcworkspace
Xcode'da açın ve çalıştırın (Cmd+R). Bir GoogleService-Info.plist
dosyası eksik olduğundan, uygulama doğru bir şekilde derlenmeli ve başlatıldığında hemen çökmelidir. Bir sonraki adımda bunu düzelteceğiz.
Firebase'i kurun
Yeni bir Firestore projesi oluşturmak için belgeleri izleyin. Projenizi aldıktan sonra, projenizin GoogleService-Info.plist
dosyasını Firebase konsolundan indirin ve Xcode projesinin kök dizinine sürükleyin. Uygulamanın doğru şekilde yapılandırıldığından ve başlatma sırasında artık çökmediğinden emin olmak için projeyi yeniden çalıştırın. Giriş yaptıktan sonra aşağıdaki örnekteki gibi boş bir ekran görmelisiniz. Giriş yapamıyorsanız, Firebase konsolunda Kimlik Doğrulama altında E-posta/Parola ile oturum açma yöntemini etkinleştirdiğinizden emin olun.
4. Firestore'a Veri Yazın
Bu bölümde, uygulama kullanıcı arayüzünü doldurabilmemiz için Firestore'a bazı veriler yazacağız. Bu, Firebase konsolu aracılığıyla manuel olarak yapılabilir, ancak temel bir Firestore yazımı göstermek için bunu uygulamanın kendisinde yapacağız.
Uygulamamızdaki ana model nesnesi bir restorandır. Firestore verileri belgelere, koleksiyonlara ve alt koleksiyonlara bölünür. Her restoranı, restaurants
adı verilen üst düzey bir koleksiyonda bir belge olarak saklayacağız. Firestore veri modeli hakkında daha fazla bilgi edinmek istiyorsanız, belgeler ve koleksiyonlar hakkında belgelerde okuyun.
Firestore'a veri ekleyebilmemiz için restoran koleksiyonuna bir referans almamız gerekiyor. Aşağıdakileri, RestaurantsTableViewController.didTapPopulateButton(_:)
yöntemindeki iç for döngüsüne ekleyin.
let collection = Firestore.firestore().collection("restaurants")
Artık bir koleksiyon referansımız olduğuna göre bazı veriler yazabiliriz. Eklediğimiz son kod satırından hemen sonra şunu ekleyin:
let collection = Firestore.firestore().collection("restaurants")
// ====== ADD THIS ======
let restaurant = Restaurant(
name: name,
category: category,
city: city,
price: price,
ratingCount: 0,
averageRating: 0
)
collection.addDocument(data: restaurant.dictionary)
Yukarıdaki kod, restoran koleksiyonuna yeni bir belge ekler. Belge verileri, bir Restoran yapısından aldığımız bir sözlükten gelir.
Neredeyse geldik – Firestore'a belge yazabilmemiz için Firestore'un güvenlik kurallarını açmamız ve veritabanımızın hangi bölümlerinin hangi kullanıcılar tarafından yazılabilir olması gerektiğini tanımlamamız gerekiyor. Şimdilik, yalnızca kimliği doğrulanmış kullanıcıların tüm veritabanını okumasına ve yazmasına izin vereceğiz. Bu, bir üretim uygulaması için biraz fazla müsamahakar, ancak uygulama oluşturma sürecinde yeterince rahat bir şey istiyoruz, böylece deney yaparken sürekli kimlik doğrulama sorunlarıyla karşılaşmayacağız. Bu kod laboratuvarının sonunda, güvenlik kurallarınızı nasıl katılaştıracağınız ve istenmeyen okuma ve yazma olasılığını nasıl sınırlayacağınız hakkında konuşacağız.
Firebase konsolunun Kurallar sekmesinde aşağıdaki kuralları ekleyin ve ardından Yayınla 'yı tıklayın.
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { // // WARNING: These rules are insecure! We will replace them with // more secure rules later in the codelab // allow read, write: if request.auth != null; } } }
Güvenlik kurallarını daha sonra ayrıntılı olarak tartışacağız, ancak aceleniz varsa güvenlik kuralları belgelerine bir göz atın.
Uygulamayı çalıştırın ve oturum açın. Ardından sol üstteki " Doldur " düğmesine dokunun; bu, henüz uygulamada görmeseniz de bir grup restoran belgesi oluşturacaktır.
Ardından, Firebase konsolundaki Firestore veri sekmesine gidin. Artık restoranlar koleksiyonunda yeni girişler görmelisiniz:
Tebrikler, bir iOS uygulamasından Firestore'a az önce veri yazdınız! Sonraki bölümde, Firestore'dan nasıl veri alacağınızı ve uygulamada nasıl görüntüleyeceğinizi öğreneceksiniz.
5. Firestore'dan Verileri Görüntüle
Bu bölümde, Firestore'dan nasıl veri alınacağını ve uygulamada nasıl görüntüleneceğini öğreneceksiniz. İki temel adım, bir sorgu oluşturmak ve bir anlık görüntü dinleyicisi eklemektir. Bu dinleyici, sorguyla eşleşen ve güncellemeleri gerçek zamanlı olarak alan tüm mevcut verilerden haberdar edilecektir.
İlk olarak, varsayılan, filtrelenmemiş restoran listesini sunacak sorguyu oluşturalım. RestaurantsTableViewController.baseQuery()
uygulamasına bir göz atın:
return Firestore.firestore().collection("restaurants").limit(to: 50)
Bu sorgu, "restoranlar" adlı üst düzey koleksiyonun 50'ye kadar restoranını alır. Artık bir sorgumuz olduğuna göre, Firestore'dan uygulamamıza veri yüklemek için bir anlık görüntü dinleyicisi eklememiz gerekiyor. stopObserving()
çağrısından hemen sonra RestaurantsTableViewController.observeQuery()
yöntemine aşağıdaki kodu ekleyin.
listener = query.addSnapshotListener { [unowned self] (snapshot, error) in
guard let snapshot = snapshot else {
print("Error fetching snapshot results: \(error!)")
return
}
let models = snapshot.documents.map { (document) -> Restaurant in
if let model = Restaurant(dictionary: document.data()) {
return model
} else {
// Don't use fatalError here in a real app.
fatalError("Unable to initialize type \(Restaurant.self) with dictionary \(document.data())")
}
}
self.restaurants = models
self.documents = snapshot.documents
if self.documents.count > 0 {
self.tableView.backgroundView = nil
} else {
self.tableView.backgroundView = self.backgroundView
}
self.tableView.reloadData()
}
Yukarıdaki kod, koleksiyonu Firestore'dan indirir ve yerel olarak bir dizide saklar. addSnapshotListener(_:)
çağrısı, sorguya, sunucudaki veriler her değiştiğinde görünüm denetleyicisini güncelleyecek bir anlık görüntü dinleyicisi ekler. Güncellemeleri otomatik olarak alıyoruz ve değişiklikleri manuel olarak zorlamamız gerekmiyor. Bu anlık görüntü dinleyicisinin, sunucu tarafındaki bir değişikliğin sonucu olarak herhangi bir zamanda çağrılabileceğini unutmayın; bu nedenle, uygulamamızın değişiklikleri işleyebilmesi önemlidir.
Sözlüklerimizi yapılarla eşleştirdikten sonra (bkz. Restaurant.swift
), verileri görüntülemek sadece birkaç görünüm özelliği atamaktan ibarettir. Aşağıdaki satırları RestaurantsTableViewController.Swift içindeki RestaurantsTableViewController.swift
RestaurantTableViewCell.populate(restaurant:)
ekleyin.
nameLabel.text = restaurant.name
cityLabel.text = restaurant.city
categoryLabel.text = restaurant.category
starsView.rating = Int(restaurant.averageRating.rounded())
priceLabel.text = priceString(from: restaurant.price)
Bu doldurma yöntemi, tablo görünümü veri kaynağının tableView(_:cellForRowAtIndexPath:)
yönteminden çağrılır; bu yöntem, daha önce değer türleri koleksiyonunu tek tek tablo görünümü hücrelerine eşlemeyle ilgilenir.
Uygulamayı tekrar çalıştırın ve daha önce konsolda gördüğümüz restoranların artık simülatörde veya cihazda göründüğünü doğrulayın. Bu bölümü başarıyla tamamladıysanız, uygulamanız artık Cloud Firestore ile veri okuyor ve yazıyor!
6. Verileri Sıralama ve Filtreleme
Şu anda uygulamamız restoranların bir listesini gösteriyor, ancak kullanıcının ihtiyaçlarına göre filtreleme yapmasının bir yolu yok. Bu bölümde, filtrelemeyi etkinleştirmek için Firestore'un gelişmiş sorgulamasını kullanacaksınız.
İşte tüm Dim Sum restoranlarını getirmek için basit bir sorgu örneği:
let filteredQuery = query.whereField("category", isEqualTo: "Dim Sum")
Adından da anlaşılacağı gibi, whereField(_:isEqualTo:)
yöntemi, sorgumuzun yalnızca alanları belirlediğimiz kısıtlamaları karşılayan koleksiyonun üyelerini indirmesini sağlar. Bu durumda, yalnızca category
"Dim Sum"
olan restoranları indirir.
Bu uygulamada kullanıcı, "San Francisco'da Pizza" veya "Popülerlik tarafından sipariş edilen Los Angeles'ta Deniz Ürünleri" gibi belirli sorgular oluşturmak için birden çok filtreyi zincirleyebilir.
RestaurantsTableViewController.swift
dosyasını açın ve query(withCategory:city:price:sortBy:)
:
if let category = category, !category.isEmpty {
filtered = filtered.whereField("category", isEqualTo: category)
}
if let city = city, !city.isEmpty {
filtered = filtered.whereField("city", isEqualTo: city)
}
if let price = price {
filtered = filtered.whereField("price", isEqualTo: price)
}
if let sortBy = sortBy, !sortBy.isEmpty {
filtered = filtered.order(by: sortBy)
}
Yukarıdaki kod parçası, kullanıcı girdisine dayalı tek bir bileşik sorgu oluşturmak için birden çok whereField
ve order
yan tümcesi ekler. Şimdi sorgumuz yalnızca kullanıcının gereksinimleriyle eşleşen restoranları döndürecek.
Projenizi çalıştırın ve fiyata, şehre ve kategoriye göre filtreleyebildiğinizi doğrulayın (kategori ve şehir adlarını tam olarak yazdığınızdan emin olun). Test ederken, günlüklerinizde şöyle görünen hatalar görebilirsiniz:
Error fetching snapshot results: Error Domain=io.grpc Code=9 "The query requires an index. You can create it here: https://console.firebase.google.com/project/testapp-5d356/database/firestore/indexes?create_index=..." UserInfo={NSLocalizedDescription=The query requires an index. You can create it here: https://console.firebase.google.com/project/project-id/database/firestore/indexes?create_index=...}
Bunun nedeni, Firestore'un çoğu bileşik sorgu için dizin gerektirmesidir. Sorgularda dizinlerin zorunlu kılınması, Firestore'un ölçekte hızlı kalmasını sağlar. Hata mesajındaki bağlantının açılması, Firebase konsolunda dizin oluşturma kullanıcı arayüzünü, doğru parametreler doldurulmuş olarak otomatik olarak açacaktır. Firestore'daki dizinler hakkında daha fazla bilgi edinmek için belgeleri ziyaret edin .
7. Bir işlemde veri yazma
Bu bölümde, kullanıcılara restoranlara yorum gönderme olanağı ekleyeceğiz. Şimdiye kadar, tüm yazılarımız atomik ve nispeten basitti. Bunlardan herhangi biri hata verdiyse, muhtemelen kullanıcıdan bunları yeniden denemesini veya otomatik olarak yeniden denemesini isteriz.
Bir restorana derecelendirme eklemek için birden fazla okuma ve yazma işlemini koordine etmemiz gerekir. Önce incelemenin kendisi gönderilmeli, ardından restoranın puan sayısı ve ortalama puan güncellenmelidir. Bunlardan biri başarısız olurken diğeri başarısız olursa, veritabanımızın bir bölümündeki verilerin diğerindeki verilerle eşleşmediği tutarsız bir durumda kalırız.
Neyse ki Firestore, verilerimizin tutarlı kalmasını sağlayarak, tek bir atomik işlemde birden çok okuma ve yazma gerçekleştirmemizi sağlayan işlem işlevselliği sağlar.
RestaurantDetailViewController.reviewController(_:didSubmitFormWithReview:)
içindeki tüm let bildirimlerinin altına aşağıdaki kodu ekleyin.
let firestore = Firestore.firestore()
firestore.runTransaction({ (transaction, errorPointer) -> Any? in
// Read data from Firestore inside the transaction, so we don't accidentally
// update using stale client data. Error if we're unable to read here.
let restaurantSnapshot: DocumentSnapshot
do {
try restaurantSnapshot = transaction.getDocument(reference)
} catch let error as NSError {
errorPointer?.pointee = error
return nil
}
// Error if the restaurant data in Firestore has somehow changed or is malformed.
guard let data = restaurantSnapshot.data(),
let restaurant = Restaurant(dictionary: data) else {
let error = NSError(domain: "FireEatsErrorDomain", code: 0, userInfo: [
NSLocalizedDescriptionKey: "Unable to write to restaurant at Firestore path: \(reference.path)"
])
errorPointer?.pointee = error
return nil
}
// Update the restaurant's rating and rating count and post the new review at the
// same time.
let newAverage = (Float(restaurant.ratingCount) * restaurant.averageRating + Float(review.rating))
/ Float(restaurant.ratingCount + 1)
transaction.setData(review.dictionary, forDocument: newReviewReference)
transaction.updateData([
"numRatings": restaurant.ratingCount + 1,
"avgRating": newAverage
], forDocument: reference)
return nil
}) { (object, error) in
if let error = error {
print(error)
} else {
// Pop the review controller on success
if self.navigationController?.topViewController?.isKind(of: NewReviewViewController.self) ?? false {
self.navigationController?.popViewController(animated: true)
}
}
}
Güncelleme bloğunun içinde, işlem nesnesini kullanarak yaptığımız tüm işlemler, Firestore tarafından tek bir atomik güncelleme olarak ele alınacaktır. Güncelleme sunucuda başarısız olursa, Firestore bunu birkaç kez otomatik olarak yeniden deneyecektir. Bu, hata koşulumuzun büyük olasılıkla tekrar tekrar meydana gelen tek bir hata olduğu anlamına gelir; örneğin, cihaz tamamen çevrimdışıysa veya kullanıcının yazmaya çalıştığı yola yazma yetkisi yoksa.
8. Güvenlik kuralları
Uygulamamızın kullanıcıları, veritabanımızdaki her veri parçasını okuyup yazamamalıdır. Örneğin, herkes bir restoranın puanlarını görebilmeli, ancak yalnızca kimliği doğrulanmış bir kullanıcının puan vermesine izin verilmelidir. İstemciye iyi kod yazmak yeterli değil, tamamen güvenli olması için veri güvenliği modelimizi arka uçta belirtmemiz gerekiyor. Bu bölümde, verilerimizi korumak için Firebase güvenlik kurallarını nasıl kullanacağımızı öğreneceğiz.
Öncelikle codelab'in başında yazdığımız güvenlik kurallarına daha yakından bakalım. Firebase konsolunu açın ve Firestore sekmesinde Veritabanı > Kurallar'a gidin .
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
// Only authenticated users can read or write data
allow read, write: if request.auth != null;
}
}
}
Yukarıdaki kurallardaki request
değişkeni, tüm kurallarda bulunan global bir değişkendir ve eklediğimiz koşullu, kullanıcıların herhangi bir şey yapmasına izin vermeden önce isteğin kimliğinin doğrulanmasını sağlar. Bu, kimliği doğrulanmamış kullanıcıların verilerinizde yetkisiz değişiklikler yapmak için Firestore API'sini kullanmasını önler. Bu iyi bir başlangıç, ancak çok daha güçlü şeyler yapmak için Firestore kurallarını kullanabiliriz.
İncelemenin kullanıcı kimliğinin kimliği doğrulanmış kullanıcının kimliğiyle eşleşmesi için inceleme yazmalarını kısıtlayalım. Bu, kullanıcıların birbirlerini taklit etmelerini ve sahte incelemeler bırakmamalarını sağlar. Güvenlik kurallarınızı aşağıdakilerle değiştirin:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /restaurants/{any}/ratings/{rating} {
// Users can only write ratings with their user ID
allow read;
allow write: if request.auth != null
&& request.auth.uid == request.resource.data.userId;
}
match /restaurants/{any} {
// Only authenticated users can read or write data
allow read, write: if request.auth != null;
}
}
}
İlk eşleşme ifadesi, restaurants
koleksiyonuna ait herhangi bir belgenin ratings
adlı alt koleksiyonuyla eşleşir. allow write
koşulu, incelemenin kullanıcı kimliği kullanıcının kimliğiyle eşleşmiyorsa herhangi bir incelemenin gönderilmesini engeller. İkinci eşleşme ifadesi, kimliği doğrulanmış herhangi bir kullanıcının restoranları okuyup veritabanına yazmasına izin verir.
Bu, daha önce uygulamamıza yazdığımız örtülü garantiyi açıkça belirtmek için güvenlik kurallarını kullandığımızdan, incelemelerimiz için gerçekten iyi çalışıyor; bu, kullanıcıların yalnızca kendi incelemelerini yazabilecekleri. İncelemeler için bir düzenleme veya silme işlevi ekleseydik, aynı kurallar dizisi, kullanıcıların diğer kullanıcıların incelemelerini de değiştirmesini veya silmesini engellerdi. Ancak Firestore kuralları, belgelerin tamamı yerine belgelerdeki tek tek alanlara yazma işlemlerini sınırlamak için daha ayrıntılı bir şekilde de kullanılabilir. Bunu, kullanıcıların bir restoran için yalnızca derecelendirmeleri, ortalama derecelendirmeyi ve derecelendirme sayısını güncellemelerine izin vermek için kullanabilir ve kötü niyetli bir kullanıcının restoran adını veya konumunu değiştirme olasılığını ortadan kaldırabiliriz.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /restaurants/{restaurant} {
match /ratings/{rating} {
allow read: if request.auth != null;
allow write: if request.auth != null
&& request.auth.uid == request.resource.data.userId;
}
allow read: if request.auth != null;
allow create: if request.auth != null;
allow update: if request.auth != null
&& request.resource.data.name == resource.data.name
&& request.resource.data.city == resource.data.city
&& request.resource.data.price == resource.data.price
&& request.resource.data.category == resource.data.category;
}
}
}
Burada, hangi işlemlere izin verilmesi gerektiği konusunda daha net olabilmek için yazma iznimizi oluşturma ve güncelleme olarak ayırdık. Herhangi bir kullanıcı, codelab'in başlangıcında yaptığımız Doldur düğmesinin işlevselliğini koruyarak veritabanına restoran yazabilir, ancak bir restoran yazıldıktan sonra adı, konumu, fiyatı ve kategorisi değiştirilemez. Daha spesifik olarak, son kural, veritabanında zaten var olan alanların aynı adı, şehri, fiyatı ve kategorisini korumak için herhangi bir restoran güncelleme işlemini gerektirir.
Güvenlik kurallarıyla neler yapabileceğiniz hakkında daha fazla bilgi edinmek için belgelere bakın.
9. Sonuç
Bu kod laboratuvarında, Firestore ile temel ve gelişmiş okuma ve yazma işlemlerinin nasıl yapıldığını ve ayrıca güvenlik kurallarıyla veri erişiminin nasıl güvence altına alınacağını öğrendiniz. Tam çözümü codelab-complete
dalında bulabilirsiniz.
Firestore hakkında daha fazla bilgi edinmek için aşağıdaki kaynakları ziyaret edin: