Berichten met label iPad

Getting to know CouchDB – 2/x

Last time around, we were able to store the OSGi log messages into a CouchDB. Now I want to tap into the _changes API that CouchDB offers. The options are simple, connect to the ‘_changes’ document in a CouchDB database and you will be presented with a log of all changes that have occurred on the database.

http://localhost:5984/db/_changes

{"results":[
{"seq":3,"id":"_design/app","changes":[{"rev":"2-0dc1002d841221e8d8d7f325b87ffd67"}]},
{"seq":196,"id":"994cf6817a74882cecda6425e8001a96","changes":[{"rev":"193-083ec5c1e1359789eb2cf09c46097fee"}]}
,
"last_seq":196}

For each document in the database only the last change is presented. This could result in quite a lot of information. It is therefore also possible to fetch changes while applying a server-side filter. For this to work, you first need to define a filter in a design document. Mine looks like this:

function(doc, req) {
    if (doc.type == "EVENT") { return true; }
    return false;
}

and is stored (using couchapp) in a design document called ‘app’. The name of the filter is ‘events’. The filters’ meaning should obvious: pass through all documents for which the function returns ‘true’. The proper URL for accessing the changes will now be:

http://localhost:5984/db/_changes?filter=app/events

{"results":[
{"seq":196,"id":"994cf6817a74882cecda6425e8001a96","changes":[{"rev":"193-083ec5c1e1359789eb2cf09c46097fee"}]}
],
"last_seq":196}

The “last_seq” part can be used for bookkeeping: If you plan to make  multiple calls to the _changes feed (say every hour) then it is nice to be able to skip the ones you already saw during a previous call. So, the second time you call the _changes feed, you can skip all previous changes by employing the following URL:

http://localhost:5984/db/_changes?filter=app/events&since=196

{"results":[

],
"last_seq":196}

Obviously, no update has occurred since we accessed the _changes feed for the first time. If, however we now add a new document to the database (with doc.type == “EVENT”) and call the _changes feed again:

http://localhost:5984/db/_changes?filter=app/events&since=196

{"results":[
{"seq":197,"id":"f0a4a559fac65ab3cca87baf1c014fa7","changes":[{"rev":"1-c8dfffec4e08ca8db2b95f30613e213d"}]}
],
"last_seq":197}

Also, a call to “http://localhost:5984/db” will result in a JSON object, where “update_seq” provides the current update sequence. This comes in handy if you’re really not interested in previous changes:

http://localhost:5984/db

{"db_name":"db","doc_count":4,"doc_del_count":0,"update_seq":196,
"purge_seq":0,"compact_running":false,"disk_size":1871961,
"instance_start_time":"1282049630257719","disk_format_version":5,
"committed_update_seq":196}

This is all great functionality if your time granularity is in the order of hours or days. But hardly optimal if you want to be notified immediately of changes to the database. Fortunately, by supplying “feed=continuous” to the URL, CouchDB will keep your socket connection open and supply the changes as soon as they become available. Each change is represented by a well-formatted JSON line. This breaks from the previous examples, where the complete response consisted of well-formatted JSON, so beware!

http://localhost:5984/db/_changes?filter=app/events&since=196&feed=continuous

{"seq":197,"id":"f0a4a559fac65ab3cca87baf1c014fa7","changes":[{"rev":"1-c8dfffec4e08ca8db2b95f30613e213d"}]}
...
...
{"last_seq":197}

Eventually, if no changes have been sent for 60 secs, the socket connection closes. Before it does so, it spits out the last_seq, again for bookkeeping purposes.

I was wondering if it would be possible to create a simple iPhone/iPad application that is able to report specific events on a MapView using the _changes feed from CouchDB. All one then has to do as iPhone/ iPad application is connect to the continuous feed, parse the JSON data and display the events on your MKMapView. Server-side, all one has to do is insert/ update documents in the database. How easy!

So I started off by connecting to the _changes feed using an asynchronous NSURLConnection, expecting that its delegate would receive connection:didReceiveData: calls on each change. Unfortunately, this did not work: The NSURLConnection employs buffering internally that cannot be circumvented. Thus only after about 5 or 6 updates the delegate received some data. This is definitely not good enough. Some searching pointed me to the CFSocket/NSStream class. This class fortunately employs some more basic (read: unbuffered) socket handling, allowing me to receive data from the socket as soon as it becomes available!

    //open the socket
    CFReadStreamRef readStream;
    CFWriteStreamRef writeStream;
    CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)[theURL host], [[theURL port] intValue], &readStream, &writeStream);

    inputStream = (NSInputStream *)readStream;
    outputStream = (NSOutputStream *)writeStream;

    [inputStream setDelegate: self];
    [outputStream setDelegate :self];
    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [inputStream open];
    [outputStream open];    

The containing class is set as delegate for both the NSInputStream and NSOutputStream. This causes the following method being called, based on events that occur on the streams:

/* stream eventing */
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {

    switch(eventCode) {
        case NSStreamEventOpenCompleted:
            [self connection: nil didReceiveResponse: nil];
			break;

		case NSStreamEventErrorOccurred:
            [self connection: nil didFailWithError: [stream streamError]];
			break;

		case NSStreamEventEndEncountered:
            [stream close];
            [stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            [stream release];
            inputStream = nil;
            outputStream = nil;
            [self connectionDidFinishLoading: nil];
			break;            

        case NSStreamEventHasSpaceAvailable:
        {
            if (stream == outputStream) {
                [self connection: nil willSendRequest: [NSURLRequest requestWithURL: theURL] redirectResponse: nil];

                NSString *str = [NSString stringWithFormat: @"GET %@?%@ HTTP/1.0\r\n\r\n", [theURL path], [theURL query]];
                const uint8_t *rawstring = (const uint8_t *)[str UTF8String];
                [outputStream write:rawstring maxLength: [str length]];
                [outputStream close];
            }
        }
            break;
		case NSStreamEventHasBytesAvailable:
        {
			if (stream == inputStream)
			{
				uint8_t buffer[1024];
				int len;
				while ([inputStream hasBytesAvailable])
				{
					len = [inputStream read: buffer maxLength: sizeof(buffer)];
					if (len > 0)
					{
						NSData *theData = [[NSData alloc] initWithBytes: buffer length:len];
                        [self connection: nil didReceiveData: theData];
					}
				}
			}
        }
            break;
    }
}

As you can see, this method kind of delegates the occurring events to calls to the NSURLConnectionDelegate (which the containing class also implements, due to my initial effort to read the changes feed using a normal NSURLConnection).

Then, the connection:didReceiveData method handles the actual changes:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    NSString *str = [[NSString alloc] initWithData:data encoding: NSUTF8StringEncoding];

    NSArray *ary = [str componentsSeparatedByString:@"\n"];
    for (NSString *line in ary) {
        if ([line length] > 0 && [line characterAtIndex: 0] == '{') {
            id json = [line JSONValue];

            NSDictionary *dict = (NSDictionary*)json;
            NSObject *seq = [dict objectForKey: @"seq"];

            if (seq && delegate) {
                [delegate changeReceived: dict];
            }
            NSObject *last_seq = [dict objectForKey: @"last_seq"];
            if (last_seq && delegate) {
                int last = [((NSNumber*)last_seq) intValue];
                [delegate lastSequence: last];
            }
        }
    }

}

This method converts the JSON data to a NSDictionary and forwards these changes to a (self-defined) @protocol called ChangesDelegate:

@protocol ChangesDelegate

@optional

-(void) changeReceived:(NSDictionary*) dict;
-(void) lastSequence:(int) last;
-(void) changesComplete:(NSError*)error;

@end

The implementation that performs the actual calls to CouchDB can be created using:

    m_changes = [[Changes alloc] initWithHost: @"localhost" database: @"db" andFilter: @"app/events"];
    m_changes.delegate = self;

    m_lastSeq = [m_changes last_seq];

Since the calling class also implements the ChangesDelegate @protocol, it will receive the changes events. I am currently using it to display MKAnnotations onto a MKMapView and it works like a charm!

Thank you CouchDB for making life easy!

p.s. I’ve made the Changes class available through github: changesfeed_xcode. Enjoy!

, , ,

Nog geen reacties

PhoneGap, een alternatief voor native mobiele applicaties

PhoneGap, een alternatief voor native mobiele applicaties

PhoneGap is een interessante open source alternatief voor het schrijven van native applicaties voor elk (mobiel) platform dat er is. In het kort komt het erop neer dat PhoneGap zorgt dat je een HTML applicatie met javascript.  De specifiek API, zoals location, contact, e.d. worden afgeschermd door een standaard API van PhoneGap.

iPhone

Voor de iPhone kan de HTML applicatie gewoon worden aangeboden via de appstore. Dit is dan ook direct de truc waardoor er voldoende rechten zijn om de hardware aan te spreken. Er zijn al vele applicatie geplaatst in de appstore (zie een selectie in www.phonegap.com/apps).  Er is tevens een getting started en er zijn  extra plugins beschikbaar

Ondersteunde platformen

Naast iPhone wordt zowel Android, Blackberry, Symbian, Palm, N900 en Windows Mobile. Ook Windows Mobile 7 is zodra dit uitkomt eenvoudig te ondersteunen en ze zullen ook geen blokkade opwerpen in het voordeel van silverlight. Interesante gedachte is natuurlijk ook de iets minder mobiele system met Linux, Windows MacOS hebben ook allemaal een browser.

IPHONE ANDROID BLACKBERRY SYMBIAN PALM
GEO LOCATION
VIBRATION
ACCELEROMETER OS 4.7
SOUND
CONTACT SUPPORT N/A

Voor meer informatie zie www.phonegap.com

, , , , , ,

1 reactie

Building iPad applications using MonoTouch: the UISplitView

First of all, I bow deeply to the people that bring us MonoTouch. It was less than 2 days after the introduction of the iPad and the release of a beta of the iPhone/iPad SDK that a version of MonoTouch (and MonoDevelop) was available that covers the new API.

To build iPad apps, you need a couple of things that are all listed on the MonoTouch iPad page.

I couldn’t wait to build something that used the way bigger UI-surface that the iPad has. The first thing that attracted my attention when I fired up the Interface Builder was the UISplitViewController.
The idea behind the SplitView is that you have some navigation on the left (like a NavigationController, or just a TableViewController) and a data view on the right. That is, only when the display is in landscape mode. As soon as you turn the iPad (the iPad SImulator, of course) to portrait, the left side disappears and all the screen is available to the data view.
That behaviour is entirely taken care of by the UISplitViewController, at least if you stick to the conventions!

I decided to make a simple app with a list of places on the left side, and a map-view on the right:

LandscapePortait version of the UI

The obvious start

When you start a new iPad application from the New Project menu, you get the well-known set-up of files in your project. Double-click the MainWindow.xib to fire up Interface Builder. Then drag the Split View Controller from the Library Window and drop it below the Window object in de MainWindow:
MainWindow

As you can see, you get a lot for free, including stuff you don’t want. Now it gets less obvious. After clicking Cmd-Backspace a thousand times and positioning my cursor everywhere, I had an epiphany. Since the SplitViewController won’t work without two other controllers I had to add something new before I could remove the old!

No kidding, that was really the solution. So I added a UITableViewController, Interface Builder magically removed the default NavigationController, and I added a MKMapView to the view-controller on the right.

This is the result:

SplitView I then added outlets to the AppDelegate for the map, the tableview and the splitview:

Outlets

The less obvious code

To make the controllers react properly when the orientation of the UI changes, you need to override the ShouldAutorotateToInterfaceOrientation() method in each of your controllers. How do you do that?
The first step is to add a class to your project, make it inherit from your controller (a UITableViewController in my case) and override the ShouldAutorotateToInterfaceOrientation() method as below:

public class PlacesController : UITableViewController
{
	public PlacesController ()
	{
	}
 
	public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
	{
		return true;
	}
}

Repeat the step for the second controller:

public class Map : UIViewController
{
	public Map ()
	{
	}
 
	public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
	{
		return true;
	}
}

This should still compile. But it doesn’t work yet.

The even less obvious link from the code to the UI

The objects in the Interface Builder are standard objects. They need to be of the types that we just defined, to make the overridden methods work. See we need to make our own classes known to Interface Builder. That’s done by adding a Register-atribute and overriding the constructor with one that accepts an IntPtr.
The Map class changed in something like this:

[Register("Map")]
public class Map : UIViewController
{
	public Map ()
	{
	}
 
	public Map(IntPtr p) : base(p)
	{
	}
 
	public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
	{
		return true;
	}
}

The final step is setting the right class on the controller. Go to Interface Builder, select the UIViewController (e.g.) in the MainWindow, then go to the Inspector Window and type your classname over the default one:

Inspector

And then it works! The map will re-orientate itself when the iPad is turned, and the Table View appears and disappears.
Of course you want some location data in the table and the map to show the locations when clicked on, but that’s all in the code you can download.

Enjoy making software for a device that makes you need to rethink the way you always made software…!!!

Mmmmh. re-reading that last line, I realize it’s a little hard to read. What I meant was something like Joe Hewitt wrote.

, , , , ,

4 reacties

About Non-Disclosure Argeements

Last night I wrote a blogpost about the first application I built for the iPad. I’m so happy with the (coming of) the new gadget and the possibilities that the MonoTouch created to rite .NET code for it, that I could not wait to write a sample app and to share my experiences with the rest of the world.

So at 11 o’clock I sent out a tweet with a link to the blog. About 60 seconds later I got a reply from someone that I trust knows what he is talking about, that I better remove the post since I agreed on a NDA.

Wow, did I? Yes, I did. When you download the beta of the iPhone 3.2 SDK there’s the following text:

NDA

Yep, the beta is considered Apple Confidential Information. So I removed the post and went out to discover what I could and could not do under this NDA. Tried searching apple.com on the exact string “Apple Confidential Information”. No hits. Searched the legal department at apple.com/legal, no hits on beta software

Finally came up with the idea to search on the sub-site of the Apple Developer Centre. On the page on Pre-release software there is the rather clear text:

Pre-release software is Apple Confidential Information and your use of such software is subject to the Apple Developer Connection Terms and Conditions, including the Prototype License and Confidentiality Agreement attached thereto. Distributing the software to anyone other than an ADC member who is working for the same entity as you is considered a violation of your agreement with Apple and is damaging to both Apple and those who develop for the Apple platform. We sincerely appreciate your efforts to keep this software Confidential.

The last part I understand: no distribution of the software. No problem. But I think the Confidentiality Agreement must hold more information. There is a “Terms and Conditions” PDF that has a section 7 on Pre-release software that essentially tells you that “You certify that pre-release software will only be used for testing and development purposes”. Fine so far, but it also refers to the attached Prototype License and Confidentiality Agreement. So where is that agreement and what does it say?

The PLCA (as they lovingly call it) is on page 5 of the PDF and has a section 4 on non-disclosure:

You agree not to disclose, publish, or disseminate Confidential Information to anyone other than employees and contractors working for the same entity as you who have an existing ADC membership

Well, there it is. No right to publish Confidential Information, which means no stuff that Apple hadn’t already published about. So I can write about the iPad, I can write about the existence of the SDK, but I cannot write about a certain new UI-control that I used to build my demo app. Even though I like this new UI-control and love what it does for me.

Is this really necessary? What good does it do Apple when I don’t write about their products? About a product that they most certainly want as much coverage of as they can get: the brand new iPad?

Should I really be worried that they will sue me when I break my Vow of Silence? Of course, being Dutch, growing up in a country that is known around the world to be liberal, it is hard to believe someone will really sue you. Especially when you write nice about them.

I don’t like secrets, I don’t like lawyer stuff that tries to protect (imprison?) ideas. I do like what Jeff Atwood wrote about ideas: ideas in itself are not very valuable, it is how you execute them. So my message to Apple: liberate your ideas, they are great, I love them. Allow others to rave and rant about them, who cares? You are the one that is going to execute them in the most profitable way, any way. Right? And if you’re not going to, then don’t blame the blogger, blame yourself.

, , ,

Nog geen reacties