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. Firebase konsoluna gidin .
  2. Yeni Proje Oluştur'u seçin ve projenize "Firestore iOS Codelab" adını verin.

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 GoogleService-Info.plist . 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ı GoogleService-Info.plist 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.

10a0671ce8f99704.png

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. 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. Aşağıdakileri, RestaurantsTableViewController.didTapPopulateButton(_:) yöntemindeki iç for döngüsüne ekleyin.

0b977777990

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 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.

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:

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. 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ı 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 doldurma yöntemi, tablo görünümü veri kaynağının tableView(_:cellForRowAtIndexPath:) yönteminden çağrılır; bu yöntem, önceki 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!

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, whereField(_:isEqualTo:) yalnızca alanları belirlediğimiz kısıtlamaları karşılayan koleksiyonun üyelerini indirmesini sağlar. Bu durumda, yalnızca category "Dim Sum" 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 query(withCategory:city:price:sortBy:) ve query(withCategory:city:price:sortBy:) 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)
}

Yukarıdaki whereField parçası, kullanıcı girdisine dayalı tek bir bileşik sorgu oluşturmak order 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 ş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 dizinler gerektirmek, Firestore'u geniş ölçekte hızlı tutar. 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 .

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.

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 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 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şmezse 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, 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ıyla neler yapabileceğiniz hakkında daha fazla bilgi edinmek için belgelere bakın .

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. Tam çözümü codelab-complete dalında bulabilirsiniz .

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