Skip to content

Instantly share code, notes, and snippets.

@mattrighetti
Last active March 18, 2024 09:32
Show Gist options
  • Save mattrighetti/4db0c5266fe8717509ab9048895461ba to your computer and use it in GitHub Desktop.
Save mattrighetti/4db0c5266fe8717509ab9048895461ba to your computer and use it in GitHub Desktop.

SQLite on iOS

This is the code for the article here

//
//  SQLiteIntroApp.swift
//  SQLiteIntro
//

import SwiftUI
import FMDB

struct User: Hashable, Decodable {
    let username: String
    let age: Int
    
    init(username: String, age: Int) {
        self.username = username
        self.age = age
    }
    
    init?(from result: FMResultSet) {
        if let username = result.string(forColumn: "username") {
            self.username = username
            self.age = Int(result.int(forColumn: "age"))
        } else {
            return nil
        }
    }
    
    private enum CodingKeys : String, CodingKey {
        case username = "first_name"
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        username = try container.decode(String.self, forKey: .username)
        age = Int.random(in: 1..<100)
    }
}

final class DataWrapper: ObservableObject {
    private let db: FMDatabase
    
    @Published var users = [User]()
    
    init(fileName: String = "test") {
        let fileURL = try! FileManager.default
            .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
            .appendingPathComponent("\(fileName).sqlite")
        
        let db = FMDatabase(url: fileURL)
        
        guard db.open() else {
            fatalError("Unable to open database")
        }
        
        do {
            try db.executeUpdate("create table if not exists users(username varchar(255) primary key, age integer)", values: nil)
        } catch {
            fatalError("cannot execute query")
        }
        
        self.db = db
        
        users = getAllUsers()
    }
    
    func insert(_ user: User) {
        do {
            try db.executeUpdate(
                """
                insert into users (username, age)
                values (?, ?)
                """,
                values: [user.username, user.age]
            )
            users.append(user)
        } catch {
            fatalError("cannot insert user: \(error)")
        }
    }
    
    func getAllUsers() -> [User] {
        var users = [User]()
        do {
            let result = try db.executeQuery("select username, age from users", values: nil)
            while result.next() {
                if let user = User(from: result) {
                    users.append(user)
                }
            }
            return users
        } catch {
            return users
        }
    }
    
    func getUser(with username: String) -> User? {
        var user: User? = nil
        do {
            let result = try db.executeQuery("select username, age from users where username = ?", values: [username])
            while result.next() {
                user = User(from: result)
            }
            return user
        } catch {
            return user
        }
    }
}

@main
struct SQLiteIntroApp: App {
    var db = DataWrapper()
    
    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(db)
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var db: DataWrapper
    
    var body: some View {
        NavigationView {
            List(db.users, id: \.self) { user in
                HStack {
                    Text(user.username)
                    Spacer()
                    Text("\(user.age)")
                }
            }
            
            .navigationTitle("Users")
            .toolbar {
                ToolbarItem(id: "plus", placement: .navigationBarTrailing, showsByDefault: true) {
                    Button(action: {
                        createRandomUser()
                    }, label: {
                        Image(systemName: "plus")
                    })
                }
            }
        }
    }
    
    private func createRandomUser() {
        let url = URL(string: "https://random-data-api.com/api/name/random_name")!
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data else {
                fatalError("No data")
            }
            
            DispatchQueue.main.async {
                let user = try! JSONDecoder().decode(User.self, from: data)
                db.insert(user)
            }
        }
        task.resume()
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment