Cloud Firestore iOS Codelab

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:

  1. Bir iOS uygulamasından Firestore'a veri okuma ve yazma
  2. Firestore verilerindeki değişiklikleri gerçek zamanlı olarak dinleyin
  3. Firestore verilerinin güvenliğini sağlamak için Firebase Kimlik Doğrulaması ve güvenlik kurallarını kullanın
  4. Karmaşık Firestore sorguları yazın

Önkoşullar

Bu codelab'i başlatmadan önce yüklediğinizden emin olun:

  • Xcode sürüm 8.3 (veya üstü)
  • CocoaPods 1.2.1 (veya üstü)

Firebase'i projeye ekleyin

  1. Git Firebase konsoluna .
  2. Yeni bir proje oluşturun ve projeyi "Firestore iOS Codelab" adını seçin.

Kodu İndir

Klonlama başlayın örnek projeyi ve çalışan pod update proje dizininde:

git clone https://github.com/firebase/friendlyeats-ios
cd friendlyeats-ios
pod update

Açık FriendlyEats.xcworkspace Xcode ve çalıştırın (Cmd + R). Uygulama doğru derlemek ve bir eksik beri hemen başlatılması çökmesine gerekir GoogleService-Info.plist dosyasını. Bir sonraki adımda bunu düzelteceğiz.

Firebase'i kurun

Takip belgelere yeni Firestore projesi oluşturmak için. Projenizi edindikten sonra, projenizin indirmek GoogleService-Info.plist dosyayı Firebase konsolunun Xcode projenin köküne ve sürükle onu. 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.

10a0671ce8f99704.png

Bu bölümde, uygulama kullanıcı arayüzünü doldurabilmemiz için Firestore'a bazı veriler yazacağız. Bu yoluyla elle yapılabilir Firebase konsoluna , ama biz de temel Firestore yazma göstermek için uygulamanın kendisindeki yapacağım.

Uygulamamızdaki ana model nesnesi bir restorandır. Firestore verileri belgelere, koleksiyonlara ve alt koleksiyonlara bölünür. Biz denilen bir üst düzey koleksiyonunda bir belge olarak her restoran saklayacaktır restaurants . Daha Firestore veri modeli hakkında bilgi edinmek isterseniz, belge ve koleksiyonları hakkında okumak belgeler .

Firestore'a veri ekleyebilmemiz için restoran koleksiyonuna bir referans almamız gerekiyor. Döngü için iç aşağıdakileri ekleyin RestaurantsTableViewController.didTapPopulateButton(_:) yöntem.

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 deneme yaparken sürekli kimlik doğrulama sorunlarıyla karşılaşmayacağız. Bu codelab'in 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.

Gelen Kuralları sekmesinin Firebase konsolunun aşağıdaki kuralları ekleyebilir ve daha sonra Yayınla 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;
    }
  }
}

Biz daha sonra ayrıntılı olarak güvenlik kurallarını tartışmak, ancak acele iseniz, bir göz alacağım güvenlik kuralları belgeler .

Uygulamayı ve işareti çalıştırın. Sonra lokanta, belgeleri toplu yaratacak sol üstteki, içinde "Doldur" düğmesine dokunun, henüz uygulamada bu görmez rağmen.

Sonra, hiç gezinmek Firestore veri sekmesine Firebase konsolunda. Artık restoranlar koleksiyonunda yeni girişler görmelisiniz:

Ekran Görüntüsü 2017-07-06 12.45.38 PM.png

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.

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. Uygulanmasında bir göz atın RestaurantsTableViewController.baseQuery() :

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. Aşağıdaki kodu ekleyin RestaurantsTableViewController.observeQuery() sadece çağrısının ardından yönteme stopObserving() .

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ı View Controller veri sunucusunda her değiştirdiğinde güncellenir sorguya anlık işleyicisi 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.

Yapılar (görmek için sözlükleri eşleştirdikten sonra Restaurant.swift ), verilerin görüntülenmesi birkaç görünüm özelliklerini atama sadece meselesidir. Aşağıdaki satırları ekleyin RestaurantTableViewCell.populate(restaurant:) içinde RestaurantsTableViewController.swift .

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 doldurmak yöntem tablo görünümü veri kaynağının çağrılır tableView(_:cellForRowAtIndexPath:) önce bireysel tablo görünümü hücrelerine yönteminden değer türleri koleksiyonu haritalama 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!

2ca7f8c6052f7f79.png

Ş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 bizim sorgu indir alanları belirlediğimiz kısıtlamaları karşılamak koleksiyonun sadece üyeler yapacaktır. Bu durumda, sadece restoranlar indirirsiniz category olan "Dim Sum" .

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.

Açık RestaurantsTableViewController.swift ve ortasına Aşağıdaki kod bloğunu ekleyin 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)
}

Pasajı yukarıda çoklu ekler whereField ve order kullanıcı girişi temel bir tek bir bileşik sorgu oluşturmak için maddeler. Ş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 şuna benzer 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ından bağlantıyı açma otomatik olarak doldurulur doğru parametrelerle Firebase konsolundaki indeks oluşturma UI açılacaktır., Firestore içinde endeksler hakkında daha fazla bilgi edinmek için belgelere ziyaret .

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.

Tüm let bildirimleri aşağıda aşağıdaki kodu ekleyin RestaurantDetailViewController.reviewController(_:didSubmitFormWithReview:) .

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.

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. İçin Firebase konsolunu ve gezinmek açın Veritabanı> Firestore sekmede Kuralları .

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;
    }
  }
}

request kurallarında değişken yukarıdaki tüm kurallara mevcut küresel değişkendir ve koşullu biz isteği kullanıcıların bir şey yapmak için izin vermeden önce doğrulanmış olmasını sağlar ekledi. 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 edememelerini 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 maç deyimi subcollection adlı maçları ratings ait herhangi bir belgenin restaurants koleksiyonu. allow write koşullu sonra incelemenin kullanıcı kimliği kullanan kişinin aynı değilse sunulmadan herhangi gözden 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, bu tam olarak 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ı ile yapabilecekleriniz hakkında daha fazla bilgi edinmek için bir göz atın belgelere .

Bu kod laboratuvarında, Firestore ile temel ve gelişmiş okuma ve yazma işlemlerinin nasıl yapılacağını ve ayrıca güvenlik kurallarıyla veri erişiminin nasıl güvence altına alınacağını öğrendiniz. Sen tam bir çözüm bulabilirsiniz codelab-complete şube .

Firestore hakkında daha fazla bilgi edinmek için aşağıdaki kaynakları ziyaret edin: