thingsinjars

  • 17 Jun 2010

    PhoneGap - The Drupal of App development

    I'm a fan of Drupal even though I don't use it that often. I like that I can see exactly what's going on. I can easily follow the execution from URL request to page serve.

    What I usually end up doing on any Drupal project is:

    1. build the majority of the site in a few hours
    2. find one small piece of functionality missing that's absolutely essential
    3. dig into the core to make it happen
    4. find a simpler way of doing it and step out of the core a bit
    5. find an even simpler way and step back a bit more
    6. figure out how to do it in a single module file and put the core back the way it was.

    That probably seems utterly inefficient but it has served me well since Drupal 4 and it means I've got a really good picture in my head of the internal workflow.

    This is in stark comparison to other systems, particularly some .NET CMSs where a request comes in, something happens and the page is served. There are even some PHP frameworks and CMSs where everything is so abstracted, the only way you can get an accurate picture of what is happening is to already have an accurate picture of what is happening.

    I've used several different ones and I keep coming back to Drupal (also, recently, Perch, but that's besides the point here).

    “What on earth does this have to do with PhoneGap?” I hear you ask. Quite rightly, too.

    When I was planning Harmonious, I looked at various frameworks for turning a combination of HTML, CSS & JavaScript into an app - PhoneGap, Appcelerator Titanium, Rhomobile. Rhomobile (or the Rhodes Framework) is built on Ruby so I didn't investigate too far. That's not to say it's not a good framework, I couldn't say either way. The idea behind using one of these frameworks is to save you the time of having to learn Objective-C and seeing as I've only done very minimal amounts of Ruby, I'd be replacing 'learn Objective-C' with 'learn Ruby'. That said, I've always thought Ruby developers opinion of themselves was slightly too high.

    The first framework I properly spent some time with was Appcelerator. It seemed quite shiny and I liked having single interface access to compilation for iPhone and Android but I wasn't so keen on having to sign up for an account with them for no obvious reason. Some further investigation suggested that this was so you could send your project to their servers for proprietary cross-platform compilation of your desktop app. This is less useful, however, if you're developing just for iPhone and Android as for both, you need the SDK installed locally and the compilation is done on your own machine.

    The main thing that I wasn't comfortable with in Appcelerator was that there seemed to be a lot happening behind the scenes. This is not necessarily a bad thing, of course, but it started that little buzz in the back of my head that I get when working on .NET. When I press 'compile', I want to know exactly what it's doing. I want to know exactly how it takes my JavaScript and embeds it, when does it include its own API files and what do I change to make it do stuff it doesn't do by default?

    After that, I moved to PhoneGap (version 0.8.3) and found myself immediately falling into my Drupal workflow. The app fell into place in less than an hour (with a liberal sprinkling of jQTouch and the Glyphish icons). I then needed to take a screenshot and couldn't see an obvious way to do it but, due to the nature of PhoneGap being completely open-source, it was easy to spot where to jump into the code. I hacked in a screenshot function in another hour, spent another half hour making it better and another making it simpler. Just to complete the cycle, I have now wrapped up all my code into a plugin and removed my hacking from the core. Hmmm... that all seemed eerily familiar.

    That's not to say PhoneGap is perfect. All the benefits of a completely open-source project referred to previously also come with all the drawbacks. The current version (0.9.0) is fiendishly difficult to download and get started with. It has been split into one parent project and several child projects (one per mobile platform) and it's no longer obvious what you do. It's easy enough if you're already set up but actually getting there is tricky. The most common failing of any open-source project is also true: poor documentation. There's a wiki but it's mostly out-of-date. There's a section on phonegap.com called 'docs' but they're also out-of-date. There's an API reference but it's autogenerated from comments and is also out-of-date. The only place to get accurate information is the Google group but that's not documentation, that's solutions to problems.

    There have also been some claims that PhoneGap is unstable and crashes but personally, I haven't seen that. It's possible that the crashes and performance issues are the result of memory leaks in the JavaScript. Appcelerator automatically runs JSLint on your code before compilation so it will highlight any problems. If you can fit that into your standard workflow, you might be able to avoid some of the instability.

    Additional comments

    It seems that Disqus (the commenting system I'm using below) has some problems with Safari 5 & Chrome so this comment was sent via gist (I knew I shouldn't have stopped using Noodle).

    I'll respond later. I've just got back from the Apple store and have toys to play with.

    Comment from Jeff Haynie (@jhaynie)

    A few comments about Appcelerator.

    1. We're completely open source and you can see all our active development every single commit on github.com/appcelerator. We have plenty of outside core open source contributors.

    2. Yeah, to do what we're doing, it's complicated - much more than Phonegap - so it does mean with complexity it's hard to grok. however, the source is all there. Also, it's full extensible through our SDKs and we this SDK as the way we build Titanium itself.

    3. For Desktop, we _only_ upload to our build servers as a convenience to cross-platform packaging. Nothing mysterious and all the scripts we run are included (and open source) so you can run them on your own. Plenty of our customers do this behind the firewall. When you're developing locally (say on a OSX machine), it's all local during dev. Only cross-platform packaging is done as a convenience to developers. We have to pay for this bandwidth and storage and we do it to make it easier. And it's free.

    Hope this clarifies some of the above. Phonegap's a great project and we love the team - but I think we're trying to do different things and come at it from different approaches. In the end, this is good for developers as it gives everyone more choice based on their needs.

    Geek, iOS, Development

  • 21 Dec 2009

    Crowdsourced Weather - Part 2

    So, I couldn't help myself. I had a niggling idea at the back of my head that I needed to get out. After coming up with this Twitter weather idea last week, I decided to spend a couple of hours this weekend building it. As if I didn't have other things I should have been doing instead...

    It works pretty much exactly how the pseudocode I mentioned last time describes. Every few minutes, a script will search Twitter for mentions of any weather words from several different languages. It will then look up the location of the person who tweeted that and store it. Single reports might be wrong and users might not have stored their actual location but over a large enough sample, this system becomes more accurate. The script removes any matching twets older than 6 hours.

    To display, I actually ended up using Geohashes instead of Geotudes because it is easier to simplify them when you zoom out just by cutting off the tail of the hash. For example, the physical area denoted by gcvwr3qvmh8vn (the geohash for Edinburgh) is contained within gcvwr3 which is itself contained within gcv. There are a few technical problems with geohashes but it seems the best fit for this purpose. If anyone knows of any better suggestion, please let me know. I do realise that this is quite possibly the slowest, most inefficient JavaScript I've ever written because it makes an AJAX call for every graticule and it probably should just send the South-East and North-West bounds and get back an array of them but, like I said, there were other things I should have been doing. Because the overlaid grid changes resolution based on zoom level, there are a few places where it is either tragically slow (resolution too fine) or terribly inaccurate (resolution too rough). That's just a case of tweaking the algorithm. Similarly, it's set to display reports of weather if there are 2 or more matches but it could be tweaked to only show if a larger number have reported something.

    So go, play with the Twitter-generated weather map. If someone can come up with a good, catchy name, or some better graphics, that'd be great, thanks.

    Source code is available: twitter-weather-1.0.zip [Zip - 298KB].

    You'll need your own Twitter login and database account to use it.

    Geek, Development, Javascript, CSS, Design

  • 17 Dec 2009

    Crowdsourced Weather

    This is a more general version of the #uksnow map idea. It's a crowd-sourced weather map which relies on the fact that any one individual tweet about the weather might be inaccurate but given a large enough sample, enough people will mention the weather in their area to make this a workable idea. It doesn't require people to tweet in a particular format.

    To get info

    Have an array of weather words in various languages (rain, hail, snow, schnee, ame, yuki)
    every 5 minutes:
    	foreach weatherword
    		search twitter for that word
    			http://search.twitter.com/search.atom?q=rain
    		retrieve latest 100 tweets
    		foreach
    			get user info
    				http://twitter.com/users/show.xml?screen_name=username
    			get user.location if available
    			geocode
    			save:
    				username, time, lat, long, geotude, weatherword
    		Remove any tweets about this weatherword older than 6 hours.
    			
    

    To display info

    Show a Google map
    Based on current Zoom level, split the current map into about 100 geotudes
    foreach geotude
    	search database for any weather results for that block (probably using an ilike "1234%" on the geotude field)
    	sort by weatherword count descending
    	draw an icon on top of that block to show the most common weatherword
    	
    If the user zooms in, recalculate geotudes and repeat.
    

    I quite like that this uses geotudes which I think are an excellent idea.

    I built a very basic version of this. Read more about it in Part 2.

    Ideas, Development, Javascript, CSS, Design

  • 1 Nov 2009

    Building an Objective-C growlView

    Wherein our intrepid hero learns some Objective-C and figures out the easy bits are actually quite hard.

    This is a little guide on how to write a growl plugin to automatically send notifications as tweets. If you just want the finished thing: GrowlBird.zip [Zip - 228KB]

    A couple of days ago, I decided to give myself a little software development task to write a Twitter Growl view. Growl is a centralised notification system for Mac OS X that lots of other applications can use so that there's one consistent way of showing notifications.

    Background

    The idea behind this little experiment wasn't to have tweets appear as growl notifications, there are already plenty of apps that do this, the idea was to have growl notifications sent to Twitter. Some friends have started organising a crowdsourced Friday afternoon playlist via Spotify and I thought it'd be handy if Spotify tweeted each song as it started. The easiest way I could think of doing this was to tap into the fact that Spotify sends a Growl notification on track start and get the Growl display plugin to tweet it as well [1].

    Build

    I downloaded the Growl Display Plugin Sample 1.2 [Zip - 186 KB] from the developer downloads page and the MGTwitterEngine library. I then downloaded Xcode so I could do the development. I have to point out here that this was my first foray into Objective-C programming and, indeed, my first attempt at anything vaguely C-related since I wrote a command-line calculator about 12 years ago. If I do it wrong, please forgive me.

    The first thing to do was open the sample project in Xcode, figure out what files do what, etc. There is very little documentation on how Growl views or display styles work so I pretty much just spend an hour reading all the source from top to bottom. Here's a quick summary:

    Sample_Prefix.pch
    Pre-compiled header. Stuff that's included before every pre-compiled file
    Growl/
    Folder containing standard Growl stuff. Don't need to touch.
    GrowlSampleDisplay.h
    Header file, didn't need to change anything
    GrowlSampleDisplay.m
    Class for setting up things. Again, didn't touch [2].
    GrowlSamplePrefs.h
    Defining default preference values and naming functions to handle them. More on this later.
    GrowlSamplePrefs.m
    The actual functions mentioned in the previous header file
    GrowlSampleWindowController.h
    Not doing anything visual, really so I didn't need to mess around with this
    GrowlSampleWindowController.m
    As above
    GrowlSampleWindowView.h
    Declaring objects needed for execution
    GrowlSampleWindowView.m
    Instantiating the objects then actually using them later on.

    Again, I'm not used to doing this stuff so if I'm using the wrong terminology, just pretend I'm not.

    I then dragged the MGTwitterEngine library into the project drawer, saved and built. At this point it successfully did nothing different which is what I was hoping it would do. Well, it popped up the 'This is a Preview of the Sample Display' message using the MusicVideo style which is what it does when you don't screw with it.

    Testing growlView

    It's not actually that easy to test growlView plugins. You can't simply add and remove them. To install, you double-click on it. You should be able to see it listed in the Display tab of the preference pane. To re-install, you need to delete ~/Library/Preferences/com.Growl.GrowlHelperApp, ~/Library/Application Support/Growl/Plugins/Sample.growlView and restart growl (click on the paw in the menu bar). It's a bit of a hassle.

    The next thing was to include the MGTwitterEngine. In GrowlSampleWindowController.h, #import "MGTwitterEngine.h" and create a new object. I just followed the instructions in the README but be sure to follow all of them. If you get errors about LibXML not being installed or YAJL not working, don't worry, you just need to make sure you set USE_LIBXML to 0 in all the places you're supposed to. GrowlSampleWindowController.h now contains this:

    
    #import "GrowlDisplayWindowController.h"
    #import "MGTwitterEngine.h"
    
    @class GrowlBirdWindowView;
    
    @interface GrowlBirdWindowController : GrowlDisplayWindowController {
    	CGFloat						frameHeight;
    	NSInteger					priority;
    	NSPoint						frameOrigin;
    	MGTwitterEngine *twitterEngine;
    }
    @end

    In GrowlSampleWindowController.m, I then instantiated the new object:

    
      @implementation GrowlBirdWindowController
      - (id) init {
      	  :
      	  :
          twitterEngine = [[MGTwitterEngine alloc] initWithDelegate:self];
    	    [twitterEngine setUsername:@"growlbirdtest" password:@"testgrowlbird"];
    	  }
    	  :

    And then modified the setNotification function to also send an update:

    
    - (void) setNotification: (GrowlApplicationNotification *) theNotification {
        :
    	[view setTitle:title];
    	[view setText:text];
      NSLog(@"sendUpdate: connectionIdentifier = %@", [twitterEngine sendUpdate:[NSString stringWithFormat:@"%@, %@", title, text]]); // The new line
      :
      }

    That was enough to get growl to send messages to appear on http://twitter.com/growlbirdtest but it doesn't make it that useful for anybody else, to be honest. The next thing to figure out was the preferences.

    Preferences

    Without documentation, this took a bit longer that I expected. To start off changing the english version before worrying about localization, find the GrowlBirdPrefs.xib in resources/en.lproj/ and open it. Interface Builder will launch then you can double-click on 'Window' and see the layout of the preference pane. Search in the Library for 'text' and drag a text field into the window then spend about half and hour clicking round the interface. Open up the various inspectors (right-click on an object), look through the different tabs, click between the newly added text field and the sliders and drop-downs that are already there just to see what's different. Once I was a bit familiar, I opened the connections tab so that I could bind the value of the text field to the value 'twitterUsername' in my code. I checked 'value', Bind to 'File's Owner' and entered 'twitterUsername' in Model Key Path. I then repeated this for twitterPassword using a Secure Text Field from the Library. The option nextKeyView is used to say which item is tabbed to next when you're navigating with the keyboard so to keep things tidy, I dragged lines from nextKeyView from each of them to the right places in the layout.

    Back in the code, I added new default preferences in GrowlSamplePrefs.h:

    
      #define Sample_USERNAME_PREF		@"Username"
      #define Sample_DEFAULT_USERNAME		@"growlbirdtest"
    
      #define Sample_PASSWORD_PREF		@"Password"
      #define Sample_DEFAULT_PASSWORD		@"testgrowlbird"
      :
      :
      @interface GrowlBirdPrefs : NSPreferencePane {
      	IBOutlet NSSlider *slider_opacity;
      	IBOutlet NSString *twitterUsername;
      	IBOutlet NSString *twitterPassword;
      }

    and named some handlers for them:

    
      - (NSString *) twitterUsername;
      - (void) setTwitterUsername:(NSString *)value;
      - (NSString *) twitterPassword;
      - (void) setTwitterPassword:(NSString *)value;

    Be careful here, I got confused and didn't have the same spelling here for twitterUsername and twitterPassword as I had put in the interface builder as I hadn't realised the two were directly connected. They are. Obviously. The next thing to do is to write the code for these handlers:

    
      - (NSString *) twitterUsername {
      	NSString *value = nil;
      	READ_GROWL_PREF_VALUE(Sample_USERNAME_PREF, SamplePrefDomain, NSString *, &value);
      	return value;
      }
      - (void) setTwitterUsername:(NSString *)value {
      	WRITE_GROWL_PREF_VALUE(Sample_USERNAME_PREF, value, SamplePrefDomain);
      	UPDATE_GROWL_PREFS();
      }
      - (NSString *) twitterPassword {
      	NSString *value = nil;
      	READ_GROWL_PREF_VALUE(Sample_PASSWORD_PREF, SamplePrefDomain, NSString *, &value);
      	return value;
      }
      - (void) setTwitterPassword:(NSString *)value {
      	WRITE_GROWL_PREF_VALUE(Sample_PASSWORD_PREF, value, SamplePrefDomain);
      	UPDATE_GROWL_PREFS();
      }
    

    Build and reinstall and this will now show the same preference pane as before but with two new text fields which allow you to enter your username and password. In fact, build at several stages along the way. Every time you make a change, in fact. If something breaks, check the error log to see if it's something predictable that should have broken at that point or if you've done something wrong. Also, keep the OS X log app Console open in the background. It will spew out error messages if you do something wrong. It's also good to have your code write out console messages to keep a track on what your code is doing like so:

    
      - (NSString *) twitterPassword {
      	NSString *value = nil;
      	READ_GROWL_PREF_VALUE(Bird_PASSWORD_PREF, SamplePrefDomain, NSString *, &value);
      	NSLog(@"twitterPassword = %@", value);
      	return value;
      }
    

    You'll notice we're still sending messages to the growlbirdtest account because, even though we are reading and saving the username and password, we're not doing anything with them. That's easily remedied by editing GrowlSampleWindowView.m again and replacing the hard-coded login details with a couple of lines to read from the preferences or fall back on the default:

    
      twitterEngine = [[MGTwitterEngine alloc] initWithDelegate:self];
    	NSString *twitter_username = Bird_DEFAULT_USERNAME;
    	NSString *twitter_password = Bird_DEFAULT_PASSWORD;
    	READ_GROWL_PREF_VALUE(Bird_USERNAME_PREF, SamplePrefDomain, NSString *, &twitter_username);
    	READ_GROWL_PREF_VALUE(Bird_PASSWORD_PREF, SamplePrefDomain, NSString *, &twitter_password);
    	[twitterEngine setUsername:twitter_username password:twitter_password];
    	NSLog(@"Twitter Login: username = %@", twitter_username);
    

    And, hooray! It works and posts to the account for which you entered details. Sort of. Some apps double-post. I haven't figured out why yet.

    Renaming

    After all that, the final bit (which I thought would be the easiest) was to rename the growlView from 'Sample' to 'Bird'. I have read that in the latest version of Xcode (which presumably comes with Snow Leopard), there's a global 'Rename' which will do all the relevant stuff for you. If you don't have that, you'll need to read 'On the Renaming of Xcode Projects' and do everything there. If you're still finding your growlView is called Sample, manually open every Info.plist you can find, 'Get Info' on everything, scour through the settings for the different build environments (Debug and Release)... It took longer to rename the project than to actually build it.

    A trivial aside, I have also added two fields to the preferences for tweet prefix and tweet postfix in exactly the same way the username and password were added. I leave the details to the interested reader.

    You should now have a completed, installable growlView/Growl View/Growl Display/growlStyle/whatever it's actually called. You can export the current Git project to have a look around or you can just download the finished GrowlBird.zip [Zip - 228KB] if you like. Note, the Git project isn't guaranteed buildable at any moment in time, I might break it. The localisations still need done and the layout of the prefPane isn't the greatest, either.

    [1] Since writing this, I have discovered that someone else already made a growlView to do this, it just didn't show up on any of my Google searching.

    [2] It turns out that I did have to modify these in the end due to a silly bug. Like I said, this is my first attempt at this kind of thing.

    Geek, Development

  • newer posts

Categories

Toys, Guides, Opinion, Geek, Non-geek, Development, Design, CSS, JS, Open-source Ideas, Cartoons, Photos

Shop

Colourful clothes for colourful kids

I'm currently reading

Projects

  • Awsm Street – Kid's clothing
  • Stickture
  • Explanating
  • Open Source Snacks
  • My life in sans-serif
  • My life in monospace
Simon Madine (thingsinjars)

@thingsinjars.com

Hi, I’m Simon Madine and I make music, write books and code.

I’m the Engineering Lead for komment.

© 2025 Simon Madine