mergesort / Boutique

✨ A magical persistence library (and so much more) for state-driven iOS and Mac apps ✨
https://build.ms/boutique/docs
MIT License
899 stars 43 forks source link

insert() does not perform expected updates to existing object. #44

Closed sebanitu closed 1 year ago

sebanitu commented 1 year ago

Hi! We've recently considered switching to Boutique as a persistence solution using the SQLite Storage Engine.

The main rationale behind using Boutique is to persist some Notification objects and also mutate them and save those changes for later use hence attempting to preserve some sort of state.

Unfortunately we are facing an issue in which, when we are mutating the object and re-inserting it by calling the Store.insert() method, the value is not "updated" and when closing and re-opening the app the updated objects have their initial state again. This does not happen with any other Store operation, i.e. .remove(), and the initial .insert() of the objects - just the subsequent insert.

Here's the code we are currently using:

` enum NotificationState { case delayedForLater case accepted case initial }

final class Notification: Codable, Equatable, Identifiable {
    var index: Int
    var subtitle: String?
    var title: String?
    var state: NotificationState = .initial /// This is modified locally based on the user interaction with the alert
    var id: String

    private enum CodingKeys: String, CodingKey {
        case index = "index"
        case subtitle = "subtitle"
        case title = "title"
        case id = "orderId"
    }

    static func ==(lhs: Notification, rhs: Notification) -> Bool {
        return lhs.id == rhs.id
    }

    func setState(_ state: HandoffNotificationInteractionState) {
        self.state = state
    }
}

final class NotificationManager {
    private let kCurrentNotifications = "CurrentNotifications"

    private lazy var currentNotificationsStore = Store<Notification>(
        storage: SQLiteStorageEngine.default(appendingPath: kCurrentNotifications),
        cacheIdentifier: \.id
    )

    public func updateNotificationInteractionSate(id: String, state: NotificationState) {
        Task { [weak self] in
            guard let self = self else { return }
            let currentNotifications = await self.currentNotificationsStore.items
            guard let notificationToUpdate = currentNotifications.first(where: {$0.id == id}) else { return }
            notificationToUpdate.setState(state)
            try await self.currentNotificationsStore.insert(notificationToUpdate)
        }
    }

}`

Please note that any typos are most likely due to my attempts to obfuscate the code a little bit.

Thank you for any assistance in advance!

mergesort commented 1 year ago

Hey there @sebanitu, thanks for giving Boutique a shot! I don't see anything wrong with the updating code in isolation here, it's actually pretty similar to some code I have working in a few apps. You can find a relevant snippet here, we're mostly following the same patterns.

The setTags function is most similar to your updateNotificationInteractionSate function, it's what I use to make updates to the Store. There are a few small differences in our code, for example I'm using a @Stored property, but it should work the same as accessing the Store directly. I'm also annotating functions with @MainActor, I'm not sure if you need to do that but just something to keep in mind depending on the context.

The biggest difference though, which I suspect may be the cause of your issue, is that you're creating a lazy currentNotificationsStore. The Store being a computed lazy property means that it will be once and only once, so that makes me think it subsequent inserts may not work.

Now I haven't tested it out because I'm answering this on mobile, but I'm running the latest version of Boutique and have an app using the latest commit from main on production, without experiencing this behavior. If changing lazy to static let doesn't work, is there any more information about the callers of this code that you could possibly share with me?

sebanitu commented 1 year ago

Hi Joe! Thank you very much for the quick reply. We've tried to switch from using a lazy var to a constant and / or using the @MainActor annotation but the result is the same.

I also took a look at the Boutique performInsert() method and it seems that it is not called with an existingItemsStrategy (screenshot attached). If you need a more concrete example of our implementation, is there a way we can share some code with you in a more private manner? Screenshot 2023-01-18 at 16 14 26

Thanks in advance and wish you an awesome day!

sebanitu commented 1 year ago

Hi Joe! Thank you very much for the quick reply. We've tried to switch from using a lazy var to a constant and / or using the @MainActor annotation but the result is the same.

I also took a look at the Boutique performInsert() method and it seems that it is not called with an existingItemsStrategy (screenshot attached). If you need a more concrete example of our implementation, is there a way we can share some code with you in a more private manner?

Screenshot 2023-01-18 at 16 14 26

Thanks in advance and wish you an awesome day!

After further investigating it seems that the issue might be on our end due to some weird encoding issue. Just wanted to post this here so I don't send you on a wild goose chase. If we fix the issue, I will close this ticket as well.

mergesort commented 1 year ago

Really appreciate the update @sebanitu, and I hope y'all are able to figure out the issue! To keep things tidy on my end I'll close out this issue, but if it ends up being something related to Boutique please feel free to re-open the issue.