Archive
Copy Prepopulated SQLite db with Phonegap
If you have an app that needs to access a large amount of offline data, say >2Mb, then you pass what’s sensible to include a json file included in the www folder of a phonegap project, and you need to use a heftier tool, such as SQLite.
One of the first problems you hit, is the fact that Phonegap SQLite plugins such as PGSqlite or lite4cordova require you to have the database file in the /Documents folder of your app, but you can only put the file in the /www folder using xcode.
Therefore, you have to write some ios native code to copy the file from the /www folder to the /Documents folder.
So, in AppDelegate.m, scroll to the function didFinishLaunchingWithOptions
And add the following code :
NSLog(@”I’ve started”);
NSString *src = [NSBundle.mainBundle.bundlePath stringByAppendingPathComponent:@”www/dict3.db”];
NSLog(@”%@”,src);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *dest = [documentsDirectory stringByAppendingPathComponent:@”dict3.db”];
NSLog(@”%@”,dest);
NSFileManager *manager = [NSFileManager defaultManager];
NSError *error;
[manager copyItemAtPath:src toPath:dest error:&error];
if(error)
{
NSLog(@”%@”,[error localizedDescription]);
}
What this does, is that it gets the path to the source file, in my case /www/dict3.db, and a reference to the destination location, which is /Documents/dict3.db then uses NSFileManager to copy the file from one location to another.
Now, that would be all fine, apart from a nasty, that comes in the form of iCloud. It breaks apple’s data storage terms if you put large amounts of data in the Documents folder, that isn’t user-generated. Since apple will try to back this up on the iCloud, and it shouldn’t need to.
So, with a function that I unashamedly took from Stack Overflow (Sorry stack overflow, I still appreciate the trip to San Francisco that you sent me on!)…
– (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
const char* filePath = [[URL path] fileSystemRepresentation];
const char* attrName = “com.apple.MobileBackup”;
if (&NSURLIsExcludedFromBackupKey == nil) {
// iOS 5.0.1 and lower
u_int8_t attrValue = 1;
int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
return result == 0;
} else {
// First try and remove the extended attribute if it is present
int result = getxattr(filePath, attrName, NULL, sizeof(u_int8_t), 0, 0);
if (result != -1) {
// The attribute exists, we need to remove it
int removeResult = removexattr(filePath, attrName, 0);
if (removeResult == 0) {
NSLog(@”Removed extended attribute on file %@”, URL);
}
}// Set the new key
return [URL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}
then add the call it like so:
// Anti-iCloud.
NSURL *destUrl = [NSURL fileURLWithPath:dest];
NSLog(@”%@”,destUrl);
[self addSkipBackupAttributeToItemAtURL:destUrl];NSLog(@”Good to go!”);
You can use terminal to navigate to the paths shown in the console output, to see that the file, has in fact been copied.
Then, we can open up index.html, and write some JavaScript to see that the database is working.
document.addEventListener(“deviceready”, init, false);
function init()
{
console.log(“Device Ready.”);
var db = window.sqlitePlugin.openDatabase({name: “dict3.db”, bgType:1});
console.log(“Database open”);
db.transaction(function(tx){
console.log(“Transaction started”);
tx.executeSql(“select ru from dictionary where word=’HELLO’;”,[], function(tx,res){
console.log(res.rows.item(0).ru);
});
});
}
This opens the database using the sqlLitePlugin, creates a transaction, then runs a simple select against the database. Obviously, your SQL statement will be different from mine.
This took me hours to figure out, so I hope it comes in useful to someone!
Error Domain=NSCocoaErrorDomain Code=256
I was working on a proof of concept app for Apple APS notifications, and happy to have the basics working. The next step I had was to send the device token to my server, so that it could be stored in a database, and matched up with a user account.
So, I wrote a simple ASPX page that took in the querystring value “token” and stored it in the database (10 minutes work). then I wanted to write some Objective C code, to add to the didRegisterForRemoteNotificationsWithDeviceToken event so that it could call my URL, with the assigned device token.
A simple way of making a Syncronous HTTP request in Objective C is
NSError* error; // this is not required, but good to have
NSString* apiUrlStr = [NSString stringWithFormat:@”http://dev.testsite.com/test.json”%5D; // First create an NSString with the link that you need to query:
NSURL* apiUrl = [NSURL URLWithString:apiUrlStr]; // Convert the String to an NSURL.
NSString *apiResponse = [NSString stringWithContentsOfURL:apiUrl encoding:NSASCIIStringEncoding error:&error];
But, as soon as I tried this:
NSString* apiUrlStr = [NSString stringWithFormat:@”http://www.website.com/aps/notify.aspx?token=%@”,deviceToken];
I got this error in the console window
013-10-17 18:39:27.975 PushChat[136:307] My token is: <5214df32 2acfa9eb 57aafaa1 29417e30 f76a2c3d e3591538 975e5623 e091af36>
2013-10-17 18:39:28.048 PushChat[136:307] Url called is: http://www.website.com/aps/notify.aspx?token=<5214df32 2acfa9eb 57aafaa1 29417e30 f76a2c3d e3591538 975e5623 e091af36>
2013-10-17 18:39:28.191 PushChat[136:307] Api response: (null)
2013-10-17 18:39:28.255 PushChat[136:307] Any Error: Error Domain=NSCocoaErrorDomain Code=256 “The operation couldn’t be completed. (Cocoa error 256.)” UserInfo=0x144df0 {}
The simple solutions offered online were
A. Your phone has no Wifi connection. (DUH)
B. If connecting to HTTPS:// the certificate may be bad.
Neither were the case, so, applying some logic, I thought that perhaps it didn’t like the non-url-encoded querystring, with spaces and angle brackets. Which, ironically, needed to be stripped off at the server anyway, so I added the code:
NSString* strToken = [NSString stringWithFormat:@”%@”, deviceToken];
strToken = [strToken stringByReplacingOccurrencesOfString:@” ” withString:@””];
strToken = [strToken stringByReplacingOccurrencesOfString:@”<” withString:@””];
strToken = [strToken stringByReplacingOccurrencesOfString:@”>” withString:@””];
And, it worked perfectly!
My first bit of real problem solving in Objective C. Very happy 🙂
Installing previous iOS SDKs for XCode
Everybody wants to program with the latest and greatest iOS SDK don’t they?, or, perhaps you want to support the millions of people still using old iOS devices? Dust off that old iPhone 3G, and here is how to manually install a specific older version of an iOS SDK without re-installing XCode.
1. Download XCode and iOS SDK 4.2 from https://developer.apple.com/downloads/index.action
2. Mount the DMG by double clicking on it
3. Open a terminal window and navigate to /Volumes/XCode and iOS SDK/Packages/
4. Copy the *.pkg files to a folder on your desktop (cp *.pkg /users/you/Desktop/sdk)
5. Navigate to your new folder /users/you/Desktop/sdk
6. Unarchive the pkg file for the required iOS version, using xar -xf iPhoneOSxxx.pkg
7. Double click on the Payload file to unzip it.
8. Navigate to the iPhoneOS folder, and copy this to /Developer/Platforms/iPhoneOS.platform/Developer/SDKS/
9. Restart XCode
10. Select your new iOS version under Base SDK in project settings.
Open Map from BlackBerry Webworks
I do find that the BlackBerry invocation framework poorly documented, and confusing, since the format changed completely between the Playbook/Smartphone version and BB10 version, however, after a bit of searching and trial and error, I discovered how to open the native maps app using BlackBerry Webworks Invocation framework
blackberry.invoke.invoke({
action :”bb.action.MAP”,
type: “application/vnd.blackberry.string.address”,
data: “Paris, France” }
, onInvkeSuccess, onInvokeError);
Where onInvkeSuccess and onInvokeError are javascript functions that get called if things work, or don’t work.
This code applies to BB10 only.
Microsoft Kills Nokia’s Symbian
Dear Nokia Developer,
With the growing business opportunities available on the Asha and Windows Phone platforms, we have been reviewing our developer content programs to see how we can maximize our support to you, our developers. As a result of this review, we have decided to focus our support and investment in new content toward Asha and Windows Phone. Over the next few months we will be transitioning our active developer support away from Symbian and MeeGo.
If you have Symbian and MeeGo content in the Nokia Store, it will continue to be available for download to customers, and you will continue to receive download and revenue reports as well as payouts for downloaded content. However, starting January 1, 2014, you will no longer be able to publish any new content or update existing content for Symbian and MeeGo.
We are very excited about the opportunities available with Asha and Windows Phone, and hope that you will bring your talents to these platforms. We believe that these changes will help improve our ability to support you as you develop fantastic apps for your customers.
Best regards,
The Nokia Developer Team
Moving IIS sites using AppCmd
Backup Process
%windir%\System32\inetsrv>appcmd list apppool /config /xml >C:\temp\apppools.xml
%windir%\System32\inetsrv>appcmd list sites /config /xml >C:\temp\sites.xml
… And all your website files
Zip up all these files, and move them to the destination server.
Restore Process
… Restore all your website files from the zip file.
Then Edit the apppools.xml file, and comment out any appPools that are already present in the destination server, for example
“DefaultAppPool” etc.
Then … Import App Pools using:
%windir%\system32\inetsrv\appcmd add apppool /in < c:\apppools.xml
Step 2.
Edit the Sites.xml file, and comment out any websites that are already present in the destination server, for example “Default website” etc
Edit the sites.xml file, and change the Site.ID=”<number>” and ID=”<number>” attributes so that they are larger than the largest Site ID in IIS.
Important: If you import a site with the same site ID as an existing site, then the entire server will crash
%windir%\system32\inetsrv\appcmd add site /in < c:\sites.xml
no xaml was found at the location ‘/mainpage.xaml’
After upgrading a WP7 app to WP8 (VS 2013 RC), I got this very annoying error when trying to run the app in the emulator. “no xaml was found at the location ‘/mainpage.xaml'”.
I came across a conversation on twitter, regarding this, and saw that someone had the same problem, but said “Found it!!! Your application MUST have Invariant Language ticked as a supported language. Blog post to come :-)”
So, that narrowed my search, and I discovered a one line fix; In the AssemblyInfo.cs file, you have to change the line:
[assembly: NeutralResourcesLanguage(“en”, UltimateResourceFallbackLocation.Satellite)]
To
[assembly: NeutralResourcesLanguage(“en”)]
One line fix!. Pity the error message is not helpful at all.
Saving files locally using PhoneGap
One of the quintessential differences between an application and a web page is that applications are trusted to read and write to the hard drive on the machine it is running on. With the exception of limited space given under certain circumstances for Isolated storage (Silverlight), Cookies and Localstorage (5Mb ~ or effectively 2.5 Mb of text), web pages aren’t trusted enough to read and write to your hard drive, and rightly so.
With Apps, there is an elevated level of trust, so you can read and write files within your application “Sandbox”, i.e. space devoted to your app, and no-one else’s. With smartphones coming now with up to 64 GB of storage, this gives plenty of opportunity to download images, video and content for offline use.
I just learned this morning how to use PhoneGap’s File API, – This example applies specifically to version 2.9.0 on iOS, but it should be equally applicable to other versions, and Android.
First off, you need to get a handle to the local file system, which you can do on the device ready event like so:
var fileSystem = null;
document.addEventListener(“deviceready”, onDeviceReady, false);
function onDeviceReady()
{
console.log(“Device Ready”);
window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, function gotFS(fs)
{
console.log(“Got File System Reference”);
fileSystem = fs;
}, fail);
}
Here the variable fileSystem is declared with global scope, and I have selected Temporary storage. Temporary storage is not designed for “mission critical” or user generated content, just things that can be downloaded again, if need be. Ideal for short-term caching.
Then, I have written a handy function to store this data onto disk:
function createFileWithContent(filename, content)
{
fileSystem.root.getFile(
filename,
{create: true, exclusive: false},
function gotFileEntry(fileEntry) {
console.log(“Got File Entry Reference”);
console.log(fileEntry.toURL());
fileEntry.createWriter(
function gotFileWriter(writer) {
writer.onwriteend = function(evt) {
console.log(writer.fileName);};
writer.write(content);
}, fail);
}, fail);
}
This function writes text content to a file. You don’t worry about the path, since this is dictated by the operating system.
To read this back in, you can use simple ajax, as you would read a local file off disk.
The fail function is omitted here, but it can be a simple console.log, or something more advanced, depending on how you wish the app to respond to errors in the process of saving data.
The Search API for Shopping has been shut down
As of this morning, Google shut down its Shopping API (As was forewarned). Now, if you try to call it, https://www.googleapis.com/shopping/search/v1/public/products?key=…&country=US&q=pogo&alt=json you get the following Error 412 response:
{
“error”: {
“errors”: [
{
“domain”: “global”,
“reason”: “conditionNotMet”,
“message”: “The Search API for Shopping has been shut down”,
“locationType”: “header”,
“location”: “If-Match”
}
],
“code”: 412,
“message”: “The Search API for Shopping has been shut down”
}
}
Adieu, le Shopping API.
Console.log – Remote
If you have a PhoneGap/Cordova App, then you surely use Console.log statements to help with the debugging of your app while it is running in a simulator, or USB-attached device.
However, if your device cannot be attached to your development machine, or you cannot debug in the normal way, then this simple script lets you see Console.log output on a web browser.
Firstly, you’ll need access to a windows web-server, and upload this script:
<script language="C#" runat="server">
public static System.Collections.Generic.List<LogItem> logList = new
System.Collections.Generic.List<LogItem>();
protected void Page_Load(object sender, EventArgs e)
{
string strText = Request.QueryString["text"];
if (strText != null)
{
logList.Add(new LogItem(strText));
return;
}
Response.Write("Log Running...<hr>");
Response.Flush();
DateTime startTime = DateTime.UtcNow;
while(DateTime.UtcNow - startTime < TimeSpan.FromMinutes(1))
{
foreach(LogItem logItem in logList)
{
if (!logItem.isRead)
{
Response.Write(logItem.text + "<br>");
Response.Flush();
logItem.isRead = true;
}
}
System.Threading.Thread.Sleep(100);
}
Response.Redirect(Request.RawUrl);
}
public class LogItem
{
public LogItem(string text)
{
this.text = text;
}
public string text = "";
public bool isRead = false;
}
</script>
Let’s imagine that you put this script at http://www.yourserver.com/log.aspx
Then, you need to override the default action of console.log with this piece of javascript (Which requires JQuery)
console = {};
console.log = function(text)
{
$.get(“http://www.yourserver.com/log.aspx?text=” + encodeURIComponent(text) + “&nocache=” + Math.random());
}
This, then means that instead of writing to the console output, it makes an AJAX call to the logging script. Visiting the logging script webpage will display the text passed to console.log.
There are some limitations, such that it is only suitable for one-app-one-developer type environments, and you have to remember to remove the override for console.log before submitting the app to Google / Apple / BlackBerry …
