Saturday, November 6, 2010

Adding a single static file

To add a single static file to your App Engine application, let's say your robots.txt file, add the following to the handlers section of your app.yaml:


- url: /robots.txt
  static_files: static/robots.txt
  upload: static/robots.txt

This assumes your robots.txt file lives in the directory static and that you want to access it from http:///robots.txt

My first try after looking at the docs missed the upload parameter.  Totally zoomed in on static_files and figured I was good to go.

Mac helpers

Some Mac-related things that have helped with the day-to-day:

  • UTC clock widget - detailed instructions here.  UTC is way easier to work with as a base for date/time normalization, but it's a pain to constantly have to convert in your head (especially if you travel).  Modifying the World Clock widget was simple.  Remember to look in /Library not /Library and make a backup just in case...
  • iTunes control - you control: tunes.  I use all of my Spaces and with this free utility I get iTunes music control no matter which Space I'm in (adds controls to your menu bar).

Tuesday, November 2, 2010

Google App Engine and Cron

  1. Stagger your cron tasks such that they don't resonate
  2. Limit cron time limits to 2 digits
Looking at the requests/second graph on my GAE dashboard recently, I noticed a pattern of medium peaks at one frequency and a pattern of large peaks at about half that frequency.  These were the result of the way I had set up my cron tasks.

Self-inflicted usage spikes have to be avoided when you need to live within per minute quotas.  Because of a natural inclination towards time anchors like whole and half hours, multiple cron tasks were often starting up at the same time even though they all had different intervals.  The large spikes were from when they would all start up at the same time.  Oops.

Changed the intervals to ones that didn't overlap so frequently and ran in the the error below: 

Error parsing yaml file:
Unable to assign value 'every 157 minutes' to attribute 'schedule':
schedule 'every 157 minutes' failed to parse: line 1:8 mismatched input u'7' expecting set None

Turns out that 'every n ...' is ok and 'every nn ...' is ok, but 'every nnn ...' is not.

Monday, July 19, 2010

Google App Engine and pytz

Here are some notes on my experience with making the Python date/time package pytz available to an application on Google App Engine (GAE).

The default pytz package is generally accepted to be unsuitable for GAE because of the load time.  Rodrigo Moraes has created a version called gaepytz that is more suitable to GAE's distributed architecture.


I downloaded the zip version from the Python site thinking that I could use zipimport as described at this GAE article: http://code.google.com/appengine/articles/django10_zipimport.html

GAE complained about not being able to find my reference to gaepytz.  A little tinkering around revealed that the only files necessary are the ones in the pytz folder and that either GAE or zipimport doesn't like hyphens in the zip.  So I unzipped the gaepytz zip, then repackage it with:

zip -r gaepytz.zip pytz

With this, the error saying that it couldn't find the gaepytz file went away and was replaced with the following:

IOError: [Errno 20] Not a directory: 
'.../gaepytz.zip/pytz/zoneinfo.zip'

Turns out that zipimport doesn't like nested zip files.  The next step was to unzip the gaepytz.zip file and just use the pytz folder directly.  This meant going from 1 file at 512kB to 6 files at 569kB which isn't the end of the world.

The next bit was to dig into the comments of the source code to figure out how to use the package because import pytz clearly wasn't the answer.  In the comments of one of the files is the helpful note:

import sys
sys.path.insert(0, 'gaepytz')
from pytz.gae import pytz

You only need the last line since the pytz folder is added directly and no longer in the zip file. A little more work than I was originally counting on, but glad to have it working.

Wednesday, April 7, 2010

All views are not created equal

Fun Android developer tip, custom views will not necessarily behave the same when added through XML instead of Java.

Summary
getResources().getDisplayMetrics().density will help with a majority of any scale issues.

Scenario
You created an Android game back in October 2008.  To this end, you used your old school AWT skills to create a custom view, an animation loop, and a touch event listener to get touches using XY coordinates.  Then you went back to your full time job because applications couldn't be sold and there was no advertising platform for Android, yet.

Fast forward to modern times.  You come back to this game and decide to try to add some advertising to support development of a new version of the game.  Whoa, there have been 6 new versions of Android released in the meantime and a lot of different phones are now on the market as well.  You build your game against 1.6 and 2.1 and test on your phones with 3 different size screens (QVGA, HVGA, WVGA -- you do have one of each, right?).  Everything works great.  There are a few details to clean up, but in general, cool beans.

You go to add an ad banner to your app and the example code all points to using XML layouts instead of setting everything up in your Activity.  Everything's set up to use XML layouts now et voila.  Something's wrong.  The ad banner shows up fine, and your game screen shows up fine but touch events are all screwy.  Let's say that a circle is drawn around where each touch happens.  On the HVGA phone, everything looks as it did before.  On the QVGA phone, the circles are way bigger than they should be.  On the WVGA phone, the circles are way smaller than they should be.  Given that everything worked fine when it was all Java, Android knows how to scale things for you, it just doesn't want to do it for you when you use an XML layout because Android is a good father.  Doing something for yourself even when it's already done for you is character building.

Solution
The issue is that you responded to XY touch events with the assumption that XY distance would always be XY distance.  This isn't true for different density displays.  X pixels on low density screens travels much further than the same X pixels on high density screens.  In XML, the general solution for dealing with this is adding dip to the end if things.  Density independent pixels...  mmm...  tasty...  Wait, the draw methods only have float arguments.  Where's my dip goodness?

final float scale = getResources().getDisplayMetrics().density;

Multiply all of your draw coordinates with this scale value and you'll be mostly done.

Bonus tip
If you have xml and xml-hdpi folders and everything works great on Android 1.6+, don't be surprised if things are borked on Android 1.5.  When the documentation says that unknown modifiers will be ignored, it doesn't mean you're all set and Cupcake will use xml and ignore xml-hdpi.  It means it will ignore the "hdpi" modifier and will consider the two folders equivalent.  It will just pick whichever resource it wants to use (read: it will usually choose xml-hdpi out of spite).  To fix this, rename xml-hdpi to xml-hdpi-v4.  "v4" means "use for API level 4 and higher".

Super bonus tip
If you're using values and values-land to store separate parameters to make a widget look nice in both portrait and landscape orientations, it'll work for Android 1.6+ not not for Android 1.5.  Fingers crossed that the 35-40% of Android users on 1.5 today don't have phones that change the home screen to landscape (Sprint, AT&T backflip, Europe, etc.).

Friday, March 19, 2010

Tricks to Layout Tricks #1

I spent a couple of days learning things about creating custom list rows and decided to share with the Android dev world.

When I needed to create a custom list item, with an icon to the left and 2 rows of text to the right, I came across Android Layout Tricks #1.  Using a relative layout for better performance sounded nice.  Defining elements relative to each other so that they automagically look right on various screen sizes sounded really nice.  But I wanted to avoid the hard coded row height.  That just didn't seem right.

Like many other developers, I track a bit on the obsessive side.  I was sure there must be some way to arrange my elements without any hard coded values so I spent a good couple of days trying out all sorts of attribute combinations.  I got the name and number too high, too low, too far apart, rendered in the same space, and one visible but not the other.  

But at the end of the day, I found that you couldn't entirely avoid hard coding.  The solution I settled on was to hard code the margins -- this seemed slightly better than a hard coded row height.

Final lesson: damn the torpedoes, use hard coded margins

------------------------

Here are a few other things I ran into which should be useful.

Tidbit 1: One thing to note about the code in the Android blog post is its use of layout_alignParentTop together with layout_alignParentBottom to center an element.  layout_centerVertical is a shorter and more reliable way of achieving the same effect.


Tidbit 2: Use LayoutInflater.inflate(layoutId, parent, false) with a non-null value for parent.

Unmentioned in the blog post is the use of LayoutInflater.inflate.  When you create your own ArrayAdapter, you need to use the version that takes (int resource, ViewGroup parent, boolean attachToRoot).  Even though you should set attachToRoot to false, that doesn't mean there shouldn't be a root.  Pass through the ViewGroup parent value and don't set it to null.  I discovered this in a forum post.


Tidbit 3: Stick with one row view for the list.

One failed line of research was to use two row views.  One view for when both rows of text in the list item were present and one for when only the bottom row was present.  Picking the correct view in ArrayAdapter.getView was a snap and everything seemed copacetic.  Done and ship.

Bug report, uh oh.  It turned out that one of my users was having issues when there were more than N items in the list.  I had tested and it looked ok; so I tested it again and it still looked ok.  It was tempting to disregard this bug report, but it was written in complete sentences so that gave it a fair bit of credence.

With yet another round of testing, I paid careful attention to the data I was entering and found the issue.  Before, I had only really checked the number of list items and the order of the phone numbers.  For the names, I usually entered "a".  Adding different names for each number revealed an Android optimization technique for rendering lists and the cause of the bug.  The first page of the list always looked ok, but after scrolling, the subsequent list item would show names that had already been displayed when it should have been using the "no name" view.  Whaaa?

Object creation and garbage collection are expensive operations, so it looks like the Android engineers decided to optimize things by 1. only instantiating as many row objects as needed to show the first page of items and then 2. reusing row objects that moved off the top and putting them at the bottom.  The assumption here is that each row view is the same kind of object.  If you use more than one kind of row view in a list, then the recycling algorithm will give you one kind of view when your code has asked for another.  So if your list might scroll, and of course it will, stick to one row view.