I have developed a hybrid mobile app that has been running on IOS, Android and Chrome reasonably well for over 5 years. My app uses an indexedDB database to maintain the state of the app and save it between restarts, however there are some cases where IOS can clear the indexedDB particularly if the client device is running low on memory.
I would like some help on how to maintain a parallel copy of the indexedDB using the native SQL database on the IOS device, in order to increase the resilience of my app, based on the hopeful expectation that IOS will not arbitrarily decide to clear an internal SQL database belonging to the app.
I understand how to communicate in both directions between Objective C and Javascript but I have not the foggiest idea in Objective C how to:
Create a SQL database in the app file system
Choose where to place the SQL database in the app file system
How to write a new key value pair to the database
Overwrite an existing key value pair in the database
Read back all key value pairs from the database (I expect there could be up to 1000 of these in practice) and pass these efficiently back to the javascript code.
Clear the database and start again.
Any pointers to useful resources on how to achieve this or better still coded examples would be most appreciated.
I would just like to add that I am not using Cordova or any other similar app development environment, so please do not suggest a SQLite plugin that is part of a bigger environment. I have got to where I am with a bespoke coding approach and I would like to keep it that way.
I have explored the idea of paralleling up each indexedDB save with a write to a file in the IOS App Documents Folder with the hope that this is less likely to be wiped than the Caches Folder.
Following each indexedDB setItem with a key-value-pair I pass a request to the objective-C IOS app code to create a text file in a sub folder created with the App Documents Folder, with the name 'key'.txt and the contents set to value.
Following each indexedDB removeItem, I pass a request to the objective-C IOS app code to delete the corresponding text file 'key'.txt.
Following each indexedDB clearAll, I delete the entire sub folder created above.
Now when the app starts up and discovers an empty localForage database, I pass a request to the objective-C IOS app code to test if the sub-folder with key-items is there or not.
If it is not, then this is a fresh install of the app and continue as normal for such a case.
if it is, then this is a case of the indexedDB database having been deleted.
In such case, I request the objective-C IOS app code to return the set of keys, by examining the contents of the folder created above and stripping off the .txt bit and in the case of the IOS Simulator ignoring the DS_Store file.
One by one I then request the contents of each key file and load them back into the previously empty localForage database and when this has been done I can continue as if it had not been deleted.
I found that it is necessary to use a zero duration timeout in the javascript before requesting each value to prevent call stack exceeded errors when restoring large databases.
This approach seems to work and I can test this any time by using the Safari Developer Resources tab actions to clear the database and then manually restarting the app. Using the same tab you can watch the indexedDB database being repopulated.
Due to the size of my database, I actually created a set of sub folders of different types of key, so that I could choose the order in which the database items were restored, especially as my app is often brought back to life in the background following a significant location change and in such cases there is an imposed maximum time limit on how long the app is given to do such a recovery. This refinement is of course optional and only needed for large databases.
The following notes are for anyone who wants to try this approach and assumes using Objective C in XCode 10.1
Use NSHomeDirectory() and stringByAppendingPathComponent #"Documents" to get the Documents folder.
Use stringByAppendingPathComponent to create a sub folder path for the keys sub folder.
Use fileExistsAtPath to check whether the keys sub folder exists already
Use createDirectoryAtPath if it does not.
When saving or changing items in indexedDB use the stringByAppendingPathComponent to create the key file name path e.g. Base.txt for a key of 'Base'.
Use fileHandleforWritingAtPath to get the fileHandle for a file
if fileHandle does not exist then need to create it using writeToFile to create the 'key' file
if fileHandle does exist then truncateFileAtOffseyt:0 (important) to clear it and then use writeData to create a new version of the 'key' file.
In both of the above specify UTF8 encoding.
When removing items from indexedDB do the same to get the key file path and then use removeItemAtPath.
The device can be cleared by removing the entire sub folder using removeItemAtPath.
The restore process uses contentsOfDirectoryAtPath to read the set of keys in the sub folder.
The restore process for each item uses stringWithContentsOfFile to read data files and return the contents enclosed in quotes using a call to stringByEvaluatingJavaScriptFromString
Hope this helps.
Related
Sorry for my english.
I have developed a mobile application that needs its own local data store. I chose Realm as the database management system. In the process of studying the Realm documentation I had no problems with the database design, it's normalization, the CRUD-operations and everything that is related directly to the code.
I was confused following. I test my application in the simulator. I did not create the local database. I’ve just done it as it is written in the documentation of Realm. Realm created by itself its database and I did not specify any settings. The location of my file with database is given below:
/Users/macbookpro/Library/Developer/CoreSimulator/Devices//EFECD945-285C-494F-8C1B-950D9AA05147/data/Containers/Data/Application/8D71FF0A-60D2-4875-96BB-36955E80D505/Documents/default.realm.
I quite inconvenient to apply each time this path for the analyzing of records in the database. The file is stored at this path is inaccessible for Realm Browser, because I need root privileges. I have seen that people store their .realm-files in project directory. Answer me please - how can I do it? Would it be the right alternative?
I have repeatedly seen the code, where paths to the database file appeared. For example, here: https://github.com/pietbrauer/CarthageRealmUploadFailureExample/blob/master/Carthage.checkout/realm-cocoa/examples/ios/swift/Migration/AppDelegate.swift
I'm not sure if it's the right direction.
As a result, I want to:
1) have on hand a database file and be able to quickly refer to it;
2) know the algorithm - how to set own path to the Realm-database
file;
3) know what realm configuration must be on a real device, to
ensure security and data integrity.
Thank you very much to all!
When you don't specify a path for the default Realm in your app, Realm will automatically create one named default.realm in the Documents directory of your app. On apps running in the iOS Simulator, this will save the Realm file in the appropriate Simulator folder, but you are correct in that it's not very intuitive to find it.
In response to your questions:
There is a cool utility called SimPholders that lets you inspect the Documents folders of apps in the iOS Simulator. This is the best way to get at any Realm files the Simulator has generated very quickly.
When creating an instance of a Realm object, you can supply a Configuration object to customise it. You can explicitly set the file path of the Realm file by setting the path property of that Configuration object.
iOS supplies an automatic layer of file encryption on disk, that makes files unable to be read when the device is locked. If you want additional security, then it's possible to set the encryptionKey property of a Realm Configuration object to have a Realm itself encrypt that Realm file on disk.
I hope that helped! Let me know if you need any more clarification!
1) and 2) does have very small effect on security and data integrity
3) if you are worry about security of you data, use Realm-level encryption like here
I have a pretty strong background in C++ and am making the switch to Objective-C to try to make an iPhone app. A main component of my app will be a database.
1) Is there a way to update the database of the app without requiring users to update to a new version from the App Store?
2) If there is, where & how do you pull the data into your app? Do you have to pull it from a website?
2a) Is there a way to make a master copy of the app that I can make data changes in and then have that copy update all other versions of the app?
If my database contains info that is only updated by me, do I need to store each piece of data as an object?
Ex: One "object" may have a name, a type, a number, and a picture
names, and types will overlap but numbers and pictures will be unique
Thanks again.
Yes, copy the datdabase (SQLite) from the application bundle to the documents folder. For an update, download over top the old one. Also, you can use something simpler than a database like an NSDictionary stored in a .plist file.
Yes, from a web server. NSDictionary has methods that let you download directly from a URL (dictionaryWithContentsOfURL). So does NSData.
Why not use third party tools to edit data? You can store a NSDictionary in a .plist file and edit it there, then upload it to the web server.
I am doing something with core data. Since the original sqlite file contains the data in the application. I decide to do the following:
Separate the reading and writing action. Make all the reading from one data source of one sqlite file and make all the writing in another file.
Since there are some rules in the directory in iOS application, I plan to copy all the reading data from bundle to the cache directory and put the writing data in the document directory.
The question is that, is it possible to use the reading file in the bundle resource directly. Which means I don't have to copy it into the cache file and that will save some space for the device.
Or you guys have any other better idea, please tell me.
I put the write data into the document because the file in it can be backuped by icloud, which could act as a feature in my program.
You ask if what you want to do is a good idea, but first you need to think through some possible pitfalls.
I suppose the file in your bundle was also created with Core Data, and it has a MOM. The new file you write - it probably uses the same MOM. What will you do if you ever need to update the MOM? The file on iCloud will be say version 1, and maybe the users iPhone uses Version 1, but your new version 2 is loaded onto the users iPad. Now what? The ipad should not update the repository as that would make the iPhone fail when it tries to use the data - the ipad has no way of knowing if all other devices have updated or not.
My app uses Core Data and I want some default entries to be inside.
What's best practices of how to do that?
If you're already loading the pre-load data via a temporary routine for testing in your current code there's no reason you can't use the sqlite file it creates in the simulator's directory (no need to write a separate Mac app).
If you're not already filling that db you can still write an iOS app that does it. Odds are you've already written the methods for adding data to your store so you can use them to import the pre-load data as well.
Either way you'd grab the sqlite file from the simulator's directory and add it to your app's bundle; on first launch you'll copy it into the appropriate place in the app's directory before pointing Core Data to it. If it's really large the downside is that there will be a copy in the bundle and another on disk, but there's not much you can do about that other than grabbing the data over the network.
As others have suggested, if the amount of data is small you can just import it at first launch, using the methods you've already written for adding data as part of the normal app's workflow.
See the CoreDataBooks example, which has sample code for copying a database at first launch.
EDIT: I've created a Core Data framework (read about it here: http://bikepress.org/?p=1120) that includes this feature.
I would just create a database and put add it to my target so that Xcode copies it into the app bundle. At the first launch just copy it from the app bundle to eg. the documents directory or wherever your app expects the database.
There is Core Data Editor at the app store. Alternatively you could build your own simple mac app just for this particular DB and manage it from there. If the amount of default entries is small, then you're better off storing it in a plist or something and loading it into DB after the first launch.
In iOS 5, my app was rejected if I put a database file into resource bundle. So, I have to download the database from internet instead.
I need to store some data files for my blackberry app. These are usually small png files that I download and store locally for performance reasons. Also I need to store an xml file locally.
My question is where are these files supposed to be saved on a blackberry? Is there such a thing as an application's home folder or settings folder in the blackberry filesystem?
What would be the path to such a folder?
This is for blackberry os 4.7 or later.
Thanks!
If it's not a huge amount of data (and by the sounds of it, it's not), take a look at the PersistentStore mechanism. You can store many types of data including native types (String, Integer, etc.) and even byte[] data (for images) using PersistentContent. The nice thing about PersistentStore is that it doesn't require any sort of filesystem access -- it doesn't leave files hanging around -- and if you include a custom class in the persistent store for your app (even a simple subclass of an existing persistible class such as Hashtable), it will automatically delete your persisted data if the app is deleted.
There's no official home folder for your application. In blackberry you can basically read/write just about anything/anywhere (well, you might get a SecurityException/IOException if you'll try do change some files).
You can write to the SDCard/Internal memory using the paths described here.
If you're worried about someone seeing and altering your data there's not much you can do except setting your files and directories as hidden using FileConnection.setHidden(true) but this is very lame since they can still be seen even from the native BlackBerry file browser if the user chooses to show hidden files from the menu.
Edit: You could of course encrypt/decrypt your data but this won't prevent someone from deleting it.