I’m pre-populating an app with an existing database that’s to be shipped with the app.
My approach is similar to the one outlined here and it works for an initial database, but now I need to update that db and am having problems modifying it.
So, in my ProjectApp file, I started with the following…
init() {
do {
// checks the db project is in the app
guard let storeURL = Bundle.main.url(forResource: “project”, withExtension: "store") else {
fatalError("Failed to find project.store")
}
let fileManager = FileManager.default
let documentDirectoryURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let documentURL = documentDirectoryURL.appendingPathComponent(“project.store")
// Only copy the store from the bundle to the Documents directory if it doesn't exist
if !fileManager.fileExists(atPath: documentURL.path) {
print ("file does not exists (storeURL) ------- (documentURL)")
try fileManager.copyItem(at: storeURL, to: documentURL)
}
let config = ModelConfiguration(url: documentURL)
container = try ModelContainer(for: Project.self, Favorite.self, Quiz.self, configurations: config)
print (“Project Imported”)
} catch {
fatalError("Failed to create model container: (error)")
}
}
This pre-populates the project db when the user first opens the app for the very first time. It is ignored on subsequent openings, as the file already exists after the initial load.
The problem I’m having now though is… how do apply an updated project database… while also maintaining relationships with the Quiz and Favorite tables?
I’d tried just project to project2 and it does copy in the new table, but the relationship data is lost with Quiz and Favorite. (it also can bloat the app size)
I’ve also been trying to work with fileManager.replaceItem (this sounded promising “replaces the contents of the item at the specified URL in a manner that ensures no data loss occurs”), but I’m getting errors that say ZQUIZ already exists”
What I want is…
-
IF the user is opening the app for the first time, use the file being sent with the app (like it’s currently doing).
-
IF I’m pushing a new version of the app that has an updated Project DB, the next time a user opens the app, for the new Project DB to replace the old one, while also maintaining relationships (and cleaning up any old Project table)
-
Otherwise, if no new DB is being pushed and the user has already opened the db (thus populating the Project db), just open as normal.
Does anyone have any suggestions? Does replaceItem seem like a path I should continue exploring? The similar posts I’ve found on this site are up to 12 years old and haven’t proven very helpful.
Thanks!
6
How about using different ModelContainer
instances? Use one for the user-generated data, and a second for the data you’re supplying.
Use two different configurations, say localData
and remoteData
. You’ll need two different filenames for the containers. And you’ll have to modify your data model a bit, because relationships can’t cross containers. So instead of having quiz.project
, you’ll have to have something like quiz.projectID
and fetch Project
instances by ID.
/a/78531250/719690 goes into detail. Note his observation that you now need separate View
subclasses because a View
only knows about a single ModelContainer
.
For a similar problem that I implemented in Core Data, I defined two separate containers, one for local data and the other for a US FCC database that is updated weekly. To update, operating only in the FCC container, I first did a batch delete of all records (NSBatchDeleteRequest
), then inserted the new records one by one (there was no NSBatchUpdateRequest
at the time). Local data kept a reference to the (immutable and unique) identifier in the corresponding FCC datastore, and did a quick lookup when the FCC information was needed.
Side note: depending on the size of the Project file, it might be worth marking the copy in the bundle as a tagged on-demand resource, so that the “starter” project can be removed after it’s used.
2