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

Popular posts from this blog

Using Core ML with SwiftUI: Build an AI-Powered App

Dependency Injection in iOS with SwiftUI

Infinite Scrolling in SwiftUI with Real-Time Pagination