Friday, September 3, 2010

Integrating Core Data into your Three20 App

The Three20 library for iPhone app development is pretty cool - it take care of a lot of the 'mundane' code that you might need to write to accomplish basic tasks. Apple really should include some of the functionality of Three20 in the iPhone SDK itself, specially the Network stack.

As I built a more advanced app using Three20, I realized that it would be great to be able to persist to a database to make loading quicker when state on the server is unchanged. I needed some pretty basic Core Data integration in my app, and it was a little annoying that Three20 didn't have a Core Data-backed implementation of TTModel built into it. It seems like a lot of people want this, but there isn't any good post about it I could find. Jeff Verkoey, who currently maintains Three20, started writing a Core Data based tutorial but didn't get around to actually doing much with it. And there are a few threads in the Three20 Google group but nothing too comprehensive.

Having built this for my app, I figured I would post some more concrete information here for others who might find it helpful. I really want to just contribute a clean cut of this code as a TTCoreDataModel with unit tests to the Three20 source, but I don't have the luxury to spend time on it currently.

So here's a quick step-by-step on how to modify your existing Three20 based app to add basic Core Data support. The specific example I use fetches data from a server using TTURLRequestModel and writes it out to SQLite using Core Data entities. When displaying data to the user, it fetches data from Core Data using a NSFetchedResultsController, which efficiently interacts with Core Data.

Step 1:
Add CoreData.framework to your list of dependencies. Then, add the Core Data stack. Usually, you would do this in the AppDelegate. Jeff's AppDelegate files have this implemented, if you want an example. In my case, I wanted different databases for different Models, so I had these in my TTModel implementations.

Step 2:
Create your model. Add a new file of type "Data Model" in XCode (available in the 'Resource' section). Make sure the .xcdatamodel file gets added to your Target so that it's compiled into your binary. Look here for information on using the modeling tool. Once you've created your model, import the code for the Entity objects into your project. Now you're ready to start using Core Data.

Step 3:
Update your TTURLRequestModel implementation. Let's assume you're simply downloading some JSON and want to write it out to Core Data as an Entity you created in Step 2. Update your (void)requestDidFinishLoad:(TTURLRequest*)request implementation of TTURLRequestDelegate as follows:




A few notes about the code above:
  • DBResource is my simple entity containing an "id" and a "name".
  • The NSAutoreleasePool helps clean up memory allocated in this tight loop here
  • -reseting the context makes sure that the data faults in as needed instead of staying in memory when you read it using the NSFetchedResultsController
Step 4:
In your TTTableViewDataSource implementation, create a NSFetchedResultsController and configure it to fetch your Entities as needed. Then, override the (id)tableView:(UITableView*)tableView objectForRowAtIndexPath:(NSIndexPath*)indexPath method to return a TTTableItem built using the Entity you fetched from Core Data




Now, your TTTableViewDataSource is configured to read results from the DB you created and display them in your view. Remember that tableViewDidLoadModel can be called multiple times, so clean up any previously allocated NSFetchedResultsController if you need to.

Step 5:
Implement your logic to delete/update your Core Data entities. Depending on how your app uses data persistence, you want to ensure that you update/delete your entities from Core Data. For instance, if you always want to fetch data from the server and load them into Core Data on first run, then update Step 3 to first delete all entities. If you wish to show local data when available but fetch when not, then update Step 3 to not hit the server before checking locally. When deleting entities, remember that Core Data only lets you delete a single entity at a time (so you must fetch all and loop through each to delete it). If you want DROP TABLE ... semantics you're going to need to maintain different NSManagedObjectContent and NSPersistentStoreCoordinator for each such table, and then simply delete the database file.

So, this isn't too hard really. Assuming you're familiar enough with building your Three20 app, my notes above should help you get started with integrating Core Data into your app. My app uses a TTSectionedDataSource, and with a few tweaks to NSFetchedResultsController it's simple to do that too.

Saturday, August 7, 2010

An Apple A Day Keeps the Bugs Away

I've been working from home for the last year or so, and it has tremendously increased my productivity as a developer. Besides the obvious reasons (lack of distractions, no meetings, etc) there is one other advantage that I haven't often heard people mention before.

I have been able to find solutions to some really hard technical problems much quicker, and I attribute it to the fact that I work from home instead of in a more office-like environment.

Most software engineers will tell you that some of their best ideas have come to them when doing the most mundane of things - taking a shower, strolling across lawns, eating a snack and so on. I don't know the psychology behind this, but I know it's true for me. Something about being alone and thinking about your problem subconsciously, and yet being physically distracted with an unrelated task seems to work wonders. I have solved more tough problems while in the shower, when sitting down to eat an apple, when watering the plants or simply doing the dishes than I have sitting at a desk and just thinking. So obviously, being at home makes it much easier for me to find one of these mundane tasks that let me 'detach' and yet perform an activity that is beneficial to me both personally and professionally.

It's become a pretty important weapon in my software development arsenal - if I'm stuck on something for more than 30 minutes, I get out of my workspace and go do something mundane around the house or eat a quick snack. More often than not a solution magically appears in my head.

Back when I worked in an office, if I was stuck trying to solve a hard problem my best option was to find someone familiar enough with the problem space and bounce ideas off of them. This worked pretty well initially but didn't scale too well with problems that became narrower and deeper in scope - I often found myself spending a lot of time trying to catch others up to where it would be valuable to bounce ideas off them. And heading off into the office kitchen to reorganize stuff or watering random plants around the office would just make me look weird.

So, the next time you're having a hard time finding a solution to a problem in the office think about taking the next day off and working from home. Oh, and make sure the dishes aren't done. ;)

Wednesday, June 2, 2010

Slow onclick Javascript handling in Google Chrome 5.0.375.55

Google's Chrome has been a pretty slick browser, and it's speed was the primary reason it became my default browser. The V8 Javascript engine supposedly blows other engines out of the water, and it did feel noticeably faster than others - until this latest version.

Sometime last week, my version of Chrome was auto updated to 5.0.375.55, and I noticed that the website I've been developing became terribly slow. Because of a previous Chrome bug, I hadn't migrated my site from jQuery 1.3 to 1.4, so one of the first things I did was verify the fix and then update my site to use the newer, faster jQuery. As I began testing, I noticed a significant reduction in performance of some features on my site.

Initially I suspected it had something to do with jQuery upgrade. After a few hours of changing things around, I was pretty convinced the problem wasn't jQuery 1.4. I noticed that Firefox and even IE 8 didn't exhibit this latency, so now I started to suspect the new version of Chrome. I hacked up a couple of test pages, and I think I found the culprit. It looks like the onclick handler's performance degrades with the size of the HTML page, resulting in a very noticeable (1-2 second) lag once you have a table with 3000/4000 rows. Firefox and IE 8 don't seem to have any trouble handling this.

I've logged Issues 45620 in the Chromium project with details and a repro to track this problem. This definitely isn't good news for me - with growing adoption of Chrome and general perception that Chrome is the fastest browser, problems like these make website look faulty even when they might not be.

As an aside, I have noticed that Chrome feels like it has regressions more often than other browsers (perhaps a function of shipping more often?) I've run into several issues developing for Chrome - the silver lining is that bug fixes are quicker than rival browsers, but better QA before release would certainly help developers.

Other Chromium issues that I've had trouble with and have had to work around are Issue 7771, Issue 30693, Issue 33411 and Issue 36559.

Update: An comment on Issue 45620 identifies one of the reasons for the slowdown and a workaround.

Wednesday, May 19, 2010

Showing the Cancel button in UIActionSheets on iPad

In it's iPad Human Interface Guidelines Apple provides several guidelines about how user experience should differ between iPhones and iPads. One such element that should behave differently is the UIActionSheet.

Apple recommends that cancel buttons not be shown for action sheets on iPad:
Because taps outside the popover dismiss the action sheet without selecting an item, this results in a default way to cancel the sheet. Including a cancel button would therefore only cause confusion.
But the guidelines do allow exceptions to this rule:
However, if you have an existing popover and are displaying an action sheet on top of other content using an animation, a cancel button is still appropriate.
So you would think following this guideline is as simple as not specifying a cancel button in most cases, and only specifying a cancel button when you have carefully considered the exception case, right? Wrong. Even when you do specify a cancel button, it looks like the iPad runtime automatically always hides it.

So how do you provide a cancel button in the exception case? You could just provide another normal button that has cancel behavior, but that's not ideal because you really want the difference in appearance that UIActionSheets on iPhone provide by default. After some tinkering, the best solution I could find was to set the actionSheetStyle property of the UIActionSheet to UIActionSheetStyleBlackOpaque.

I can speculate why this works, but I don't know for certain so I'd rather not. If you know of a better/more correct way to do this, please do leave a comment.

I am surprised that Apple was mindful enough to list the exception case, and yet careless enough to not provide documentation on how to handle this case - they even seem to have forgotten to document that the iPad automatically overrides user-provided cancel buttons by default.

Saturday, May 8, 2010

Notes from a Week in Paris

I just got back from a lovely week in Paris with my wife - it was a rejuvenating and much required holiday for both of us. Work has been crazy, for her especially. Paris was wonderful and more than lived up to my expectations. We rented a tiny little studio in Le Marais (~120 sq ft, fully equipped including a washer - extremely efficient use of space!) Spending the entire week gave us enough time to explore sights, sounds and tastes of Paris without too much of a rush.

There are lots of books and blogs about visiting Paris and things to do/see. We used a bunch of resources - advice from friends, Lonely Planet's excellent iPhone app, the very insightful Secrets of Paris website with lots of tips on what to see and do and Paris By Train for everything related to using Paris' excellent rail network. However, there were some things I hadn't read about or was surprised by:
  1. Cafés, brasseries and boulangeries/patisseries often have different prices for take away (emporter), eating inside (sallé) and terrace dining. Most cafés/brasseries offer set menus (Formulae) and chef's recommendations which change daily (menu and price). They are almost always much more cost effective than ordering a-la-carté.

  2. The French are very proper. You greet business owners when you enter their store/café and thank/greet them before you leave. Sprawling across lush, green lawns in the several Parisian parks (Jardins) or putting your feet up on empty train seats is frowned upon. You sit in chairs provided in the Jardins and sit up straight in trains.

  3. Freshly squeezed orange juice (jus d'orange pressé) is extremely popular. You'll find orange juice machines pretty much everywhere (including Starbucks), but it's best to find a green-grocer/fruit vendor and get a half-litre jar of fresh squeezed juice each morning. Fruit vendors are everywhere.

  4. By default, coffee means espresso. A cappuccino/café-au-lait at most Cafés is expensive (between 4-8 EUR) and isn't that great (for a Seattlelite, at least). A mocha at Starbucks is EUR 4.50. Little créperies or street cafés have better deals for coffee that tastes identical (1-2 EUR). Or, hit up a McCafé. A 'mocha' is called a 'chocochino' and isn't commonly available.

  5. Sit-down créperies are totally not worth the price/experience. Get a crépe from a street vendor/corner shop and sit in a park or a street bench. The more exotic crépes (savory & sweet) are only available in nicer establishments, obviously.

  6. Wine/liquor is available in grocery stores (supermachés). Supermachés open around 8:30am and shut around 8:30pm. You are expected to bag your own groceries. For late-night grocery runs, there's usually a little-store-that-sells-most-things every few blocks which stays open late (2am)

  7. Bars have later Happy Hours than in the US. Happy Hour generally runs from 6pm to 9-10pm. Folks eat dinner late (most restaurants stay open until 11pm).

  8. Time is specified using 24 hour notation. "What time do you shut?" is answered with "23 hours". Most people don't understand am/pm.

  9. Take binoculars to fully appreciate the amazing art/architecture details.

  10. Use the 'Vélib' shared bike service. Vélib stations are everywhere, the roads and drivers are biker-friendly and it is the cheapest and most convenient way to get between locations in the city. Buy a map-book that has streets sorted by districts (arrondissements) and also has metro/Vélib stations marked.

  11. It is not common for crémeries/gelaterias to offer tastes.

  12. On the metro, or just walking around, you'll see a significant number of people going home (presumably) with one or more fresh baguettes from the bakery. For some reason almost all of these people are men. Join the crowd - fresh baguettes are the only way to go. Bread is baked fresh twice a day (if not continuously).

  13. When you pay cash, check the change you get back. Parisians are either terrible at math or prone to short-changing customers. I routinely got back incorrect change, on average twice a day.

  14. Before you go to a specific place (café, boulangerie, museum, grocery store, monument) on a Saturday/Sunday/Monday check before you head out. Almost every establishment is closed on at least one or more of these days.

  15. Strange, English speaking 'beggar women' holding hand-written post cards will randomly approach you at monuments/museums and ask "Speak English?". I didn't quite figure out what the scam was (I never answered "Yes") but this is most certainly some sort of scam.

  16. Comté tastes awesome, and is the most popular cheese. Camembert is a close second. Get cheese fresh from your neighborhood fromagerie.

  17. If you decide to cook at home, chances are good that you'll find an Italian store nearby selling freshly prepared pastas and raviolis that you can cook at home, in addition to a large number of other Italian foods (including freshly prepared Tiramisu).

Monday, April 19, 2010

Why I use Google Voice for SMS

I have an iPhone with AT&T service, but I recently started using Google Voice for most of my SMS needs. SMSes sent to my Google Voice number are forwarded to my email. It works great for me:
  • I don't want to pay AT&T any more than I have to. I use AT&T out of necessity, not choice.

  • GMail has support for Exchange ActiveSync, and is the default Exchange account on my iPhone. So I get notified of the SMS in email on the phone as soon as it is received (assuming I have data coverage, which is pretty much all of the time) giving me the equivalent of the native SMS experience.

  • The Google Voice extension for Chrome. Chrome is my default browser, I'm in front of a computer for a large part of the day, and my browser is usually open. I can read, send and reply to SMSes using a full keyboard and without exiting the browser, instead of pulling out my iPhone and fiddling with the tiny keyboard to type out a message. So much more convenient!

  • All my SMSes are archived and made searchable not only by Google Voice, but also by GMail. Win.

Sunday, April 18, 2010

Living Every Developer's Wet Dream

Back in June I wrote about new technologies I had started working with and my experiences learning those technologies. Having continued working with most of those since then, I've become incredibly comfortable and proficient with them. It's pretty normal to have a week where I write a bunch of PHP, a few SQL queries, some fancy Javascript, learn new CSS tricks, hack up features in client software using Win32/Cocoa, debug some corner-case issue using logs and a mini-dump and also write some straight up C/C++ server side code with unit tests. Initially, jumping languages and client/server boundaries came with large context-switch penalties but it's become almost second nature now, which feels very empowering and has had a dramatic impact on productivity.

Then, there are the one-offs - new technologies that need to be learnt, integrated into the product/service (in a way that's easy to understand/change in the future) and then 'forgotten' before moving on to the next one. These tend to be client-side technologies usually - server side stuff is much more on-going and stable. Some of the 'bigger' things I've done in the last 6-9 months that would fall into this one-off category are:
  • Fiddling around with HTML5 <audio> and <video> before giving up
    For all the promise of new HTML5 multimedia standards, the sad reality is that we are far from any consensus. Different stakeholders have different motivations, which leaves the issue of a single standard codec up in the air. The proprietary nature of Flash, licensing issues surrounding H.264, Apple's opposition to including Ogg Theora in Quicktime and Microsoft's silence have resulted in the current stalemate. And this shows when you experiment with the different browsers. Development for web/mobile-web is poised to become radically easier once/if this is sorted out, but there is a lot of pain to be endured before then - pain that wasn't worth it to me presently.

  • Flash audio support
    Having decided to not want to build a flash media plugin myself, I first played around with this MP3 Player and later switched to the much more powerful SoundManager2. With a few tweaks, I was able to make it do exactly what I wanted.

  • Facebook Connect
    For single sign-on and any sort of social media integration, Facebook Connect is a must have. Having implemented Facebook Connect for my website and for iPhone, I must say that I am very pleased with the facilities offered by the platform and the consistency across client/server applications. XFBML and FQL are nice touches. My only gripe is that, for a mature and widely adopted platform, documentation seems disproportionately poor as compared to the richness of the Platform APIs.

  • Online Payments
    There's a lot you learn about how safe online transactions really are (they're not) when you dive into how payments really work. I developed a quick payment portal using Payflow Pro by PayPal. Can't really vouch for Payflow Pro yet, though. A few weeks after I finished working with Payflow Pro, PayPal X was announced. I'll eventually get around to migrating to it, I guess.

  • Identicons
    Simple, cute, pretty. I use the ones provided by Gravatar.

  • Javascript Craziness
    One of the crazier things I've done, among others, with Javascript is to try and emulate a desktop-software like user experience in a website. Dragging, dropping, keyboard navigation, sorting, multi-select with Ctrl/Shift/Meta... oh boy! It look a while, but with some help from jQuery UI (the different components work great individually, but try to also make a sortable a draggable and a selectable, and you're in a world of hurt) and a lot of long nights, I finally got a lot of this done. For Forefox. Then I spent some time making it work for Chrome. And then a whole bunch of more time making it work for IE8. It sorta works in IE7, and not at all in IE6, but honestly if you're still using those browsers you should be chastised. Someday, I want to wrap all this into my own little plugin that others can use. If you told me a year ago that I would be some kind of JS nut, I would have laughed. Ha!

  • iPhone web-app (before I ditched it for a full blown iPhone app)
    Not wanting to deal with Apple and their strange AppStore practices, I decided to try and be really creative with native Quicktime support built into Safari on the iPhone. I almost got all the way to hacking up really neat functionality, complete with callback from Quicktime into my website, background playback and an rich interactive experience. With Flash coming soon to other mobile platforms, this would have been ideal for providing mobile experiences without needing multiple platform-specific applications. However, the iPhone web app felt clumsy and not polished, so I ditched it and went the iPhone app route. I reserve comment on my experiences with development for the iPhone - my app hasn't been submitted/approved yet. ;)
    I also foresee myself playing with other mobile development platforms in the very near future. I think a blog post comparing my experiences with the different platforms might make for good reading.
In addition to these one-offs, there are the perpetual how-will-it-scale and how-will-it-perform issues that need to be considered for every little feature. One of the harder lessons for me was to change my default mindset about how I approached design. While I wouldn't say I optimized prematurely, my design was heavily influenced by potential for optimization. (segue: This, I now realize, is a result of my years at Microsoft. At companies like Microsoft, you do things once and you do them right - rapid iteration is often not possible or not practical. So, while this approach has its merits it's not ideal for agility in startup environments). The new approach that's imbued into me now is to build it quick and make it simple first. Simplicity is the bedrock of agility. Simpler code is easier to revisit and to scale. Complexity is technical debt that you don't really need or want.

What's the biggest thing I've learnt in the last year? I learnt how to learn efficiently.
You can spend hours studying/learning about something and barely scratch the surface. Or you can study to retain information for very short periods of time. Exceptional developers are able to identify when they need to deep-dive vs when they need to simply skim the surface. They can instinctively answer the "How do you learn enough to make well informed decisions and yet don't spend a disproportionate amount of time learning without producing?" question. Transient learning is only good when you never need to revisit a decision - such cases are rare. Otherwise, learning to execute quickly and leaving a trail behind you is a crucial skill. I believe I'm well on my way down this path.

So yes, in many ways I am living a developer's wet-dream. And fortunately, I am able to do a little more than just development stuff. But I know this probably won't last forever. This series of blog posts is my attempt to capture my memories for when I'm not doing this everyday anymore.