Swift 4-এ চালু করা Swift-এর Codable API, সিরিয়ালাইজ করা ফর্ম্যাট থেকে Swift-এর প্রকারে ডেটা ম্যাপ করার কাজ সহজ করতে, কম্পাইলারের ক্ষমতাকে কাজে লাগাতে আমাদের সাহায্য করে।
আপনি হয়ত Codable ব্যবহার করে ওয়েব API থেকে আপনার অ্যাপের ডেটা ম্যাপ করছেন (এবং উল্টোটাও), কিন্তু এটি তার চেয়ে অনেক বেশি নমনীয়।
এই গাইডে, আমরা দেখব কীভাবে Codable ব্যবহার করে Cloud Firestore থেকে Swift টাইপে ডেটা ম্যাপ করা যায় এবং এর উল্টোটাও করা যায়।
Cloud Firestore থেকে কোনও ডকুমেন্ট ফেচ করার সময়, আপনার অ্যাপ কী/ভ্যালু পেয়ারের একটি ডিকশনারি (অথবা ডিকশনারির একটি অ্যারে, যদি আপনি একাধিক ডকুমেন্ট ফেরত দেয় এমন কোনও অপারেশন ব্যবহার করেন) পাবে।
এখন, আপনি অবশ্যই Swift-এ সরাসরি ডিকশনারি ব্যবহার করা চালিয়ে যেতে পারেন এবং সেগুলি কিছু দুর্দান্ত নমনীয়তা অফার করে যা আপনার ব্যবহারের ক্ষেত্রে ঠিক যা প্রয়োজন তা হতে পারে। তবে, এই পদ্ধতি টাইপ নিরাপত্তা প্রদান করে না এবং অ্যাট্রিবিউটের নাম ভুল বানান করে অথবা আপনার টিম গত সপ্তাহে যে নতুন ফিচার লঞ্চ করেছে তার নতুন অ্যাট্রিবিউট ম্যাপ করতে ভুলে গিয়ে সহজেই এমন বাগ তৈরি করা যায় যা ট্র্যাক করা কঠিন।
অতীতে, অনেক ডেভেলপার একটি সাধারণ ম্যাপিং লেয়ার প্রয়োগ করে এই ত্রুটিগুলি সমাধান করেছেন যা তাদের ডিকশনারিগুলিকে Swift টাইপে ম্যাপ করতে দেয়। তবে, এই ধরনের বেশিরভাগ ইমপ্লিমেন্টেশন Cloud Firestore ডকুমেন্ট এবং আপনার অ্যাপের ডেটা মডেলের সংশ্লিষ্ট ধরনের মধ্যে ম্যাপিং ম্যানুয়ালি নির্দিষ্ট করার উপর ভিত্তি করে করা হয়।
Swift-এর Codable API-এর জন্য Cloud Firestore-এর সহায়তা থাকায়, এটি অনেক সহজ হয়ে যায়:
- আপনাকে আর ম্যানুয়ালি কোনও ম্যাপিং কোড প্রয়োগ করতে হবে না।
- বিভিন্ন নামের সাথে অ্যাট্রিবিউট কীভাবে ম্যাপ করতে হয় তা নির্ধারণ করা সহজ।
- এতে Swift-এর অনেক ধরনের বিল্ট-ইন সাপোর্ট আছে।
- এবং কাস্টম টাইপ ম্যাপ করার জন্য সহায়তা যোগ করা সহজ।
- সবচেয়ে ভালো হল: সাধারণ ডেটা মডেলের জন্য আপনাকে কোনও ম্যাপিং কোড লিখতে হবে না।
ম্যাপিং ডেটা
Cloud Firestore ডকুমেন্টে ডেটা স্টোর করে যা ভ্যালুর সাথে 'কী' ম্যাপ করে। কোনও স্বতন্ত্র ডকুমেন্ট থেকে
ডেটা ফেচ করতে, আমরা DocumentSnapshot.data() কল করতে পারি, যা
ফিল্ডের নামকে Any-এ ম্যাপ করে একটি ডিকশনারি রিটার্ন করে:
func data() -> [String : Any]?.
এর অর্থ হল, প্রতিটি স্বতন্ত্র ফিল্ড অ্যাক্সেস করতে আমরা Swift-এর সাবস্ক্রিপ্ট সিনট্যাক্স ব্যবহার করতে পারি।
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 ফিল্ডের জন্য স্ট্রিং বেছে নিয়েছেন, যার ফলে
ম্যাপিং সংক্রান্ত সমস্যা খুঁজে পাওয়া কঠিন হবে। এছাড়াও, নতুন ফিল্ড যোগ করা হলে আপনাকে ম্যাপিং কোড আপডেট করতে হবে, যা বেশ কষ্টকর।
আর আমরা যেন ভুলে না যাই যে আমরা Swift-এর শক্তিশালী টাইপ সিস্টেমের সুবিধা নিচ্ছি না, যা Book-এর প্রতিটি প্রপার্টির সঠিক টাইপ জানে।
Codable কী?
Apple-এর ডকুমেন্টেশন অনুযায়ী, Codable হল "এমন একটি ধরন যা নিজেকে এক্সটার্নাল রিপ্রেজেন্টেশনে এবং তা থেকে অন্য কিছুতে কনভার্ট করতে পারে।" আসলে, Codable হল Encodable এবং Decodable প্রোটোকলের জন্য একটি টাইপ অ্যালিয়াস। এই প্রোটোকলের সাথে Swift টাইপ কনফর্ম করার মাধ্যমে, কম্পাইলার JSON-এর মতো সিরিয়ালাইজ করা ফর্ম্যাট থেকে এই ধরনের ইনস্ট্যান্স এনকোড/ডিকোড করার জন্য প্রয়োজনীয় কোড সিন্থেসাইজ করবে।
কোনও বই সম্পর্কে ডেটা স্টোর করার জন্য একটি সাধারণ ধরন এইরকম দেখতে হতে পারে:
struct Book: Codable {
var title: String
var numberOfPages: Int
var author: String
}
আপনি দেখতে পাচ্ছেন, Codable-এ টাইপ কনফর্ম করাটা ন্যূনতম আক্রমণাত্মক। আমাদের শুধু প্রোটোকলের সাথে কনফর্মেন্স যোগ করতে হয়েছে; অন্য কোনও পরিবর্তন করার প্রয়োজন হয়নি।
এটি করার পরে, আমরা এখন সহজেই একটি বইকে 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)
Codable ব্যবহার করে Cloud Firestore ডকুমেন্টে সহজ ধরনের থেকে এবং সেখানে ম্যাপিং করা
Cloud Firestore-এ সাধারণ স্ট্রিং থেকে শুরু করে নেস্টেড ম্যাপ পর্যন্ত বিভিন্ন ধরনের ডেটা ব্যবহার করা যায়। এগুলির বেশিরভাগই সরাসরি Swift-এর বিল্ট-ইন টাইপের সাথে সম্পর্কিত। আরও জটিল ডেটা টাইপ নিয়ে আলোচনা করার আগে, আসুন কিছু সহজ ডেটা টাইপ ম্যাপ করার বিষয়টি দেখে নেওয়া যাক।
Cloud Firestore ডকুমেন্ট Swift টাইপে ম্যাপ করতে, এই ধাপগুলি অনুসরণ করুন:
- আপনার প্রোজেক্টে
FirebaseFirestoreফ্রেমওয়ার্ক যোগ করেছেন কিনা তা নিশ্চিত করুন। এটি করার জন্য আপনি Swift Package Manager অথবা CocoaPods ব্যবহার করতে পারবেন। - আপনার Swift ফাইলে
FirebaseFirestoreইমপোর্ট করুন। - আপনার টাইপ
Codable-এ কনফর্ম করুন। - (ঐচ্ছিক, আপনি যদি
Listভিউতে টাইপ ব্যবহার করতে চান) আপনার টাইপে একটিidপ্রপার্টি যোগ করুন এবং@DocumentIDব্যবহার করে Cloud Firestore-কে বলুন এটি ডকুমেন্ট আইডিতে ম্যাপ করতে। আমরা নিচে এই বিষয়ে আরও বিস্তারিত আলোচনা করব। - Swift টাইপে ডকুমেন্ট রেফারেন্স ম্যাপ করতে
documentReference.data(as: )ব্যবহার করুন। - Swift টাইপ থেকে Cloud Firestore ডকুমেন্টে ডেটা ম্যাপ করতে
documentReference.setData(from: )ব্যবহার করুন। - (ঐচ্ছিক, তবে অত্যন্ত সাজেস্ট করা হয়) সঠিক সমস্যা ম্যানেজমেন্ট প্রয়োগ করুন।
আসুন, সেই অনুযায়ী আমাদের 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-এ এটি করার সবচেয়ে সহজ উপায় হল ম্যাপ ব্যবহার করা:

সংশ্লিষ্ট Swift স্ট্রাক্ট লেখার সময়, আমরা এই তথ্য ব্যবহার করতে পারি যে Cloud Firestore URL-কে সাপোর্ট করে — URL আছে এমন ফিল্ড স্টোর করার সময়, এটি স্ট্রিংয়ে কনভার্ট করা হবে এবং এর উল্টোটাও হবে:
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]
}
যেহেতু এটি যেকোনও কোডেবল টাইপের জন্য কাজ করে, তাই আমরা কাস্টম টাইপও ব্যবহার করতে পারি। ধরুন, আমরা প্রতিটি বইয়ের জন্য ট্যাগের তালিকা স্টোর করতে চাই। ট্যাগের নামের সাথে, আমরা ট্যাগের রঙও স্টোর করতে চাই, যেমন:

এইভাবে ট্যাগ স্টোর করতে, আমাদের যা করতে হবে তা হল ট্যাগ দেখানোর জন্য একটি 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]
}
ডকুমেন্ট আইডি ম্যাপিং সম্পর্কে কিছু কথা
আরও ধরনের ম্যাপিং করার আগে, আসুন ডকুমেন্ট আইডি ম্যাপিং সম্পর্কে একটু আলোচনা করা যাক।
আমরা আগের কিছু উদাহরণে @DocumentID প্রপার্টি র্যাপার ব্যবহার করেছি
যাতে আমাদের Cloud Firestore ডকুমেন্টের ডকুমেন্ট আইডিকে আমাদের Swift টাইপের
id প্রপার্টিতে ম্যাপ করা যায়। এটি একাধিক কারণে গুরুত্বপূর্ণ:
- ব্যবহারকারী লোকাল পরিবর্তন করলে, কোন ডকুমেন্ট আপডেট করতে হবে তা জানতে এটি আমাদের সাহায্য করে।
- SwiftUI-এর
List-এর জন্য এর এলিমেন্টগুলিIdentifiableহতে হবে যাতে এলিমেন্টগুলি যোগ করার সময় এদিক-ওদিক লাফিয়ে না যায়।
এটি উল্লেখ করা প্রয়োজন যে, @DocumentID হিসেবে চিহ্নিত অ্যাট্রিবিউট,
ডকুমেন্ট আবার লেখার সময় Cloud Firestore-এর এনকোডার দ্বারা এনকোড করা হবে না। এর কারণ হল, ডকুমেন্ট আইডি ডকুমেন্টের কোনও অ্যাট্রিবিউট নয় — তাই
ডকুমেন্টে এটি লিখলে ভুল হবে।
নেস্টেড টাইপ (যেমন, এই গাইডের আগের উদাহরণে Book-এ ট্যাগের অ্যারে) নিয়ে কাজ করার সময়,
@DocumentID প্রপার্টি যোগ করার প্রয়োজন হয় না: নেস্টেড প্রপার্টি হল Cloud Firestore ডকুমেন্টের অংশ এবং
এটি আলাদা ডকুমেন্ট তৈরি করে না। তাই, তাদের কোনও ডকুমেন্ট আইডি প্রয়োজন হয় না।
তারিখ ও সময়
তারিখ ও সময় ম্যানেজ করার জন্য Cloud Firestore-এ বিল্ট-ইন ডেটা টাইপ আছে এবং Codable-এর জন্য Cloud Firestore-এর সহায়তা থাকার কারণে, এগুলি ব্যবহার করা খুবই সহজ।
আসুন, এই ডকুমেন্টটি দেখে নেওয়া যাক, যা ১৮৪৩ সালে আবিষ্কৃত সমস্ত প্রোগ্রামিং ল্যাঙ্গুয়েজের জননী, Ada-কে উপস্থাপন করে:

এই ডকুমেন্ট ম্যাপ করার জন্য Swift টাইপ দেখতে এইরকম হতে পারে:
struct ProgrammingLanguage: Codable {
@DocumentID var id: String?
var name: String
var year: Date
}
@ServerTimestamp সম্পর্কে আলোচনা না করে তারিখ ও সময় সংক্রান্ত এই বিভাগটি
আমরা ছেড়ে যেতে পারি না। আপনার অ্যাপে টাইমস্ট্যাম্প নিয়ে কাজ করার সময়
এই প্রপার্টি র্যাপারটি অত্যন্ত কার্যকর।
যেকোনও ডিস্ট্রিবিউটেড সিস্টেমে, প্রতিটি সিস্টেমের ঘড়ি সব সময় সম্পূর্ণভাবে সিঙ্ক নাও হতে পারে। আপনি ভাবতে পারেন যে এটি কোনও বড় ব্যাপার নয়, তবে স্টক ট্রেড সিস্টেমের জন্য সামান্য সিঙ্ক না হওয়া ঘড়ির প্রভাব কল্পনা করুন: এমনকি এক মিলিসেকেন্ডের বিচ্যুতিও ট্রেড কার্যকর করার সময় লক্ষ লক্ষ ডলারের পার্থক্য ঘটাতে পারে।
Cloud Firestore, @ServerTimestamp দিয়ে চিহ্নিত অ্যাট্রিবিউটকে এইভাবে
ম্যানেজ করে: আপনি এটি স্টোর করার সময় (যেমন, addDocument() ব্যবহার করে) অ্যাট্রিবিউটটি nil হলে,
ডেটাবেসে লেখার সময় 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
}
Swift-এ সংশ্লিষ্ট ধরন হল CLLocationCoordinate2D এবং আমরা নিম্নলিখিত অপারেশনের মাধ্যমে
এই দুটি ধরনের মধ্যে ম্যাপ করতে পারি:
CLLocationCoordinate2D(latitude: office.location.latitude,
longitude: office.location.longitude)
ফিজিক্যাল লোকেশন অনুযায়ী ডকুমেন্ট কোয়েরি করার ব্যাপারে আরও জানতে, এই সমাধান সংক্রান্ত গাইড দেখুন।
Enums
Enums সম্ভবত Swift-এর সবচেয়ে অবমূল্যায়িত ভাষা ফিচার;
এতে যা দেখা যায় তার চেয়ে অনেক বেশি কিছু আছে। enum-এর একটি সাধারণ ব্যবহার হল
কোনও কিছুর বিচ্ছিন্ন অবস্থাকে মডেল করা। যেমন, আমরা হয়ত নিবন্ধ ম্যানেজ করার জন্য
একটি অ্যাপ লিখছি। কোনও নিবন্ধের স্ট্যাটাস ট্র্যাক করতে, আমরা হয়ত
একটি enum Status ব্যবহার করতে চাইব:
enum Status: String, Codable {
case draft
case inReview
case approved
case published
}
Cloud Firestore-এ নেটিভভাবে enum কাজ করে না (অর্থাৎ, এটি ভ্যালুর সেট এনফোর্স করতে পারে না),
তবে আমরা এখনও এই সুবিধা নিতে পারি যে enum টাইপ করা যেতে পারে এবং
কোড করা যায় এমন টাইপ বেছে নিতে পারি। এই উদাহরণে, আমরা String বেছে নিয়েছি, যার অর্থ হল
Cloud Firestore ডকুমেন্টে স্টোর করার সময়
সব enum ভ্যালু স্ট্রিংয়ে/থেকে ম্যাপ করা হবে।
এবং, যেহেতু Swift কাস্টম র ভ্যালু সাপোর্ট করে, তাই আমরা এমনকি কাস্টমাইজ করতে পারি যে কোন ভ্যালু
কোন enum কেসকে রেফার করে। যেমন, আমরা যদি Status.inReview কেস "পর্যালোচনা করা হচ্ছে" হিসেবে সেভ করার সিদ্ধান্ত নিই, তাহলে আমরা উপরের enum এইভাবে আপডেট করতে পারি:
enum Status: String, Codable {
case draft
case inReview = "in review"
case approved
case published
}
ম্যাপিং কাস্টমাইজ করা
কখনও কখনও, আমরা যে Cloud Firestoreটি ডকুমেন্ট ম্যাপ করতে চাই সেগুলির অ্যাট্রিবিউটের নাম Swift-এ আমাদের ডেটা মডেলের প্রপার্টির নামের সাথে মেলে না। যেমন, আমাদের কোনও সহকর্মী Python ডেভেলপার হতে পারেন এবং তিনি তার সব অ্যাট্রিবিউটের নামের জন্য snake_case বেছে নেওয়ার সিদ্ধান্ত নিয়েছেন।
চিন্তা করবেন না: Codable আমাদের সাহায্য করবে!
এই ধরনের ক্ষেত্রে, আমরা CodingKeys ব্যবহার করতে পারি। এটি একটি enum যা আমরা
নির্দিষ্ট অ্যাট্রিবিউট কীভাবে ম্যাপ করা হবে তা নির্দিষ্ট করতে একটি কোডেবল স্ট্রাক্টে যোগ করতে পারি।
এই ডকুমেন্টটি বিবেচনা করুন:

এই ডকুমেন্টকে 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
}
}
ডিফল্ট হিসেবে, Codable API আমাদের Swift টাইপের প্রপার্টির নাম ব্যবহার করে
Cloud Firestore ডকুমেন্টের অ্যাট্রিবিউটের নাম নির্ধারণ করবে যা আমরা
ম্যাপ করার চেষ্টা করছি। তাই, অ্যাট্রিবিউটের নাম ম্যাচ করলে, আমাদের কোডেবল টাইপে
CodingKeys যোগ করার প্রয়োজন নেই। তবে, কোনও নির্দিষ্ট ধরনের জন্য একবার CodingKeys ব্যবহার করলে, আমরা ম্যাপ করতে চাই এমন সব প্রপার্টির নাম যোগ করতে হবে।
উপরের কোড স্নিপেটে, আমরা একটি id প্রপার্টি সংজ্ঞায়িত করেছি যা আমরা SwiftUI List ভিউতে শনাক্তকারী হিসেবে ব্যবহার করতে চাইতে পারি। আমরা যদি এটি
CodingKeys-এ নির্দিষ্ট না করি, তাহলে ডেটা আনার সময় এটি ম্যাপ করা হবে না এবং এইভাবে nil হয়ে যাবে।
এর ফলে, List ভিউ প্রথম ডকুমেন্ট দিয়ে ভর্তি হয়ে যাবে।
ম্যাপিং প্রসেস চলাকালীন, সংশ্লিষ্ট CodingKeys enum-এ
কেস হিসেবে তালিকাভুক্ত নয় এমন যেকোনও প্রপার্টি উপেক্ষা করা হবে। আমরা যদি নির্দিষ্ট কিছু প্রপার্টি ম্যাপ করা থেকে বাদ দিতে চাই,
তাহলে এটি আসলে সুবিধাজনক হতে পারে।
যেমন, আমরা যদি reasonWhyILoveThis প্রপার্টিকে ম্যাপ করা থেকে বাদ দিতে চাই, তাহলে আমাদের যা করতে হবে তা হল এটিকে CodingKeys enum থেকে সরিয়ে দেওয়া:
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 ডকুমেন্টে
একটি খালি অ্যাট্রিবিউট লিখতে চাইতে পারি। Swift-এ ভ্যালু না থাকার বিষয়টি বোঝাতে ঐচ্ছিক ভ্যালুর ধারণা আছে এবং Cloud Firestore-এ null ভ্যালুও কাজ করে।
তবে, nil ভ্যালু আছে এমন ঐচ্ছিক এনকোডিংয়ের ডিফল্ট অ্যাকশন হল
সেগুলি বাদ দেওয়া। @ExplicitNull আমাদের Swift
বিকল্প এনকোড করার সময় কীভাবে ম্যানেজ করা হবে তার উপর কিছুটা নিয়ন্ত্রণ দেয়: কোনও বিকল্প প্রপার্টিকে
@ExplicitNull হিসেবে ফ্ল্যাগ করে, আমরা Cloud Firestore-কে বলতে পারি যে এই প্রপার্টিকে
ডকুমেন্টে ফাঁকা ভ্যালু সহ লিখতে, যদি এতে nil-এর ভ্যালু থাকে।
রঙের ম্যাপিংয়ের জন্য কাস্টম এনকোডার ও ডিকোডার ব্যবহার করা হচ্ছে
Codable-এর সাথে ম্যাপিং ডেটার কভারেজের শেষ বিষয় হিসেবে, আসুন কাস্টম এনকোডার এবং ডিকোডার সম্পর্কে জেনে নেওয়া যাক। এই বিভাগে নেটিভ Cloud Firestore ডেটাটাইপ কভার করা হয় না, তবে কাস্টম এনকোডার ও ডিকোডার আপনার Cloud Firestore অ্যাপে ব্যাপকভাবে উপযোগী।
"আমি কীভাবে রঙ ম্যাপ করতে পারি" হল ডেভেলপারদের সবচেয়ে বেশি জিজ্ঞাসিত প্রশ্নগুলির মধ্যে একটি, শুধু Cloud Firestore-এর জন্য নয়, Swift এবং JSON-এর মধ্যে ম্যাপিংয়ের জন্যও এটি প্রযোজ্য। There are plenty of solutions out there, but most of them focus on JSON, and almost all of them map colors as a nested dictionary composed of its RGB components.
মনে হচ্ছে, আরও ভালো ও সহজ সমাধান থাকা উচিত। আমরা কেন ওয়েব কালার ব্যবহার করি না (বা, আরও নির্দিষ্ট করে বলতে গেলে, CSS হেক্স কালার নোটেশন) — সেগুলি ব্যবহার করা সহজ (মূলত শুধু একটি স্ট্রিং) এবং সেগুলি এমনকি ট্রান্সপারেন্সিও সাপোর্ট করে!
Swift Color-কে এর হেক্স ভ্যালুতে ম্যাপ করতে, আমাদের একটি Swift এক্সটেনশন তৈরি করতে হবে যা Color-এ Codable যোগ করে।
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() ব্যবহার করে, আমরা String-কে এর
Color সমতুল্য হিসেবে ডিকোড করতে পারি, RGBA কম্পোনেন্ট নেস্ট না করেই। এছাড়াও, আপনি
প্রথমে কনভার্ট না করেই আপনার অ্যাপের ওয়েব 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 আমাদের লিসেনারকে কল করবে।
এখানে একটি কোড স্নিপেট দেওয়া হল যা দেখায় কীভাবে স্ন্যাপশট লিসেনার রেজিস্টার করতে হয়, Codable ব্যবহার করে ডেটা ম্যাপ করতে হয় এবং কোনও সমস্যা হলে তা সমাধান করতে হয়। এছাড়াও, এটি দেখায় যে কীভাবে কালেকশনে নতুন ডকুমেন্ট যোগ করতে হয়। আপনি দেখতে পাবেন যে, ম্যাপ করা ডকুমেন্ট সহ লোকাল অ্যারে আপডেট করার প্রয়োজন নেই, কারণ স্ন্যাপশট লিসেনারের কোড এটি ম্যানেজ করে।
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 রিপোজিটরি থেকে ডাউনলোড করতে পারবেন।
এগিয়ে যান এবং Codable ব্যবহার করুন!
Swift-এর Codable API, সিরিয়ালাইজ করা ফর্ম্যাট থেকে আপনার অ্যাপ্লিকেশন ডেটা মডেলে এবং সেখান থেকে সিরিয়ালাইজ করা ফর্ম্যাটে ডেটা ম্যাপ করার জন্য একটি শক্তিশালী এবং নমনীয় উপায় প্রদান করে। এই গাইডে, আপনি দেখেছেন যে Cloud Firestore-কে ডেটাস্টোর হিসেবে ব্যবহার করে এমন অ্যাপে এটি ব্যবহার করা কতটা সহজ।
সহজ ডেটা টাইপ সহ একটি প্রাথমিক উদাহরণ থেকে শুরু করে, আমরা ধীরে ধীরে ডেটা মডেলের জটিলতা বাড়িয়েছি, সব সময় ম্যাপিং করার জন্য Codable এবং Firebase-এর ইমপ্লিমেন্টেশনের উপর নির্ভর করতে পেরেছি।
Codable সম্পর্কে আরও জানতে, আমি নিম্নলিখিত রিসোর্সগুলি সাজেস্ট করছি:
- জন সানডেলের লেখা কোডেবলের প্রাথমিক বিষয় সম্পর্কে একটি সুন্দর নিবন্ধ আছে।
- আপনি যদি বই পড়তে বেশি পছন্দ করেন, তাহলে Mattt-এর Flight School Guide to Swift Codable দেখুন।
- এবং সবশেষে, ডনি ওয়ালসের কাছে Codable সম্পর্কে সম্পূর্ণ সিরিজ আছে।
ম্যাপিং Cloud Firestore ডকুমেন্টের জন্য একটি বিস্তারিত গাইড কম্পাইল করার জন্য আমরা যথাসাধ্য চেষ্টা করেছি, তবে এটি সম্পূর্ণ নয় এবং আপনার ধরন ম্যাপ করার জন্য আপনি অন্যান্য কৌশল ব্যবহার করতে পারেন। নিচের মতামত পাঠান বোতাম ব্যবহার করে, অন্যান্য ধরনের Cloud Firestore ডেটা ম্যাপ করার জন্য বা Swift-এ ডেটা দেখানোর জন্য আপনি কোন কৌশল ব্যবহার করেন তা আমাদের জানান।
Cloud Firestore-এর কোডেবল সাপোর্ট ব্যবহার না করার কোনও কারণ নেই।