সুইফট কোডেবল সহ ক্লাউড ফায়ারস্টোর ডেটা ম্যাপ করুন

সুইফটের কোডেবল এপিআই, সুইফট 4-এ প্রবর্তন করা হয়েছে, আমাদেরকে কম্পাইলারের শক্তিকে কাজে লাগাতে সাহায্য করে যাতে করে ক্রমিক বিন্যাস থেকে সুইফট প্রকারে ডেটা ম্যাপ করা সহজ হয়।

আপনি একটি ওয়েব API থেকে আপনার অ্যাপের ডেটা মডেলে ডেটা ম্যাপ করতে কোডেবল ব্যবহার করতে পারেন (এবং এর বিপরীতে), কিন্তু এটি তার চেয়ে অনেক বেশি নমনীয়।

এই নির্দেশিকায়, আমরা Cloud Firestore থেকে সুইফট প্রকারে ডেটা ম্যাপ করতে কোডেবল কীভাবে ব্যবহার করা যেতে পারে তা দেখতে যাচ্ছি এবং এর বিপরীতে।

Cloud Firestore থেকে একটি নথি আনার সময়, আপনার অ্যাপটি কী/মান জোড়ার একটি অভিধান পাবে (অথবা অভিধানের একটি অ্যারে, যদি আপনি একাধিক নথি ফিরিয়ে দেওয়ার ক্রিয়াকলাপগুলির একটি ব্যবহার করেন)।

এখন, আপনি অবশ্যই সুইফটে সরাসরি অভিধান ব্যবহার করা চালিয়ে যেতে পারেন, এবং তারা কিছু দুর্দান্ত নমনীয়তা অফার করে যা আপনার ব্যবহারের ক্ষেত্রে যা প্রয়োজন তা হতে পারে। যাইহোক, এই পদ্ধতিটি টাইপ নিরাপদ নয় এবং বৈশিষ্ট্যের নামের ভুল বানান করে বা আপনার দল গত সপ্তাহে সেই উত্তেজনাপূর্ণ নতুন বৈশিষ্ট্যটি পাঠানোর সময় যোগ করা নতুন বৈশিষ্ট্য ম্যাপ করতে ভুলে গিয়ে হার্ড-টু-ট্র্যাক-ডাউন বাগগুলি প্রবর্তন করা সহজ।

অতীতে, অনেক বিকাশকারী একটি সাধারণ ম্যাপিং স্তর প্রয়োগ করে এই ত্রুটিগুলিকে ঘিরে কাজ করেছে যা তাদের সুইফ্ট প্রকারের অভিধানগুলিকে ম্যাপ করতে দেয়৷ কিন্তু আবার, এই বাস্তবায়নের বেশিরভাগই Cloud Firestore ডকুমেন্ট এবং আপনার অ্যাপের ডেটা মডেলের সংশ্লিষ্ট প্রকারের মধ্যে ম্যাপিং ম্যানুয়ালি নির্দিষ্ট করার উপর ভিত্তি করে।

Cloud Firestore সাহায্যে সুইফ্টের কোডেবল API-এর জন্য এটি অনেক সহজ হয়ে যায়:

  • আপনাকে আর ম্যানুয়ালি কোনো ম্যাপিং কোড বাস্তবায়ন করতে হবে না।
  • বিভিন্ন নামের সাথে বৈশিষ্ট্যগুলি কীভাবে ম্যাপ করা যায় তা সংজ্ঞায়িত করা সহজ।
  • এটিতে সুইফটের অনেক ধরণের জন্য অন্তর্নির্মিত সমর্থন রয়েছে।
  • এবং কাস্টম ধরনের ম্যাপিংয়ের জন্য সমর্থন যোগ করা সহজ।
  • সর্বোত্তম: সাধারণ ডেটা মডেলগুলির জন্য, আপনাকে কোনও ম্যাপিং কোড লিখতে হবে না।

ম্যাপিং ডেটা

Cloud Firestore নথিতে ডেটা সঞ্চয় করে যা মানগুলির কী ম্যাপ করে। একটি স্বতন্ত্র নথি থেকে ডেটা আনার জন্য, আমরা DocumentSnapshot.data() কল করতে পারি, যা একটি অভিধান প্রদান করে যা ফিল্ডের নাম ম্যাপিং করে Any : func data() -> [String : Any]? .

এর মানে আমরা প্রতিটি পৃথক ক্ষেত্রে অ্যাক্সেস করতে সুইফটের সাবস্ক্রিপ্ট সিনট্যাক্স ব্যবহার করতে পারি।

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

যদিও এটি সহজবোধ্য এবং প্রয়োগ করা সহজ বলে মনে হতে পারে, এই কোডটি ভঙ্গুর, বজায় রাখা কঠিন এবং ত্রুটি-প্রবণ।

আপনি দেখতে পাচ্ছেন, আমরা ডকুমেন্ট ফিল্ডের ডেটা প্রকার সম্পর্কে অনুমান করছি। এইগুলি সঠিক হতে পারে বা নাও হতে পারে।

মনে রাখবেন, যেহেতু কোনও স্কিমা নেই, আপনি সহজেই সংগ্রহে একটি নতুন নথি যোগ করতে পারেন এবং একটি ক্ষেত্রের জন্য একটি ভিন্ন প্রকার চয়ন করতে পারেন৷ আপনি ঘটনাক্রমে numberOfPages ক্ষেত্রের জন্য স্ট্রিং বেছে নিতে পারেন, যার ফলে ম্যাপিং-এর সমস্যা খুঁজে পাওয়া কঠিন হবে। এছাড়াও, যখনই একটি নতুন ক্ষেত্র যোগ করা হবে তখনই আপনাকে আপনার ম্যাপিং কোড আপডেট করতে হবে, যা বরং কষ্টকর।

এবং আসুন আমরা ভুলে যাই না যে আমরা সুইফটের শক্তিশালী টাইপ সিস্টেমের সুবিধা নিচ্ছি না, যা Book প্রতিটি বৈশিষ্ট্যের জন্য সঠিক টাইপ জানে।

কোডেবল কি, যাইহোক?

অ্যাপলের ডকুমেন্টেশন অনুসারে, কোডেবল হল "একটি প্রকার যা নিজেকে একটি বাহ্যিক উপস্থাপনায় এবং এর বাইরে রূপান্তর করতে পারে।" আসলে, কোডেবল হল এনকোডেবল এবং ডিকোডেবল প্রোটোকলের জন্য একটি টাইপ উপনাম। এই প্রোটোকলের সাথে একটি সুইফ্ট টাইপ মেনে চলার মাধ্যমে, কম্পাইলার এই ধরণের একটি উদাহরণকে এনকোড/ডিকোড করার জন্য প্রয়োজনীয় কোড সংশ্লেষিত করবে, যেমন JSON-এর মতো সিরিয়াল ফর্ম্যাট থেকে।

একটি বই সম্পর্কে ডেটা সঞ্চয় করার জন্য একটি সাধারণ ধরন এইরকম দেখতে পারে:

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

আপনি দেখতে পাচ্ছেন, কোডেবলের সাথে টাইপ মেনে চলা ন্যূনতম আক্রমণাত্মক। আমাদের শুধুমাত্র প্রোটোকলের সাথে সামঞ্জস্য যোগ করতে হয়েছিল; অন্য কোন পরিবর্তন প্রয়োজন ছিল না.

এটির সাথে, আমরা এখন সহজেই একটি JSON অবজেক্টে একটি বই এনকোড করতে পারি:

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)")
}

একটি Book উদাহরণে একটি JSON অবজেক্ট ডিকোড করা নিম্নরূপ কাজ করে:

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

Cloud Firestore নথিতে সহজ প্রকারে এবং থেকে ম্যাপিং
কোডেবল ব্যবহার করে

Cloud Firestore সাধারণ স্ট্রিং থেকে নেস্টেড ম্যাপ পর্যন্ত ডেটা প্রকারের বিস্তৃত সেট সমর্থন করে। এর মধ্যে বেশিরভাগই সরাসরি সুইফটের বিল্ট-ইন প্রকারের সাথে মিলে যায়। আরও জটিল তথ্যের মধ্যে ডুব দেওয়ার আগে আসুন প্রথমে কিছু সাধারণ ডেটা প্রকারের ম্যাপিংয়ের দিকে একবার নজর দিই।

Cloud Firestore নথিগুলিকে সুইফ্ট প্রকারে ম্যাপ করতে, এই পদক্ষেপগুলি অনুসরণ করুন:

  1. নিশ্চিত করুন যে আপনি আপনার প্রকল্পে FirebaseFirestore ফ্রেমওয়ার্ক যোগ করেছেন। আপনি এটি করতে সুইফট প্যাকেজ ম্যানেজার বা কোকোপডস ব্যবহার করতে পারেন।
  2. আপনার সুইফট ফাইলে FirebaseFirestore আমদানি করুন।
  3. Codable সাথে আপনার টাইপ কনফর্ম করুন।
  4. (ঐচ্ছিক, যদি আপনি একটি List ভিউতে টাইপটি ব্যবহার করতে চান) আপনার টাইপে একটি id প্রপার্টি যোগ করুন এবং Cloud Firestore ডকুমেন্ট আইডিতে ম্যাপ করতে বলার জন্য @DocumentID ব্যবহার করুন। আমরা নীচে আরো বিস্তারিত আলোচনা করব.
  5. একটি সুইফট টাইপের নথির রেফারেন্স ম্যাপ করতে documentReference.data(as: ) ব্যবহার করুন।
  6. একটি Cloud Firestore নথিতে Swift প্রকার থেকে ডেটা ম্যাপ করতে documentReference.setData(from: ) ব্যবহার করুন৷
  7. (ঐচ্ছিক, কিন্তু অত্যন্ত প্রস্তাবিত) সঠিক ত্রুটি হ্যান্ডলিং বাস্তবায়ন করুন।

আসুন সেই অনুযায়ী আমাদের Book ধরন আপডেট করি:

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

যেহেতু এই প্রকারটি ইতিমধ্যেই কোডযোগ্য ছিল, তাই আমাদের শুধুমাত্র id প্রপার্টি যোগ করতে হবে এবং @DocumentID প্রপার্টি র‍্যাপার দিয়ে টীকা করতে হবে।

একটি নথি আনা এবং ম্যাপ করার জন্য পূর্ববর্তী কোড স্নিপেট গ্রহণ করে, আমরা সমস্ত ম্যানুয়াল ম্যাপিং কোডকে একটি লাইন দিয়ে প্রতিস্থাপন করতে পারি:

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

getDocument(as:) কল করার সময় নথির ধরন উল্লেখ করে আপনি এটি আরও সংক্ষিপ্তভাবে লিখতে পারেন। এটি আপনার জন্য ম্যাপিং সঞ্চালন করবে, এবং ম্যাপ করা নথি সম্বলিত একটি Result ধরণ প্রদান করবে, অথবা ডিকোডিং ব্যর্থ হলে একটি ত্রুটি:

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)"
    }
  }
}

একটি বিদ্যমান নথি আপডেট করা documentReference.setData(from: ) কল করার মতোই সহজ। কিছু মৌলিক ত্রুটি পরিচালনা সহ, এখানে একটি 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)
    }
  }
}

একটি নতুন নথি যোগ করার সময়, Cloud Firestore স্বয়ংক্রিয়ভাবে নথিতে একটি নতুন নথি আইডি বরাদ্দ করার যত্ন নেবে৷ অ্যাপটি বর্তমানে অফলাইনে থাকলেও এটি কাজ করে।

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

সাধারণ ডেটা টাইপ ম্যাপ করার পাশাপাশি, Cloud Firestore অনেকগুলি অন্যান্য ডেটাটাইপ সমর্থন করে, যার মধ্যে কিছু কাঠামোগত প্রকার যা আপনি একটি নথির ভিতরে নেস্টেড অবজেক্ট তৈরি করতে ব্যবহার করতে পারেন।

নেস্টেড কাস্টম প্রকার

আমরা আমাদের নথিতে ম্যাপ করতে চাই বেশিরভাগ গুণাবলী হল সাধারণ মান, যেমন বইয়ের শিরোনাম বা লেখকের নাম। কিন্তু সেই ক্ষেত্রে কী হবে যখন আমাদের আরও জটিল বস্তু সঞ্চয় করতে হবে? উদাহরণস্বরূপ, আমরা বিভিন্ন রেজোলিউশনে বইয়ের কভারে URLগুলি সংরক্ষণ করতে চাই।

Cloud Firestore এটি করার সবচেয়ে সহজ উপায় হল একটি মানচিত্র ব্যবহার করা:

একটি Firestore নথিতে একটি নেস্টেড কাস্টম টাইপ সংরক্ষণ করা

সংশ্লিষ্ট সুইফ্ট স্ট্রাকট লেখার সময়, Cloud Firestore ইউআরএল-কে সমর্থন করে তা আমরা ব্যবহার করতে পারি — যখন একটি ইউআরএল রয়েছে এমন একটি ফিল্ড সংরক্ষণ করার সময়, এটি একটি স্ট্রিং-এ রূপান্তরিত হবে এবং এর বিপরীতে:

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

Cloud Firestore নথিতে কভার মানচিত্রের জন্য আমরা কীভাবে একটি স্ট্রাকট, CoverImages সংজ্ঞায়িত করেছি তা লক্ষ্য করুন। BookWithCoverImages এ কভার প্রপার্টিটিকে ঐচ্ছিক হিসেবে চিহ্নিত করার মাধ্যমে, আমরা কিছু নথিতে একটি কভার অ্যাট্রিবিউট নাও থাকতে পারে তা সামলে নিতে পারি।

আপনি যদি কৌতূহলী হন কেন ডেটা আনার বা আপডেট করার জন্য কোনও কোড স্নিপেট নেই, তাহলে আপনি এটি শুনে খুশি হবেন যে Cloud Firestore থেকে/এ পড়ার বা লেখার জন্য কোড সামঞ্জস্য করার প্রয়োজন নেই: এই সমস্ত কিছু আমরা কোডের সাথে কাজ করি প্রাথমিক বিভাগে লিখেছি।

অ্যারে

কখনও কখনও, আমরা একটি নথিতে মান সংগ্রহ করতে চাই। একটি বইয়ের জেনারগুলি একটি ভাল উদাহরণ: The Hitchhiker's Guide to the Galaxy- এর মতো একটি বই বিভিন্ন বিভাগে পড়তে পারে — এই ক্ষেত্রে "Sci-Fi" এবং "কমেডি":

একটি ফায়ারস্টোর নথিতে একটি অ্যারে সংরক্ষণ করা

Cloud Firestore , আমরা মানগুলির একটি অ্যারে ব্যবহার করে এটিকে মডেল করতে পারি। এটি যেকোন কোডেবল প্রকারের জন্য সমর্থিত (যেমন String , Int , ইত্যাদি)। আমাদের Book মডেলে জেনারের একটি অ্যারে কীভাবে যুক্ত করা যায় তা নিম্নলিখিতটি দেখায়:

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

যেহেতু এটি যেকোনো কোডেবল টাইপের জন্য কাজ করে, তাই আমরা কাস্টম প্রকারগুলিও ব্যবহার করতে পারি। কল্পনা করুন আমরা প্রতিটি বইয়ের জন্য ট্যাগের একটি তালিকা সংরক্ষণ করতে চাই। ট্যাগের নামের সাথে, আমরা ট্যাগের রঙও সংরক্ষণ করতে চাই, যেমন:

একটি Firestore নথিতে কাস্টম ধরনের একটি অ্যারে সংরক্ষণ করা

এইভাবে ট্যাগগুলি সংরক্ষণ করার জন্য, আমাদের যা করতে হবে তা হল একটি ট্যাগ প্রতিনিধিত্ব করার জন্য একটি Tag স্ট্রাকট প্রয়োগ করা এবং এটিকে কোডেবল করা:

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

এবং ঠিক তেমনই, আমরা আমাদের Book নথিতে Tags একটি অ্যারে সংরক্ষণ করতে পারি!

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

ডকুমেন্ট আইডি ম্যাপিং সম্পর্কে একটি দ্রুত শব্দ

আরও টাইপ ম্যাপ করার আগে, আসুন কিছুক্ষণের জন্য ডকুমেন্ট আইডি ম্যাপিং সম্পর্কে কথা বলি।

আমাদের Cloud Firestore ডকুমেন্টের ডকুমেন্ট আইডিকে আমাদের সুইফট ধরনের id প্রপার্টিতে ম্যাপ করতে আমরা পূর্ববর্তী কিছু উদাহরণে @DocumentID প্রপার্টি র‍্যাপার ব্যবহার করেছি। এটি বেশ কয়েকটি কারণে গুরুত্বপূর্ণ:

  • ব্যবহারকারী স্থানীয় পরিবর্তন করলে কোন নথি আপডেট করতে হবে তা জানতে এটি আমাদের সাহায্য করে।
  • সুইফটইউআই-এর List উপাদানগুলিকে Identifiable হতে হবে যাতে উপাদানগুলি ঢোকানোর সময় চারপাশে লাফাতে না পারে।

এটি উল্লেখ করার মতো যে @DocumentID হিসাবে চিহ্নিত একটি বৈশিষ্ট্য Cloud Firestore এনকোডার দ্বারা নথিটি লেখার সময় এনকোড করা হবে না। কারণ ডকুমেন্ট আইডি ডকুমেন্টেরই কোনো অ্যাট্রিবিউট নয়—তাই ডকুমেন্টে লেখাটা ভুল হবে।

নেস্টেড প্রকারের সাথে কাজ করার সময় (যেমন এই নির্দেশিকায় আগের উদাহরণে Book ট্যাগগুলির অ্যারে), এটি একটি @DocumentID বৈশিষ্ট্য যোগ করার প্রয়োজন নেই: নেস্টেড বৈশিষ্ট্যগুলি Cloud Firestore নথির একটি অংশ, এবং গঠন করে না একটি পৃথক নথি। অতএব, তাদের একটি নথি আইডির প্রয়োজন নেই।

তারিখ এবং সময়

তারিখ এবং সময় পরিচালনা করার জন্য Cloud Firestore একটি অন্তর্নির্মিত ডেটা টাইপ রয়েছে এবং কোডেবলের জন্য Cloud Firestore সমর্থনের জন্য ধন্যবাদ, সেগুলি ব্যবহার করা সহজ।

আসুন এই নথিটি একবার দেখে নেওয়া যাক যা 1843 সালে উদ্ভাবিত সমস্ত প্রোগ্রামিং ভাষার জননী অ্যাডাকে প্রতিনিধিত্ব করে:

একটি Firestore নথিতে তারিখ সংরক্ষণ করা

এই নথির ম্যাপিংয়ের জন্য একটি সুইফ্ট টাইপ এইরকম দেখতে পারে:

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

@ServerTimestamp সম্পর্কে কথোপকথন না করে আমরা তারিখ এবং সময় সম্পর্কে এই বিভাগটি ছেড়ে যেতে পারি না। আপনার অ্যাপে টাইমস্ট্যাম্প নিয়ে কাজ করার ক্ষেত্রে এই সম্পত্তির মোড়ক একটি পাওয়ার হাউস।

যেকোন ডিস্ট্রিবিউটেড সিস্টেমে, আলাদা আলাদা সিস্টেমের ঘড়িগুলো সব সময় সম্পূর্ণ সিঙ্ক না হওয়ার সম্ভাবনা থাকে। আপনি ভাবতে পারেন এটি একটি বড় বিষয় নয়, তবে একটি স্টক ট্রেড সিস্টেমের জন্য একটি ঘড়ির কিছুটা সিঙ্কের বাইরে চলার প্রভাব কল্পনা করুন: এমনকি একটি মিলিসেকেন্ডের বিচ্যুতিও একটি ট্রেড নির্বাহ করার সময় মিলিয়ন ডলারের পার্থক্য হতে পারে৷

Cloud Firestore @ServerTimestamp দ্বারা চিহ্নিত বৈশিষ্ট্যগুলিকে নিম্নরূপ পরিচালনা করে: যদি আপনি এটি সংরক্ষণ করার সময় বৈশিষ্ট্যটি nil হয় (উদাহরণস্বরূপ addDocument() ব্যবহার করে), Cloud Firestore ডাটাবেসে লেখার সময় বর্তমান সার্ভার টাইমস্ট্যাম্প সহ ক্ষেত্রটি পূরণ করবে . আপনি addDocument() বা updateData() কল করার সময় ক্ষেত্রটি nil না হলে, Cloud Firestore অ্যাট্রিবিউটের মানটিকে স্পর্শ না করে রেখে দেবে। এইভাবে, createdAt এবং lastUpdatedAt মতো ক্ষেত্রগুলি বাস্তবায়ন করা সহজ।

জিওপয়েন্ট

জিওলোকেশন আমাদের অ্যাপে সর্বব্যাপী। অনেক উত্তেজনাপূর্ণ বৈশিষ্ট্য তাদের সংরক্ষণ করে সম্ভব হয়ে ওঠে. উদাহরণস্বরূপ, একটি কাজের জন্য একটি অবস্থান সংরক্ষণ করা দরকারী হতে পারে যাতে আপনি একটি গন্তব্যে পৌঁছালে আপনার অ্যাপটি আপনাকে একটি টাস্ক সম্পর্কে মনে করিয়ে দিতে পারে।

Cloud Firestore একটি অন্তর্নির্মিত ডেটা টাইপ রয়েছে, GeoPoint , যা যেকোনো অবস্থানের দ্রাঘিমাংশ এবং অক্ষাংশ সংরক্ষণ করতে পারে। একটি Cloud Firestore ডকুমেন্ট থেকে/তে অবস্থান ম্যাপ করতে, আমরা GeoPoint টাইপ ব্যবহার করতে পারি:

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

সুইফ্টের সংশ্লিষ্ট ধরনটি হল CLLocationCoordinate2D , এবং আমরা নিম্নলিখিত ক্রিয়াকলাপের মাধ্যমে এই দুটি প্রকারের মধ্যে মানচিত্র করতে পারি:

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

ভৌত অবস্থান দ্বারা নথি অনুসন্ধান সম্পর্কে আরও জানতে, এই সমাধান নির্দেশিকাটি দেখুন।

Enums

Enums সম্ভবত সুইফটের সবচেয়ে আন্ডাররেটেড ভাষার বৈশিষ্ট্যগুলির মধ্যে একটি; চোখের দেখা ছাড়া তাদের কাছে আরও অনেক কিছু আছে। enums-এর জন্য একটি সাধারণ ব্যবহারের ক্ষেত্রে কিছুর বিচ্ছিন্ন অবস্থার মডেল করা। উদাহরণস্বরূপ, আমরা নিবন্ধগুলি পরিচালনা করার জন্য একটি অ্যাপ লিখতে পারি। একটি নিবন্ধের স্থিতি ট্র্যাক করতে, আমরা একটি enum Status ব্যবহার করতে চাই:

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

Cloud Firestore স্থানীয়ভাবে enums সমর্থন করে না (অর্থাৎ, এটি মানগুলির সেট প্রয়োগ করতে পারে না), তবে আমরা এখনও এই সত্যটি ব্যবহার করতে পারি যে enums টাইপ করা যেতে পারে এবং একটি কোডেবল টাইপ চয়ন করতে পারি। এই উদাহরণে, আমরা String বেছে নিয়েছি, যার মানে Cloud Firestore ডকুমেন্টে সঞ্চয় করার সময় সমস্ত enum মান স্ট্রিং-এ/থেকে ম্যাপ করা হবে।

এবং, যেহেতু সুইফ্ট কাস্টম কাঁচা মান সমর্থন করে, আমরা এমনকি কাস্টমাইজ করতে পারি কোন মানগুলি কোন enum কেসকে নির্দেশ করে। সুতরাং উদাহরণস্বরূপ, যদি আমরা Status.inReview কেসকে "পর্যালোচনায়" হিসাবে সংরক্ষণ করার সিদ্ধান্ত নিই, তাহলে আমরা উপরের enumটিকে নিম্নরূপ আপডেট করতে পারি:

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

ম্যাপিং কাস্টমাইজ করা

কখনও কখনও, আমরা যে Cloud Firestore ডকুমেন্টগুলি ম্যাপ করতে চাই তার অ্যাট্রিবিউটের নামগুলি আমাদের সুইফটের ডেটা মডেলের বৈশিষ্ট্যগুলির নামের সাথে মেলে না৷ উদাহরণস্বরূপ, আমাদের সহকর্মীদের মধ্যে একজন পাইথন বিকাশকারী হতে পারে এবং তাদের সমস্ত বৈশিষ্ট্যের নামের জন্য snake_case বেছে নেওয়ার সিদ্ধান্ত নিয়েছে৷

চিন্তা করবেন না: কোডেবল আমাদের কভার করেছে!

এই ধরনের ক্ষেত্রে, আমরা CodingKeys ব্যবহার করতে পারি। এটি একটি enum যা আমরা নির্দিষ্ট বৈশিষ্ট্যগুলি কীভাবে ম্যাপ করা হবে তা নির্দিষ্ট করতে একটি কোডেবল স্ট্রাকটে যোগ করতে পারি।

এই নথি বিবেচনা করুন:

একটি snake_cased অ্যাট্রিবিউট নামের একটি Firestore নথি৷

এই ডকুমেন্টটিকে এমন একটি স্ট্রাকটে ম্যাপ করতে যার একটি নাম বৈশিষ্ট্য রয়েছে String টাইপ, আমাদের ProgrammingLanguage স্ট্রাকটে একটি CodingKeys enum যোগ করতে হবে এবং নথিতে বৈশিষ্ট্যের নাম উল্লেখ করতে হবে:

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

ডিফল্টরূপে, Cloud Firestore নথিতে আমরা ম্যাপ করার চেষ্টা করছি এমন বৈশিষ্ট্যের নাম নির্ধারণ করতে Codable API আমাদের সুইফ্ট প্রকারের সম্পত্তির নাম ব্যবহার করবে। তাই যতক্ষণ পর্যন্ত অ্যাট্রিবিউটের নাম মেলে, ততক্ষণ আমাদের কোডেবল প্রকারে CodingKeys যোগ করার দরকার নেই। যাইহোক, একবার আমরা একটি নির্দিষ্ট ধরণের জন্য CodingKeys ব্যবহার করলে, আমরা ম্যাপ করতে চাই এমন সমস্ত সম্পত্তির নাম যোগ করতে হবে।

উপরের কোড স্নিপেটে, আমরা একটি id প্রপার্টি সংজ্ঞায়িত করেছি যা আমরা একটি SwiftUI List ভিউতে সনাক্তকারী হিসাবে ব্যবহার করতে চাই। যদি আমরা এটি CodingKeys এ নির্দিষ্ট না করে থাকি, তাহলে ডেটা আনার সময় এটি ম্যাপ করা হবে না এবং এইভাবে nil হয়ে যাবে। এর ফলে List ভিউ প্রথম নথিতে পূর্ণ হবে।

সংশ্লিষ্ট CodingKeys enum-এ কেস হিসেবে তালিকাভুক্ত নয় এমন কোনো সম্পত্তি ম্যাপিং প্রক্রিয়া চলাকালীন উপেক্ষা করা হবে। এটি আসলে সুবিধাজনক হতে পারে যদি আমরা বিশেষভাবে কিছু বৈশিষ্ট্য ম্যাপ করা থেকে বাদ দিতে চাই।

সুতরাং উদাহরণস্বরূপ, যদি আমরা এই বৈশিষ্ট্যটিকে ম্যাপ করা থেকে reasonWhyILoveThis বাদ দিতে চাই, তবে আমাদের যা করতে হবে তা হল 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
  }
}

মাঝে মাঝে আমরা Cloud Firestore নথিতে একটি খালি বৈশিষ্ট্য লিখতে চাই। সুইফ্টের একটি মানের অনুপস্থিতি বোঝাতে ঐচ্ছিক ধারণা রয়েছে এবং Cloud Firestore null মানগুলিকেও সমর্থন করে। যাইহোক, এনকোডিং ঐচ্ছিকগুলির জন্য ডিফল্ট আচরণ যেগুলির একটি nil মান রয়েছে কেবল সেগুলিকে বাদ দেওয়া। @ExplicitNull আমাদেরকে সুইফ্ট বিকল্পগুলিকে এনকোড করার সময় কীভাবে পরিচালনা করা হয় তার উপর কিছু নিয়ন্ত্রণ দেয়: @ExplicitNull হিসাবে একটি ঐচ্ছিক প্রপার্টি ফ্ল্যাগ করে, আমরা Cloud Firestore বলতে পারি এই প্রপার্টিটিকে শূন্য মান সহ নথিতে লিখতে যদি এতে nil এর মান থাকে।

রঙ ম্যাপ করার জন্য একটি কাস্টম এনকোডার এবং ডিকোডার ব্যবহার করা

কোডেবলের সাথে ম্যাপিং ডেটার আমাদের কভারেজের একটি শেষ বিষয় হিসাবে, আসুন কাস্টম এনকোডার এবং ডিকোডারগুলি প্রবর্তন করি। এই বিভাগটি একটি নেটিভ Cloud Firestore ডেটাটাইপকে কভার করে না, তবে কাস্টম এনকোডার এবং ডিকোডারগুলি আপনার Cloud Firestore অ্যাপগুলিতে ব্যাপকভাবে কার্যকর।

"কিভাবে আমি রঙের ম্যাপ করতে পারি" ডেভেলপারের সবচেয়ে বেশি জিজ্ঞাসিত প্রশ্নগুলির মধ্যে একটি, শুধুমাত্র Cloud Firestore জন্য নয়, সুইফট এবং JSON-এর মধ্যে ম্যাপিংয়ের জন্যও৷ সেখানে প্রচুর সমাধান রয়েছে, তবে তাদের বেশিরভাগই JSON-এ ফোকাস করে এবং প্রায় সবকটিই এর আরজিবি উপাদানগুলির সমন্বয়ে গঠিত একটি নেস্টেড অভিধান হিসাবে রঙগুলিকে ম্যাপ করে।

মনে হচ্ছে একটি ভাল, সহজ সমাধান হওয়া উচিত। কেন আমরা ওয়েব রঙ ব্যবহার করি না (অথবা, আরও নির্দিষ্ট হতে, CSS হেক্স রঙের স্বরলিপি) — এগুলি ব্যবহার করা সহজ (মূলত কেবল একটি স্ট্রিং), এবং এমনকি তারা স্বচ্ছতা সমর্থন করে!

একটি সুইফ্ট Color এর হেক্স মানের সাথে মানচিত্র করতে সক্ষম হতে, আমাদের একটি সুইফট এক্সটেনশন তৈরি করতে হবে যা Codable-কে 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)
  }

}

decoder.singleValueContainer() ব্যবহার করে, আমরা RGBA উপাদানগুলিকে নেস্ট না করেই একটি String এর Color সমতুল্য ডিকোড করতে পারি। এছাড়াও, আপনি এই মানগুলিকে প্রথমে রূপান্তর না করেই আপনার অ্যাপের ওয়েব UI-তে ব্যবহার করতে পারেন!

এটির সাহায্যে, আমরা ম্যাপিং ট্যাগের জন্য কোড আপডেট করতে পারি, আমাদের অ্যাপের UI কোডে ম্যানুয়ালি ম্যাপ করার পরিবর্তে ট্যাগের রঙগুলিকে সরাসরি পরিচালনা করা সহজ করে তোলে:

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

হ্যান্ডলিং ত্রুটি

উপরের কোড স্নিপেটগুলিতে আমরা ইচ্ছাকৃতভাবে ত্রুটি হ্যান্ডলিংকে ন্যূনতম রেখেছি, তবে একটি প্রোডাকশন অ্যাপে, আপনি নিশ্চিত করতে চাইবেন যে কোনও ত্রুটি সুন্দরভাবে পরিচালনা করুন।

এখানে একটি কোড স্নিপেট রয়েছে যা দেখায় যে কীভাবে আপনি যে কোনও ত্রুটির পরিস্থিতি পরিচালনা করতে পারেন তা দেখায়:

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)"
        }
      }
    }
  }
}

লাইভ আপডেটে ত্রুটি পরিচালনা করা

পূর্ববর্তী কোড স্নিপেট দেখায় কিভাবে একটি একক নথি আনার সময় ত্রুটিগুলি পরিচালনা করতে হয়। একবার ডেটা আনার পাশাপাশি, Cloud Firestore তথাকথিত স্ন্যাপশট শ্রোতাদের ব্যবহার করে আপনার অ্যাপে আপডেটগুলি সরবরাহ করতেও সমর্থন করে: আমরা একটি সংগ্রহে (বা প্রশ্ন) একটি স্ন্যাপশট শ্রোতা নিবন্ধন করতে পারি এবং Cloud Firestore যখনই সেখানে আমাদের শ্রোতাকে কল করবে একটি আপডেট হয়।

এখানে একটি কোড স্নিপেট রয়েছে যা দেখায় কিভাবে একটি স্ন্যাপশট শ্রোতা নিবন্ধন করতে হয়, কোডেবল ব্যবহার করে ডেটা ম্যাপ করতে হয় এবং যে কোনো ত্রুটি ঘটতে পারে তা পরিচালনা করতে হয়। এটি সংগ্রহে একটি নতুন নথি কীভাবে যুক্ত করতে হয় তাও দেখায়। আপনি দেখতে পাবেন, ম্যাপ করা নথিগুলিকে ধারণ করে স্থানীয় অ্যারে আপডেট করার দরকার নেই, কারণ এটি স্ন্যাপশট শ্রোতার কোড দ্বারা যত্ন নেওয়া হয়।

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

এই পোস্টে ব্যবহৃত সমস্ত কোড স্নিপেট একটি নমুনা অ্যাপ্লিকেশনের অংশ যা আপনি এই GitHub সংগ্রহস্থল থেকে ডাউনলোড করতে পারেন।

এগিয়ে যান এবং কোডেবল ব্যবহার করুন!

Swift's Codable API আপনার অ্যাপ্লিকেশন ডেটা মডেল থেকে সিরিয়ালাইজড ফরম্যাট থেকে ডেটা ম্যাপ করার একটি শক্তিশালী এবং নমনীয় উপায় প্রদান করে। এই নির্দেশিকাটিতে, আপনি দেখেছেন যে অ্যাপগুলিকে তাদের ডেটাস্টোর হিসাবে Cloud Firestore ব্যবহার করা কতটা সহজ৷

সাধারণ ডেটা টাইপ সহ একটি মৌলিক উদাহরণ থেকে শুরু করে, আমরা ধীরে ধীরে ডেটা মডেলের জটিলতা বাড়িয়েছি, আমাদের জন্য ম্যাপিং সম্পাদন করার জন্য কোডেবল এবং ফায়ারবেসের বাস্তবায়নের উপর নির্ভর করতে সক্ষম হয়েছি।

কোডেবল সম্পর্কে আরও বিশদ বিবরণের জন্য, আমি নিম্নলিখিত সংস্থানগুলির সুপারিশ করছি:

যদিও আমরা Cloud Firestore ডকুমেন্ট ম্যাপ করার জন্য একটি বিস্তৃত নির্দেশিকা কম্পাইল করার জন্য যথাসাধ্য চেষ্টা করেছি, এটি সম্পূর্ণ নয় এবং আপনি আপনার প্রকারগুলিকে ম্যাপ করার জন্য অন্যান্য কৌশলগুলি ব্যবহার করতে পারেন৷ নীচের প্রতিক্রিয়া পাঠান বোতামটি ব্যবহার করে, আপনি অন্য ধরনের Cloud Firestore ডেটা ম্যাপ করার জন্য বা Swift-এ ডেটা উপস্থাপন করার জন্য কী কৌশলগুলি ব্যবহার করেন তা আমাদের জানান৷

Cloud Firestore কোডেবল সাপোর্ট ব্যবহার না করার কোন কারণ নেই।