Friday, June 11, 2010

UIScrollView looping and lazy loading

Recently, I got to do some fun exploration of what you can do with a UIScrollView. Specifically, in the areas of Lazy Loading views/tiles, and looping around the scrollview when the user scrolls to the beginning or end. For this usage, the scroll 'view' is a full-screen window into the scrolling canvas which displays one of the contained views, one at a time, with the bounce-style scrolling behavior as you scroll left and right through the pages/views in the canvas.

Some notes follow:

Lazy Loading a UIScrollView

To lazy load views inside a UIScrollView, load greedily around the currently visible view by hooking into the

- (void)scrollViewDidScroll:(UIScrollView *)sender


method of UIScrollViewDelegate

Assuming a uniform page size in your UIScrollView, you can detect which page is being scrolled 'toward' with some math like this:

BOOL isScrollingRight = _scrollView.contentOffset.x > _previousContentOffsetX;

_mostRecentScrollWasRight = isScrollingRight;

_previousContentOffsetX = _scrollView.contentOffset.x;


CGFloat pageWidth = _scrollView.frame.size.width;


int scrollingToPageNum = isScrollingRight ? (ceil((_scrollView.contentOffset.x - pageWidth) / pageWidth) + 1) : (floor((_scrollView.contentOffset.x -pageWidth) / pageWidth) + 1);


And you can even calculate the percent of that page that is on-screen (assuming one page on-screen at a time) with

int percentOfScrollToPageOnscreen = isScrollingRight ? floor((((int)_scrollView.contentOffset.x % (int)pageWidth) / pageWidth)*100)

: floor((1 - ((int)_scrollView.contentOffset.x % (int)pageWidth) / pageWidth) * 100);


Use that percent as a gauge to know when to lazy load the next set of pages. I used 3% for the app I writing at the moment, and scrolling is fluid. It's important to note that you'll want to load the page you're scrolling to, and the pages around it, to avoid a visual flickering.

For a pattern of how to lazy load controllers themselves, see the PageControl example provided by Apple.

That's the bulk of the complexity for lazy loading views in a UIScrollView.

Looping a UIScrollView

To add the ability to loop around from the last item in your UIScrollView, to the first item, and vice versa, the simplest technique is to dupe your last page at the beginning, dupe your first page at the end, and then hook into

- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView


to detect the landing page like so

int landingPage = floor((self._scrollView.contentOffset.x - self._scrollView.frame.size.width / _pageCount) / self._scrollView.frame.size.width) + 1;


and scroll them 'around' to the other side (beginning when at the end, end when at the beginning) without any animation/visual clue:

if (landingPage == 0 ) {

[_scrollView scrollRectToVisible:CGRectMake(_pageSize.size.width*(_pageCount-2),0,_pageSize.size.width,_pageSize.size.height) animated:NO];

} else if (landingPage == (_pageCount-1)) {

[_scrollView scrollRectToVisible:CGRectMake(_pageSize.size.width*1,0,_pageSize.size.width,_pageSize.size.height) animated:NO];

}


Code for this entry is available in a gist: http://gist.github.com/434586

Monday, February 22, 2010

How I built FeederTweeter in less than a day (appengine/oauth/tweepy/feedparser/bitly)

In this post, I'll look at some basics of what it took to stitch together a simple appengine web app which checks an atom feed and posts new entries to a twitter account. This is mostly an exercise, though I do intend to use it personally. I realize there are existing sites (and blogging services) that provide this type of functionality out of the box.

I'm not planning on making the service itself publicly available, but I am open sourcing it. You can easily setup and deploy your own instance on appengine for your own purposes. It's not ready for multiple users or a public site, but I do think it is ready for you to use it personally on your own appengine account.

Let's get started.

Part 1: Parsing the atom feed

Universal Feed Parser is an excellent python library for parsing atom/rss feeds and ever since google added transperant urllib support to appengine (in v1.1.9), it's been even simpler to use this 3rd party lib. Here's an example of using feedparser:
 import feedparser
atomxml = feedparser.parse('http://netsmith.blogspot.com/feeds/posts/default')
entries = atomxml['entries']
You can then pull out titles, links, etc with expressions like these:
 print entries[0]['title'] # title of the first entry
print entries[1].link # link from the second entry
print [entry.title for entry in entries] # titles for all entries
Universal feed parser provides plenty of ways to get at the structured results of the Atom/rss feed. See Universal Feed Parser for some excellent concise examples.

To check for new entries, we could write a pubsubhub integration or poll the rss/atom feed every so often and check for new entries. Since my blog is still on blogger.com (crazy, I know!), we'll use a polling strategy (booo!) by creating a cron.yaml file that looks like:
cron:
- description: Check for new posts and tweet them
schedule: every 5 minutes
url: /tasks/poller
and then creating a recurring task that retrieves the feed and checks to see if there are any new posts like this:
...
url = 'http://netsmith.blogspot.com/feeds/posts/default'
d = feedparser.parse(url)
idsThatHaveAlreadyBeenTweeted = getAlreadyTweetedEntries(url) # retrieve from datastore

for entry in d['entries']:
if not entry.id in idsThatHaveAlreadyBeenTweeted:
tweet(entry)
updateTweetedEntries(url, entry) # updates the datastore
...
As far as fetching the feed and checking for new entries, that's pretty much it!

Part 2: Posting to twitter

Posting to twitter is a relatively straightforward process, and I was able to hack something together very quickly by building on the shoulders of some excellent libraries and examples (specifically tweepy and tweepy-examples/app-engine). Coincidentally, right as I reached the point where I got it all figured out, I saw Nick Johnson (google) post this article in which he outlines how to authenticate a user with twitter using appengine-oauth on appengine.

Rather than re-hash what he and others have explained fairly well (OAuth), I want to just recommend you either check-out the tweepy app engine example and start tweaking/experimenting from there or follow through Nick's article for a little bit more explanation. Either way, you need to be writing some code around oauth to really get your hands around it and either place is an excellent start.

I do want to point out an interesting wrinkle though when it comes to oauth on appengine.

Since you can specify a callback url for oauth, you'll want to use code like the following to set the appropriate oauth callback url depending on whether you're testing your app locally or running it in the cloud on appengine:

import os
if os.environ.get('SERVER_SOFTWARE', '').startswith('Devel'): # running on local server
TWITTER_CALLBACK = 'http://127.0.0.1:8080/oauth/callback'
else:
TWITTER_CALLBACK = 'http://yourapp.appspot.com/oauth/callback'
If you run into any 'unauthorized' messages while testing twitter oauth,
try:
  1. resetting you twitter api key
  2. ensuring that the twitter method you're using is one your authorized for -- i.e. - make sure you're set to read/write access at the twitter api key level if you're attempting to post a tweet.
Once you've successfully authorized through OAuth, post the tweet with tweepy:

# after a successful oauth authentication w/twitter
import tweepy

tweet = 'Hello twitter world'
auth = tweepy.OAuthHandler(TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET)
auth.set_access_token(thisUser.access_token_key, thisUser.access_token_secret)

api = tweepy.API(auth_handler=auth, secure=True, retry_count=3)
api.update_status(tweet) # post the tweet

When it comes to integrating with the twitter api on appengine, it's really easy and straightforward with tweepy, once you've figured out OAuth (which should only take a couple of hours at most).

Part 3: Shortening url's

Once you can parse atom feeds, authenticate users, and post to twitter, the only piece that's really missing is shortening those long (thanks again blogger!) blog urls. For this we'll use bit.ly. Here's a simple method for shortening a url with bit.ly:

from django.utils import simplejson
from google.appengine.api import urlfetch

class BitLy():
def __init__(self, login, apikey):
self.login = login
self.apikey = apikey

def shorten(self,param):
url = "http://" + param
request = "http://api.bit.ly/shorten?version=2.0.1&longUrl="
request += url
request += "&login=" + self.login + "&apiKey=" +self.apikey

result = urlfetch.fetch(request)
json = simplejson.loads(result.content)
return json

(btw, I had this clipped from somewhere but can't find the source, so if you know where it came from, please let me know and I'll add credit)

To use it write something like this:
bitly = BitLy(BITLY_LOGIN, BITLY_API_KEY)
shortUrl = bitly.shorten('www.yahoo.com')['results']['http://www.yahoo.com']['shortUrl']
If you don't want to use bit.ly, You can always quickly build and host your own url shortener on appengine (like this).

Part 4: Putting it all together

So far we've covered how to grab the feed, post to twitter, and shorten a url prior to posting. That+oauth is the bulk of what is necessary to understand how to build this simple web app.

To put it all together into a working appengine app is relatively simple once you have built an appengine project or two. If you haven't (or if you're impatient like me), it can help to have a good, simple, webapp appengine project template (for reference, mine is here) to get up an running quickly.

Wrapping up, I want to mention that most of this project was developed in a test driven manner using continuous testing to keep myself honest and verify and test behavior of external api's. I'm using Ale in conjunction with my project starter template for this.

Here's the source for FeederTweeter in a working appengine web app. It includes a simple (and horrible looking!) web ui that is set to be only accessible by the admin of the appengine account. There may be some slight differences from what is shown here -- usually for the sake of brevity in this blog post.

Friday, February 19, 2010

5 things you should know about developing for Google appengine

I've been building a few apps on google app engine over the last year (after spending 10 years doing java apps), and here are some things I noticed not many people are discussing when they mention appengine:
  1. It's pay for what you use, but it will probably be completely free for you. The free usage quotas are _generous_ and even when you reach them, the sophisticated billing google has set up allows you to 'budget' for particular resources. Even with a 'budget', _you only pay for what you use_ (rather than the typical monthly fixed fees in play with every other hosting provider).
  2. The google appengine team is working at a blazingly fast pace. In the last year, I've seen a flurry of releases with some of the most requested features and improvements (cursors to work past the 1000 results limit, auto-datastore retries, secure connections by default, better instrumentation/optimization tools, etc).
  3. Use Python unless you can't. The learning curve for the language is small and your web apps will be slimmer and more lightweight (as opposed to going the java route on app engine). There is a reason google hired Guido van Rossum and there is a reason he is working with the app engine teams. Python feels closer to the metal and anytime you're working with something that's constantly evolving, it is beneficial and efficient to be able to get to the things you need to without being roadblocked by too many layers of abstraction.
  4. Your apps deploy to appname.appspot.com by default. This is interesting for a few reasons: 1. It's free, 2. There are a limited number of names available, and 3. It means that to look like every other web app you'll most likely end up purchasing and linking a separate domain to your app. You can easily set up a custom domain+cname (i.e. - www.myapp.com) to use your app, but not yet a naked domain (myapp.com). Most apps are working around this by just setting up forwards/redirects with their domain hosting provider.
  5. I think this is going somewhere you may not have thought. Looking at the resources Google is putting behind app engine, and the way it lowers the cost of entry and the cost of infrastructure management for custom applications, I can tell you one thing: A behemoth like Google, that has already infiltrated enterprises with a 'drop-in' search appliance (after honing the inner workings with consumers), is not a far jump away from creating a 'drop-in' app hosting appliance/grid (after honing the inner workings with its consumers -- app developers).
btw, this post is a really elaborate excuse to test an app-engine based webapp I hacked together for myself to tweet out my new blog entries (example) automatically.

Wednesday, February 03, 2010

Angled linear gradients in sass/fancy-buttons

I'm so happy with how simple it is to make a nice looking button for a web-page with the combination of fancy-buttons and sass/compass that I just had to share:

Recently a design concept came back that included buttons (done in photoshop of course) in this style:



I had used fancy-buttons in the prototype of this project (www.coolchars.com). I was very happy that my html boiled down to simple 'button' tags and some fairly straight forward sass (which generates all the goobly-gook css). I was so happy with sass+fancy-buttons that I wanted to resist the urge to fall back to image based buttons for this angled gradient... so I did a little digging. It turns out you can do an angled gradient.

The following sass:
!lightgrey = #d8d8d8
!darkgrey = #adadad

button
+linear-gradient("left top", "right bottom", !lightgrey, !darkgrey, color_stop(65%, !lightgrey))
//...other styling irrelevant to the gradient example goes here...
can help produce a button that looks like:




The two keys to this are the directional cues ("left top", "right bottom") and the color_stop function. While we only used one gradient here, color_stop will let you do multiple color gradients stopping at different percentage points across a button if needed.

The only downside is that css generated for the angled linear gradient is pretty much webkit only right now. Firefox 3.6 has new angled gradient support and the author of fancy-buttons is currently working on adding FF support. Other browsers fallback to background color/image styling based on what you set up for your buttons in your sass files.

Detecting iPhone/iPod/iPad clients on Google App engine

Recently, while working on a fun little side project (www.coolchars.com), we needed to detect and render a very different page for iPhone users (no Flash and slightly different page structure to accommodate iPhone copy&paste)


When the iPhone (or iPod/iPad) requests a url, it includes a user-agent string like the following:


Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3


To detect and parse that from a webapp-based application on appengine, just do the following:


import os
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext.webapp import template

class MainPage(webapp.RequestHandler):
def get(self):
user_agent = self.request.headers.get('User-Agent', '').lower()
iphone = 'iphone' in user_agent or 'ipod' in user_agent

if iphone:
self.redirect('/index_iphone.html')
else:
self.redirect('/index.html')

application = webapp.WSGIApplication([('/', MainPage)], debug=True)

def main():
run_wsgi_app(application)

if __name__ == "__main__":
main()


Of course you can do something more sophisticated like parsing and rendering templates differently, but this is just an example.

A quick way to test it out is launch Safari, go to Develop->User Agent->Other, and pop-in the user-agent string at the top of this blog entry. Be sure to test it out on the simulator or the actual device since screen size will affect rendering as well.

Tuesday, December 08, 2009

Continuous Quality of .py files on app engine experiments

I've been working on a few small experiments with appengine capabilities (sending e-mail, receiving e-mail, sending and receiving instant messages over xmpp) . It has given me the chance to try Ale on some projects smaller than Flexvite, and I've really enjoyed it.

Typically I get the skeleton of the experiment up in about 60 seconds by using the helloworld quickstart, then adding pyflakes and watcher into the mix with 'ale install pyflakes' and 'ale install watcher'. Once I have spun up 'ale watcher', I've got something that is continuously checking my .py files for extraneous imports, undeclared vars, etc. It's really very nice to be told that you can loose two or three imports and simplify your codebase -- even when your codebase is small! Of course 'ale watcher' can also be used to trigger the running of unit testing or other checks, but for the experimental nature of these projects, that didn't seem necessary.

So for these experiments, my ale setup looks like:

$ ale

Syntax: ale <command>

Core commands:
list list commands currently recipes_installed
create create -- create the skeleton for a new command
install install
remove remove Uninstalls a command
search search commands available for install

Additional Commands:
gae google app engine .ale
pyflakes run pyflakes (lint tool) against the project or pyflakes [dir]
watcher monitors files matching a filespec for change and triggers command(s)

$


which is something I'm liking a lot.... you can see the commands I'm using with the project without a lot of extraneous stuff.

Starting it up is as simple as 'ale gae start' (which launches the server, and a browser to the app), and deploying it to the google cloud infrastructure (appengine) is as simple as 'ale gae deploy'.

It also turns out to be pretty simple to add these to the ale 'createapp' command (using the tarballs generated by github), so I went ahead and did that so that it's easy to pull one down quickly when you need it:


$ale createapp

createapp [templatename] -- create an app from a template
available app templates:
helloworld -- simple helloworld app
helloworldwebapp -- simple helloworld app using webapp fmk
xmppsendandreply -- simple xmpp (instant message) send and reply
emailreceive -- simple e-mail receive example
emailsendui -- simple e-mail send example

$

Friday, December 04, 2009

App engine [local] hello world in 60 seconds

I'm toying around with a little experiment at http://github.com/mpstx/Ale It helps you get a tool stack configured quickly to do appengine development without affecting your system's default python environment or installed apps -- something I spent more than a little time rolling by hand during the development of Flexvite. Here's a little preview:

To create an _Isolated_ (no global installs/sudos!), local App engine hello world on OS X in 60 seconds, do this:

  1. Environment setup :
    mkdir aehello; cd aehello

  2. Install Ale :
    git init; git submodule add git://github.com/mpstx/Ale.git .ale

  3. Init Ale :
    .ale/init.sh

  4. Install appengine :
     ale install gae

  5. Install createapp :
    ale install createapp

  6. Create a basic app :
    ale createapp helloworldwebapp

  7. Start local server and browser :
    ale gae start



You can now edit helloworld.py and refresh the browser...change should be reflected instantly.

To delete everything just rm -rf the aehello directory and you'll be totally clean again.

To Deploy it to the cloud...

  1. Go create a google app engine account.

  2. create an appid

  3. modify your app.yaml to reflect your new id

  4. To deploy to yourappid.appspot.com :
    ale gae deploy

Tuesday, September 22, 2009

I'm tired of typing "self." before every assert method in a python unit test

If you've spent anytime using the python unittest framework, and you have any appreciation for things being succinct, perhaps you will find something useful in this approach to co-opting the basic assert methods and making them available in a global context (as opposed to them only being available on the TestCase instance (through a 'self.' reference). It seems safe enough since these methods are generally not using a lot of instance state anyway -- just raising a particular exception. Enough talk. Here's the stuff:


class FakeTest(unittest.TestCase):
"""
only purpose is to provide a surrogate for providing global assert methods below
"""
def testNothing(self):
pass

assertMethodStealerTestCaseInstance = FakeTest(methodName='testNothing')

assertEquals = assertEqual = assertMethodStealerTestCaseInstance.assertEqual
assertNotEquals = assertNotEqual =assertMethodStealerTestCaseInstance.assertNotEqual


After typing 'self.assert...' hundreds of times, I can attest that that 10 lines is well worth it - besides the typing, it makes your tests easier to read too!

Saturday, September 19, 2009

A small jquery+iso+timezone+browser recipe

So, on my current project (Flexvite), I wanted dates and times to adjust themselves to the browser's timezone on-the-fly throughout our site and without any extra steps for the user (like setting a timezone on their account). HTTP posts will include a datetime with a timezone that you could parse out, but I wanted it for HTTP gets also, and without having to do any hidden frame or secret post or redirect tricks.

Perhaps some jquery guru will tell me this is horribly non-performant, but for now it works and meets our need so I sharing it here.

Here's the basic recipe:

Step 1: Go grab these excellent 2 libraries for javascript iso8601/rfc3339 datetime parsing and javascript date formatting. Include them in your page.

Step 2: When serving up your page, wherever you want a date or time localized to the user's timezone, create a div structured like this:


<span class="dateToLocalize">
<span class="dateIsoDate" style="display:none">2009-09-18T11:30:00-05:00</span>
<span class="dateFormat" style="display:none">h:MM tt</span>
<span class="date">11:30 am</span>
</span>

// Notice that it includes a hidden copy of the datetime in iso8601/rfc3339 format, as well as

// parameters to pass to the client-side datetime formatting function


Step 3: Use some jquery magic to find these structured div's on the client side, and adjust to the user's local (browser) timezone if necessary:


$('.dateToLocalize > .date').each(
function() {
d = new Date().setISO8601($(this).parent().find('.dateIsoDate').text());
format = $(this).parent().find('.dateFormat').text();
formattedDateHtml = d.format(format);
$(this).html(formattedDateHtml);
}
);

Step 4: Just run that function when your page loads and every date/time in the page (that is layed out with the aforementioned structure) will be automatically localized to the browser's timezone. sweeeet.

Wednesday, August 19, 2009

Hotkey screen capture and post to Campfire with Spark + Pyro + Applescript + OS X screencapture

I found something _reallly_ handy with applescript (finally!).

Here's the applescript to do an interactive screen capture (os x style) and post the result immediately to a campfire room.

property N : 0

set N to N + 1

set picPath to ((POSIX path of (path to desktop)) & "Picture_" & N & ".png") as string

do shell script "screencapture -i -tpng " & quoted form of picPath


tell application "Pyro"

upload picPath to room "roomoncampfire" in campfire "yourcampfiresubdomain.campfirenow.com"

end tell


I tied this to a keystroke with my tool of choice 'Spark':
















Pretty handy, and thanks to some of the existing articles out there, it didn't take long to put together either. Applescript win!

Inspired by:

Saturday, December 27, 2008

Sunday, September 21, 2008

App store observations

Having a few apps on the store, and having closely followed rankings, popularity and pricing over the past couple of monthes, here are a couple observations:
  • There are still plenty of business issues (mktg, customer service, ip, taxes, etc) to deal with even though Apple takes care of some (distribution, payment, part of taxes). I think there's some opportunity there (publishing houses, co-ops?, consolidation?), but I'm not sure what yet.
  • 'mimic' apps are making there way on to the store...sometimes better, sometimes not, often stealing wording and sample data straight from other apps and cross-posting reviews to drive traffic to their apps
  • Not a lot of apps that access critical consumer data (i.e. - my bank, my frequent travler info, etc) -- slow-moving large corps [opportunity]
  • Surprise market for apps for 'little kids' (parents appear to gladly pay 99c-4.99 to keep a kid busy for some period of time)
  • Great market for taking something that's specialized and mechanical (sleep sounds, chess clock, specialized calculator) and making a cheap digital version.
  • Still lots of new game concepts that haven't been tapped . . .
  • When you have something that's selling all over the world, it's a challenge to keep up with what's going on in all the different country-based app stores, and partitioning feedback, marketing, and sales to drive prioritization.
Apple is starting a free program for universities and students: http://developer.apple.com/iphone/program/university.html that will let them openly discuss and collaborate on iphone/touch development.

However, the NDA is still in effect for experienced developers and publishers, though the enforcement seems erratic (some developers are openly violating it in discussion forums, app descriptions, blogs, tweets, etc).

Sunday, July 27, 2008

New apps on the store RSS feeds

RSS feed for new apps on the app store:

feed://webobjects.mdimension.com/iPhoneApps.rss

- or -

feed://feeds.feedburner.com/RecentlyAddedIphoneApplications-PinchMedia

Thursday, July 17, 2008

Environment Optimization: Marco Polo + Synergy

Wow.  Synergy + Marco Polo = Lots of automatic goodness.

I've got a laptop and a home computer which occasionally cross paths.  When I bring the laptop home, I like to use both it and my home computer in parallel -- sometimes compiling/linking software on one, while doing research on the other.   Synergy works great for sharing my keyboard/mouse between them.  Now, with Marco Polo though, life just got even lazier!

Rather than manually starting the synergy daemons on both machines, Marco Polo let me set up some network 'evidence' policies (available wifi ssid + bonjour services) which detect when my computers are at home, near each other, or at work and execute various 'actions' based on that 'evidence'.  Some of the 'actions' I have set up thus far are :
  1. [Both machines] Automatically launch the synergy daemons (on both machines) to share my desk's keyboard/mouse when laptop & desktop are both at home
  2. [laptop] Launch my work chat client (iChat) at work, and my home chat (Adium) client at home.
  3. [laptop] Set my iChat status to 'at work' when at work.    
  4. [laptop] Set the default printer appropriately based on location.

Sunday, June 22, 2008

Interacting with GIT

Git interactive rocks!

It's a quick and easy way to stage several files [for a subsequent commit], regardless of location and name, without an IDE or ninja master scripting skills . . .example follows: [my typing in bold]

machine1$ git add -i
staged unstaged path
1: unchanged +7/-2 Classes/AllViewController.m
2: unchanged +2/-0 Classes/AppDelegate.h

*** Commands ***
1: [s]tatus 2: [u]pdate 3: [r]evert 4: [a]dd untracked
5: [p]atch 6: [d]iff 7: [q]uit 8: [h]elp
What now> 4

1: Classes/FilteredByGroupViewController.h
2: Classes/FilteredByGroupViewController.m
3: Classes/GroupsViewController.h
4: Classes/GroupsViewController.m
Add untracked>> 1-4
* 1: Classes/FilteredByGroupViewController.h
* 2: Classes/FilteredByGroupViewController.m
* 3: Classes/GroupsViewController.h
* 4: Classes/GroupsViewController.m
Add untracked>> [enter]
added 4 paths

*** Commands ***
1: [s]tatus 2: [u]pdate 3: [r]evert 4: [a]dd untracked
5: [p]atch 6: [d]iff 7: [q]uit 8: [h]elp
What now> q
Bye.

machine1$


update (July 1): just for comparison, a co-worker sent me one of those wonderful scripting commands for subversion to add all unknown files:

svn stat -u | awk '/^[?]/ { print $NF }' | xargs svn add


Monday, June 16, 2008

Time to GIT you a quick update

I've either been living on an island, or I'm guilty of being a slowly boiled frog depending on how you look at it.  

Let's compare the default output for 'svn up' vs. 'git pull' shall we?  My annotations are in bold...

In this corner: 'svn up' . . .

$ svn up

U Classes/ClassA.m                <-- _awesome_ single letter indicator for

U Classes/AppDelegate.m           <--   what happened to the file during the update

U Classes/DynamicSheetDelegate.m       

U Classes/AlertDelegate.m                

U Classes/SalesTransaction.m             

U App.xcodeproj/user.pbxuser             

Updated to revision 1028.        <-- _short_ summary

$


and the challenger: 'git pull' . . . 

$ git pull                                

remote: Counting objects: 58, done.               <- Keeps you apprised of comm.

remote: Compressing objects: 100% (48/48), done.  <-  rather than pauses w/o info

remote: Total 48 (delta 37), reused 0 (delta 0)

Unpacking objects: 100% (48/48), done.

From git://machineB/projectA

   f355111..99258fb  master     -> origin/master    <- SHA-1's of from and to 

From git://machineB/projectA                        <-  versions on remote repo 

* [new tag]         aftermemfixes -> aftermemfixes  <- New tags on remote repo 

Updating f355111..99258fb                           <- SHA-1's of current to new ver

Fast forward                                        <-  on local repo + current op.

 Classes/ClassA.m                       |   25 +-   

 Classes/AppDelegate.m                  |   11 +-   <- Counters and visual (ascii :)

 Classes/DynamicSheetDelegate.m         |    2 +    <-  indicators show amount of

 Classes/AlertDelegate.m                |    2 +    <-  change on per-file basis

 Classes/SalesTransaction.m             |    5 +-   

 App.xcodeproj/user.pbxuser             | 2510 ++++++++++++++++++--------------

 7 files changed, 1604 insertions(+), 1176 deletions(-)  <- Summary totals 

$




Sunday, June 15, 2008

WWDC 2008 wrap-up

I just got back from WWDC 2008 . . .  Unfortunately there is a confidentiality agreement that prevents me from posting any technical details here, but if you hop around the rumors/news sites, you can find most of the juicy details.

In general, is was a very well produced conference.   For me, the best thing about it was the chance to talk one-on-one with some of the engineers behind key technologies on the platform.  The venue and location were great and I definitely got the impression that Apple values their development community tremendously.  If the conference was any indication, there will be some very cool and innovative Apps coming out on the iPhone.





Does it GIT any better than this?

Cool GIT feature of the moment:   Content based tracking of files.

I renamed a file in a project, and then did a 'git rm', and 'git add' to update the index and subsequently committed and pushed the changes.  Then, on the origin repo, I executed a 'git status' to see what changes were pending.  GIT displayed:

# renamed:    AllViewController.h -> RootViewController.h

# renamed:    AllViewController.m -> RootViewController.m


Huh?   It recognized that the content was renamed even though I had only communicated a 'delete' and 'add' operation for the file.   Apparently this is because GIT does sha-1 on all content -- allowing it to know when you move content around (even if you don't tell it).  Nice!



Saturday, June 14, 2008

The Pragmatics of Innovation




This guy is one hell of a speaker and he has a really great (pragmatic) perspective on innovation.   The idea that people sometimes tend to look back at innovations without considering any hard facts is something of an understatement.  Scott explains that true innovation comes from rigorous habits, not doing what you're told sometimes, and not letting past failures stop you.    It's also interesting how people sometimes think innovations come entirely from 'magic moments'/epiphanies.   Best example Scott uses:  How they teach you in school that Newton came up with the idea of gravity when an apple fell on his head.   HA!  Listen to Scott's talk to hear the real story.  Fascinating stuff.


Tuesday, June 10, 2008

It's GIT'ing better all the time.

Goals:
  • I have some source code that I need to share between a desktop and laptop sporadically.
  • When I'm working on the code on the laptop (potentially disconnected from any network for several days), I want all the same scm style capabilities (tagging, branching, roll-backs) that I get when I'm working on the desktop.
  • I don't want the overhead of maintaining a server securely or paying for a hosted solution.
  • I'll only ever be actively developing on either the laptop or desktop at any given time, so merges are no concern [for now].
After watching the ever opinionated Linus, a few git casts, and reading Wincent's summary, I was gung-ho convinced GIT could do the job.

GIT Recipe #1. One-way pushes between two trusted peers.


Given:
Base dir

|-> Myproject dir

Step 1. Create a GIT repo and index on machine #1 for your project.
machine1$ cd /myproject
machine1$ git init
machine1$ git add .
machine1$ git commit

Step 2. Start the git server component on Machine #1. Warning this allows fully anonymous unauthenticated access, so only do this in a secured LAN environment -- and personally, I would only do it for short sporadic bursts of time even in that environment. For a more secure option (i.e. - apache/ssh/etc), see the git docs (it's a bit more work).
machine1$ git-daemon --verbose --export-all --enable=receive-pack --base-path=/parentof/myproject/dir

Step 3, clone the repo to machine 2.

machine2$ git clone git://machine1/myproject


To do work on machine 1, and send the updates to machine 2

On Machine 1 (origin repo), remove a file and commit the change.
machine1$ vi README.txt
machine1$ git add README.txt machine1
machine1$ git commit


On Machine 2, pull those changes across

machine2$ git pull

To do work on machine 2, and send the updates to machine 1

Make some change . . .
machine2$ vi README.txt
machine2$ git add README.txt
machine2$ git commit

Push those changes over to the origin repo

machine2$ git push

Then, on Machine A [be careful not to have any local changes], to get those changes into your working copy:
machine2$ git checkout -f


Bottom-line: The big benefit here is that I can commit, tag and branch at will on either machine without them being connected, and then sync them up when there is connectivity. There are git workflows which allow for that capability with larger groups, but I personally only need this one workflow (working while disconnected) to function at the moment.

Some of the better GIT Resources I've found:
  1. http://rails.wincent.com/wiki/Git_advantages
  2. http://www.youtube.com/watch?v=4XpnKHJAok8
  3. http://www.sourcemage.org/Git_Guide
  4. http://www.gitcasts.com
  5. http://www.rockstarprogrammer.org/post/2008/apr/06/differences-between-mercurial-and-git/
  6. http://cheat.errtheblog.com/s/git
  7. http://cheat.errtheblog.com/s/git_usage/
  8. http://ktown.kde.org/~zrusin/git/git-cheat-sheet-medium.png
  9. http://code.google.com/p/git-osx-installer/
  10. http://www-cs-students.stanford.edu/~blynn/gitmagic/index.html
  11. http://google-opensource.blogspot.com/2008/05/develop-with-git-on-google-code-project.html
  12. http://osteele.com/archives/2008/05/my-git-workflow
  13. http://ktown.kde.org/~zrusin/git/git-cheat-sheet-medium.png
  14. http://www.dekorte.com/blog/blog.cgi?do=item&id=2539

Saturday, June 07, 2008

Eric Schmidt - What can others copy from Google, how does his style compare to other CEO's


Great video with CEO Eric Schmidt on a variety of mgmt topics, challenges he faces at google, and how he tries to facilitate the focusing of the energies of passionate engineers and founders.  He alludes to what he thinks would be hard for others to copy, and what wouldn't.

One thing in particular that Eric mentions is along the lines of:  When you have smart people, encourage debate on anything and everything, BUT you MUST set a dead-line and make decisions in order to ensure your organization doesn't devolve into a purely academic environment.   He also talks about keeping people involved, how to challenge people and evaluate innovation ideas.  On challenging and evaluating innovative ideas, he seems to embrace a model similiar to Bill Gate's -- rigorously challenging down to the details and requiring hard facts.  Good stuff.


Saturday, May 17, 2008

Attaining Agile Mastery -- Forget the word Agile!

Here's a thought that has been bouncing around my head:

To attain mastery of practicing Agile, try never again using the word itself to explain things you do or why you do them.   Oh, don't get me wrong, the word 'Agile' is valuable for a assigning a name to the umbrella over those wonderful principles.   But, if you don't know WHY those principles are important, when to make exceptions to the those guidelines, or how to explain them to someone else then you're missing a big opportunity!

You see, saying "It's Agile", is not a good explanation for WHY you are doing something.   Oh, it's an explanation to be sure, but what exactly does it say about you when your explanation for why you do something equates to 'It's policy'.   It's most definitely a good way to a) end the conversation or b) leave your listener with the impression that you are some sort reasonless zealot.

Personally, I'm going to test out this theory.   I'll post something here if it blows up in my face :)

p.s. - I imagine there is also value to the word in a sales context (given the current climate), but since I'm just attempting to be a successful practitioner, let's ignore that context for now.
 



Sunday, January 27, 2008

From Tiger to Leopard

Well, I finally bit the bullet and got Leopard at home (on a mac mini).    So far, the transition from Tiger has been relatively painless. 
 
Here's a breakdown of the highs/lows for me compared to Tiger:
  • Spotlight performance: +1;  (usable!)
  • General OS responsiveness:  +1;
  • Safari Speed: +1;
  • Safari Reliability: -1; (has crashed on two different web sites for me so far! -- guess I'll go back to my camino/firefox combo)
  • Quicklook: +1;
  • Dock improvements (grids and fans):  +1;  
  • Time machine: ?; I set it up, but for me the jury is out on any backup software until I actually need to restore something.
  • Haven't found any major app incompatibilities yet.


Don Brown is working on serious Maven 2 improvements

Stumbled on this interesting work going on around Maven 2.  Don claims to have reduced some maven build times significantly.