Offline Data Caching in SwiftUI: API Integration with Local Database (Realm)
In today’s mobile-first world, users expect your app to work even without an internet connection. Whether your app shows product listings, news feeds, or profile data, caching API responses locally is a must-have. In this blog post, we’ll explore how to:
✅ Fetch data from an API
✅ Save it to a local database (using Realm)
✅ Detect internet connectivity
✅ Display data from cache when offline
All using SwiftUI 🚀
🧱 Prerequisites
- Basic knowledge of SwiftUI
- Xcode installed (15+ recommended)
- Add RealmSwift using Swift Package Manager
🧩 Project Architecture
+------------------------+
| Network Layer (API) |
+------------------------+
|
v
+------------------------+
| Local DB (RealmSwift) |
+------------------------+
|
v
+------------------------+
| SwiftUI UI Layer |
+------------------------+
🌐 Step 1: Setup Realm Models
We’ll model the API data and create a Realm version of it.
API Model
struct Product: Codable {
let id: Int
let name: String
let price: Double
}
Realm Object
import RealmSwift
class ProductObject: Object, ObjectKeyIdentifiable {
@Persisted(primaryKey: true) var id: Int
@Persisted var name: String
@Persisted var price: Double
}
🔌 Step 2: Network Manager
Use URLSession
to fetch data and store it.
class NetworkManager {
static let shared = NetworkManager()
func fetchProducts(completion: @escaping ([Product]) -> Void) {
guard let url = URL(string: "https://api.example.com/products") else { return }
URLSession.shared.dataTask(with: url) { data, _, _ in
guard let data = data else { return }
do {
let products = try JSONDecoder().decode([Product].self, from: data)
completion(products)
} catch {
print("Decoding failed: \(error)")
}
}.resume()
}
}
🗂 Step 3: Save and Load from Realm
class ProductDBManager {
let realm = try! Realm()
func save(products: [Product]) {
try! realm.write {
for p in products {
let obj = ProductObject()
obj.id = p.id
obj.name = p.name
obj.price = p.price
realm.add(obj, update: .modified)
}
}
}
func getAll() -> [ProductObject] {
return Array(realm.objects(ProductObject.self))
}
}
📡 Step 4: Monitor Internet Connection
Use Apple’s Network framework:
import Network
class NetworkMonitor: ObservableObject {
private var monitor = NWPathMonitor()
private let queue = DispatchQueue(label: "InternetMonitor")
@Published var isConnected = true
init() {
monitor.pathUpdateHandler = { path in
DispatchQueue.main.async {
self.isConnected = (path.status == .satisfied)
}
}
monitor.start(queue: queue)
}
}
🧠 Step 5: ViewModel Logic
class ProductViewModel: ObservableObject {
@Published var products: [ProductObject] = []
let network = NetworkManager.shared
let db = ProductDBManager()
func loadData(isConnected: Bool) {
if isConnected {
network.fetchProducts { [weak self] result in
DispatchQueue.main.async {
self?.db.save(products: result)
self?.products = self?.db.getAll() ?? []
}
}
} else {
products = db.getAll()
}
}
}
🖼️ Step 6: SwiftUI View
struct ProductListView: View {
@StateObject private var viewModel = ProductViewModel()
@StateObject private var monitor = NetworkMonitor()
var body: some View {
NavigationView {
List(viewModel.products) { product in
VStack(alignment: .leading) {
Text(product.name).font(.headline)
Text("₹\(product.price, specifier: "%.2f")")
.foregroundColor(.secondary)
}
}
.navigationTitle("Products")
.onAppear {
viewModel.loadData(isConnected: monitor.isConnected)
}
}
}
}
🎯 Final Output
With this setup:
- When the user opens the app:
- If online, fetch from API → save to Realm → show in UI
- If offline, load directly from Realm and show cached data
📌 Conclusion
Offline support improves reliability and user trust. By combining SwiftUI + URLSession + Realm + Network framework, we ensure a seamless experience with or without internet.
Comments
Post a Comment