Search code examples
swiftuirealm

SwiftUI with MVVM + Realm database: How to create a list with elements?


I want to use a realm database in my SwiftUI app and I would like to apply the MVVM pattern. Unfortunately when I create a list with the elements in my database I get a Fatal error: Unexpectedly found nil while unwrapping an Optional value: error message

DatabaseManager:

class DatabaseManager{

    private let realm: Realm
    public static let sharedInstance = DatabaseManager()

    private init(){
        realm = try! Realm()
    } 

    func fetchData<T: Object>(type: T.Type) -> Results<T>{
        let results: Results<T> = Realm.objects(type)
        return results
    }
}

Model:

class FlashcardDeck: Object, Codable, Identifiable{
    @objc private (set) dynamic var id = NSUUID().uuidString
    @objc dynamic var title: String?
    var cards = RealmSwift.List<Flashcard>()

    convenience init(title: String?, cards: [Flashcard]){
        self.init()
        self.title = title
        self.cards.append(objectsIn: cards)
    }
    
    override class func primaryKey() -> String? {
        return "id"
    }
}

ViewModel

class FlashcardDeckViewModel: ObservableObject{
    let realm = DatabaseManager.sharedInstance
    @Published var decks: Results<FlashcardDeck>?

    public func fetchDecks(){
        decks = realm.fetchData(type: FlashcardDeck.self)
    } 
}

View

struct FlashcardDeckView: View {
    private let gridItems = [GridItem(.flexible())]
    @StateObject var viewModel = FlashcardDeckViewModel()

    var body: some View {
        NavigationView{
            ScrollView{
                LazyVGrid(columns: gridItems, spacing: 30){
                    ForEach(viewModel.decks!) { item in // <----- ERROR APPEARS HERE
                        FlashcardDeckItem(deck: item)
                    }
                }
            }
            .navigationTitle("Flashcard decks")
        }
        .onAppear{
            self.viewModel.fetchDecks()
            print(self.viewModel.cards?[0].title) // <------ prints the title of the deck! So this element exists
        }
    }
}

I'm pretty sure that my database has an element and if I try to print the name of the deck in the fetchData()function it will be displayed. I know the line ForEach(viewModel.decks!)isn't beautiful code, but this is just for testing/debugging now.


Solution

  • Include it conditionally, like

        NavigationView{
          if viewModel.decks == nil {
            Text("Loading...")
          } else {
            ScrollView{
                LazyVGrid(columns: gridItems, spacing: 30){
                    ForEach(viewModel.decks!) { item in // <----- ERROR APPEARS HERE
                        FlashcardDeckItem(deck: item)
                    }
                }
            }
            .navigationTitle("Flashcard decks")
          }
        }