What is your favorite API?

Some APIs are just great.

What are some of the funniest, weirdest, ugliest, most beautiful, easiest to understand, most rewarding once you know it or just in general awesome API that you have used?

I design APIs from time to time, and like to peruse API documentation in my spare time for inspiration (yes, I’m quite dull).

Some of my favorites include these gems from BeOS:

// Returns 1 if the computer is on. If the computer isn't on,
// the value returned by this function is undefined.
int32 is_computer_on();

// Returns the temperature of the motherboard if the computer
// is currently on fire. Smoldering doesn't count.
// If the computer isn't on fire, the function returns some
// other value.
double is_computer_on_fire();

This very recent classic in Android SDK:


// What a Terrible Failure: Report a
// condition that should never happen.
public static int wtf (String tag, String msg)

And this (also in Android):


// Returns "true" if the user interface is
// currently being messed with by a monkey.
public static boolean isUserAMonkey()

So, what are some of your favorite API-calls and why?

Would you think less of a “funny” API or would you rather enjoy using it more? Do you think there’s danger in being funny in code? What is your experience?

‘NSInvalidUnarchiveOperationException’, reason: ‘Could not instantiate class named MKMapView’

Did you just get hit by this cute error, even after adding MapKit to your Frameworks in Xcode? I did and the fix was not exactly obvious if you’re not used to Xcode.

You need to double-click your target and under General you must add MapKit under Linked Libraries. Next you should be good to go!

Here’s a few screenshots to show you how you do it (click for full size):

Here’s the full stacktrace for all you Googlers:

[Session started at 2011-03-13 17:59:56 +0100.]
2011-03-13 17:59:59.254 MyApplication[3491:207] *** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: 'Could not instantiate class named MKMapView'
*** Call stack at first throw:
(
0 CoreFoundation 0x025aeb99 __exceptionPreprocess + 185
1 libobjc.A.dylib 0x026fe40e objc_exception_throw + 47
2 CoreFoundation 0x02567238 +[NSException raise:format:arguments:] + 136
3 CoreFoundation 0x025671aa +[NSException raise:format:] + 58
4 UIKit 0x00598bdf UINibDecoderDecodeObjectForValue + 309
5 UIKit 0x00598dc2 UINibDecoderDecodeObjectForValue + 792
6 UIKit 0x0059a179 -[UINibDecoder decodeObjectForKey:] + 398
7 UIKit 0x00300a3f -[UIView initWithCoder:] + 642
8 UIKit 0x005994ca UINibDecoderDecodeObjectForValue + 2592
9 UIKit 0x0059a179 -[UINibDecoder decodeObjectForKey:] + 398
10 UIKit 0x004bfd77 -[UIRuntimeConnection initWithCoder:] + 212
11 UIKit 0x005994ca UINibDecoderDecodeObjectForValue + 2592
12 UIKit 0x00598dc2 UINibDecoderDecodeObjectForValue + 792
13 UIKit 0x0059a179 -[UINibDecoder decodeObjectForKey:] + 398
14 UIKit 0x004bf034 -[UINib instantiateWithOwner:options:] + 804
15 UIKit 0x004c0eb5 -[NSBundle(UINSBundleAdditions) loadNibNamed:owner:options:] + 168
16 UIKit 0x0037695f -[UIViewController _loadViewFromNibNamed:bundle:] + 70
17 UIKit 0x00374675 -[UIViewController loadView] + 120
18 UIKit 0x0037454f -[UIViewController view] + 56
19 UIKit 0x0038673e -[UITabBarController transitionFromViewController:toViewController:transition:shouldSetSelected:] + 120
20 UIKit 0x003852b2 -[UITabBarController transitionFromViewController:toViewController:] + 64
21 UIKit 0x0038708c -[UITabBarController _setSelectedViewController:] + 263
22 UIKit 0x00386efb -[UITabBarController _tabBarItemClicked:] + 352
23 UIKit 0x002ca7f8 -[UIApplication sendAction:to:from:forEvent:] + 119
24 UIKit 0x004c72d0 -[UITabBar _sendAction:withEvent:] + 422
25 UIKit 0x002ca7f8 -[UIApplication sendAction:to:from:forEvent:] + 119
26 UIKit 0x00355de0 -[UIControl sendAction:to:forEvent:] + 67
27 UIKit 0x00358262 -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 527
28 UIKit 0x00355d97 -[UIControl sendActionsForControlEvents:] + 49
29 UIKit 0x002ca7f8 -[UIApplication sendAction:to:from:forEvent:] + 119
30 UIKit 0x00355de0 -[UIControl sendAction:to:forEvent:] + 67
31 UIKit 0x00358262 -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 527
32 UIKit 0x00356e0f -[UIControl touchesEnded:withEvent:] + 458
33 UIKit 0x002ee3d0 -[UIWindow _sendTouchesForEvent:] + 567
34 UIKit 0x002cfcb4 -[UIApplication sendEvent:] + 447
35 UIKit 0x002d49bf _UIApplicationHandleEvent + 7672
36 GraphicsServices 0x02e0a822 PurpleEventCallback + 1550
37 CoreFoundation 0x0258fff4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 52
38 CoreFoundation 0x024f0807 __CFRunLoopDoSource1 + 215
39 CoreFoundation 0x024eda93 __CFRunLoopRun + 979
40 CoreFoundation 0x024ed350 CFRunLoopRunSpecific + 208
41 CoreFoundation 0x024ed271 CFRunLoopRunInMode + 97
42 GraphicsServices 0x02e0900c GSEventRunModal + 217
43 GraphicsServices 0x02e090d1 GSEventRun + 115
44 UIKit 0x002d8af2 UIApplicationMain + 1160
45 MyApplication 0x00002266 main + 84
46 MyApplication 0x00002209 start + 53
)
terminate called after throwing an instance of 'NSException'

Icon sizes for iOS and Android

When doing cross-platform projects we need a bunch of different sizes for the application icon in order for it to be useable for both the Android Market and Apple App Store. The sizes and filenames below are what I corrently use for iPod, iPhone, iPad and Android handsets as well as the promotional graphics.

iOS

Size (width x height) Name Usage Platform
320×480 Default.png Launch image for iPhone and iPod touch (only one available in iOS < 3.2) ios
320×480 Default~iphone.png Launch image for iPhone and iPod touch (to separate from ~ipad) ios
640×960 Default@2x.png Launch image for iPhone high resolution (iPhone 4) (also Default@2x~iphone.png) ios
768×1004 Default-Portrait.png Launch image for iPad (also Default~ipad.png) ios
768×1004 Default-PortraitUpsideDown.png Launch image for iPad (takes precedence over -Portrait) ios
1024×748 Default-Landscape.png Launch image for iPad ios
1024×748 Default-LandscapeLeft.png Launch image for iPad (takes precedence over -Landscape) ios
1024×748 Default-LandscapeRight.png Launch image for iPad (takes precedence over -Landscape) ios
512×512 iTunesArtwork App Store icon ios
114×114 Icon@2x.png Application/web clip icon for iPhone high resolution (iPhone 4) ios
72×72 Icon-72.png Application/web clip icon for iPad ios
58×58 Icon-Small@2x.png Settings/Spotlight icon for iPhone high resolution (iPhone 4) ios
57×57 Icon.png Application/web clip icon for iPhone and iPod touch ios
50×50 Icon-Small-50.png Spotlight icon for iPad ios
29×29 Icon-Small.png Settings/Spotlight icon for iPhone and iPod touch and Settings for iPad ios
22×29 ~ my-document-type.png Document icon for custom document types for iPhone and iPod touch ios
44×58 ~ my-document-type@2x.png Document icon for custom document types for iPhone high resolution (iPhone 4) ios
64×64 ~ my-document-type-small.png Document icon for custom document types for iPad (small) ios
320×320 ~ my-document-type.png Document icon for custom document types for iPad (large)/td>

ios
~20×20 ~ toolbar.png Toolbar and navigation bar icon for iPhone, iPod touch and iPad ios
~40×40 ~ toolbar@2x.png Toolbar and navigation bar icon for iPhone high resolution (iPhone 4) ios
~30×30 ~ tabbar.png Tab bar icon for iPhone, iPod touch and iPad ios
~60×60 ~ tabbar@2x.png Tab bar icon for iPhone high resolution (iPhone 4) ios

Android

Size (width x height) Name Usage Platform
36×36 ~ ic_launcher-ldpi.png Launcher/Menu icon, ldpi android
48×48 ~ ic_launcher-mdpi.png Launcher/Menu icon, mdpi android
72×72 ~ ic_launcher-hdpi.png Launcher/Menu icon, hdpi android
12x~19 ~ ic_status_ldpi.png Status bar (Android 2.3+), ldpi android
16x~25 ~ ic_status_mdpi.png Status bar (Android 2.3+), mdpi android
24x~38 ~ ic_status_hdpi.png Status bar (Android 2.3+), hdpi android
19×19 ~ ic_status_ldpi.png Status bar (Android 2.2 and below), ldpi android
25×25 ~ ic_status_mdpi.png Status bar (Android 2.2 and below), mdpi android
38×38 ~ ic_status_hdpi.png Status bar (Android 2.2 and below), hdpi android
24×24 ~ ic_tab_dialog_list_ldpi.png Tab/Dialog/List View icon, ldpi android
32×32 ~ ic_tab_dialog_list_mdpi.png Tab/Dialog/List View icon, mdpi android
48×48 ~ ic_tab_dialog_list_hdpi.png Tab/Dialog/List View icon, hdpi android

By size

Size (width x height) Name Usage Platform
12x~19 ~ ic_status_ldpi.png Status bar (Android 2.3+), ldpi android
16x~25 ~ ic_status_mdpi.png Status bar (Android 2.3+), mdpi android
19×19 ~ ic_status_ldpi.png Status bar (Android 2.2 and below), ldpi android
~20×20 ~ toolbar.png Toolbar and navigation bar icon for iPhone, iPod touch and iPad ios
22×29 ~ my-document-type.png Document icon for custom document types for iPhone and iPod touch ios
24x~38 ~ ic_status_hdpi.png Status bar (Android 2.3+), hdpi android
24×24 ~ ic_tab_dialog_list_ldpi.png Tab/Dialog/List View icon, ldpi android
25×25 ~ ic_status_mdpi.png Status bar (Android 2.2 and below), mdpi android
29×29 Icon-Small.png Settings/Spotlight icon for iPhone and iPod touch and Settings for iPad ios
~30×30 ~ tabbar.png Tab bar icon for iPhone, iPod touch and iPad ios
32×32 ~ ic_tab_dialog_list_mdpi.png Tab/Dialog/List View icon, mdpi android
36×36 ~ ic_launcher-ldpi.png Launcher/Menu icon, ldpi android
38×38 ~ ic_status_hdpi.png Status bar (Android 2.2 and below), hdpi android
~40×40 ~ toolbar@2x.png Toolbar and navigation bar icon for iPhone high resolution (iPhone 4) ios
44×58 ~ my-document-type@2x.png Document icon for custom document types for iPhone high resolution (iPhone 4) ios
48×48 ~ ic_launcher-mdpi.png Launcher/Menu icon, mdpi android
48×48 ~ ic_tab_dialog_list_hdpi.png Tab/Dialog/List View icon, hdpi android
50×50 Icon-Small-50.png Spotlight icon for iPad ios
57×57 Icon.png Application/web clip icon for iPhone and iPod touch ios
58×58 Icon-Small@2x.png Settings/Spotlight icon for iPhone high resolution (iPhone 4) ios
~60×60 ~ tabbar@2x.png Tab bar icon for iPhone high resolution (iPhone 4) ios
64×64 ~ my-document-type-small.png Document icon for custom document types for iPad (small) ios
72×72 Icon-72.png Application/web clip icon for iPad ios
72×72 ~ ic_launcher-hdpi.png Launcher/Menu icon, hdpi android
114×114 Icon@2x.png Application/web clip icon for iPhone high resolution (iPhone 4) ios
320×480 Default~iphone.png Launch image for iPhone and iPod touch (to separate from ~ipad) ios
320×320 ~ my-document-type.png Document icon for custom document types for iPad (large) ios
320×480 Default.png Launch image for iPhone and iPod touch (only one available in iOS < 3.2) ios
512×512 iTunesArtwork App Store icon ios
640×960 Default@2x.png Launch image for iPhone high resolution (iPhone 4) (also Default@2x~iphone.png) ios
768×1004 Default-PortraitUpsideDown.png Launch image for iPad (takes precedence over -Portrait) ios
768×1004 Default-Portrait.png Launch image for iPad (also Default~ipad.png) ios
1024×748 Default-LandscapeRight.png Launch image for iPad (takes precedence over -Landscape) ios
1024×748 Default-LandscapeLeft.png Launch image for iPad (takes precedence over -Landscape) ios
1024×748 Default-Landscape.png Launch image for iPad ios

I used a very simple Sinatra application to generate these tables as I just know there are a bunch more sizes that should be here, and I probably want another layout soon. Plus I love to find excuses for hacking a little bit :) If you’re like me, see this gist for the source.

Sources:

extThree20JSON dependencies using ttmodule

I’m getting started with Three20, an iOS framework with some nice eye candy and various nice-to-have features.

While attempting to use the extThree20JSON module and adding the project dependencies using ttmodule.py I ran into the following:

$ python three20/src/scripts/ttmodule.py -v  -p acando-directory/ios-client/AcandoMobile/AcandoMobile.xcodeproj:AcandoMobile -c Debug -c Release extThree20JSON
INFO:root:acando-directory/ios-client/AcandoMobile/AcandoMobile.xcodeproj target:AcandoMobile guid:xxxxx prodguid: xxxxxxx prodname: Acando.app
INFO:root:Checking dependencies...
INFO:root:Existing dependencies:
INFO:root:	Three20UICommon
INFO:root:	Three20
INFO:root:	Three20UINavigator
INFO:root:	Three20Core
INFO:root:	Three20UI
INFO:root:	Three20Network
INFO:root:	Three20Style
INFO:root:Checking dependency:extThree20JSON
ERROR:root:This is fatal: Unable to find the build phases from your target at: /Users/pal/projects/three20/src/extThree20JSON/extThree20JSON.xcodeproj/project.pbxproj
ERROR:root:This is fatal: Unable to find the build phases from your target at: /Users/pal/projects/three20/src/extThree20JSON/extThree20JSON.xcodeproj/project.pbxproj
Failed to get dependencies; it's possible that the given target doesn't exist.

Now, there are a bunch of other reasons this can fail, but for me I just had to decide which JSON implementation I wanted to use by adding :extThree20JSON+SBJSON to the end of the command, like so:

python three20/src/scripts/ttmodule.py -v -p acando-directory/ios-client/AcandoMobile/AcandoMobile.xcodeproj:AcandoMobile -c Debug -c Release extThree20JSON:extThree20JSON+SBJSON

MyApplicationAppDelegate.h:13: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘interface’

Aww.

If you’re doing iPhone/iPad/iOS development (as I am at the moment), you might have gotten what I will now deem the infamous error: expected '=', ',', ';', 'asm' or '__attribute__' before 'interface' error.

I call it infamous because it seems it can be caused by about a gazillion different errors and can thus be quite hard to pinpoint. In my embarassing case, I got about 10 of these due to a single, superfluous character at the start of a sourcefile. A single char before a comment, like so: w//.

Now, commiting code more often would have helped me as the diff would not have been as long, but I eventually just tried the last working checkout and had to retrace my changes one by one until that extra w stared me in the eye.

Oh well, here’s hoping it helps some poor developer like myself.

NoMethodError with Carrierwave & ActiveRecord

Hacking on a Padrino-powered application and need file uploads? I needed a nice way to do image uploads and decided to go with Jonas Nicklas’ carrierwave to add an image field to my ActiveRecord model.

Current version of carrierwave is/was 0.4.10 and I got this error:

Turns out this was fixed in master and is available as version 0.5.0.beta2.

In order to use this version, add/update the following in you Gemfile:

gem 'carrierwave', "0.5.0.beta2"

Good luck!

Creating a REST-based API for PrestaShop, part 1

I’m currently helping a client with integrations between custom, in-house systems and their upcoming PrestaShop-based e-commerce site. In doing this job it’s clear they will need a two-way synchronization scheme for products/articles in PrestaShop (PS) (set stock levels, adjust base price, send orders, read order statuses, CRUD products etc).

Now, the default PS-way (from reading the forums and blog posts) seem to be either repetitively importing/exporting CSV-files, using WebHooks or going straight to the database. Neither of these options seem right for the task, so I started thinking on what would feel right.

What if PrestaShop exposed an API for handling products, categories, tags, orders etc? Wouldn’t that be nice? A simple, REST-based API would be even sweeter, but I’d settle for pretty much any variant of decent API. I’ve since used my Google-fu, but to no avail. Seems I cannot find examples of anyone doing something like this, despite the 50k registered community users in the forums and the reported 33k shops using PS.

Oh well, I’ll have to create my own API and deploy it as a configurable module for PrestaShop. This is the start of that journey.

Proposed API

I like clean, well-designed REST API’s, so the odds are low on this being my attempt at such an API. Here’s the proposed, minimal Article API:

URL POST GET PUT DELETE HEAD
/api/articles Create new Article List all Articles Replace all Articles with supplied set Delete all Articles List only Article ID’s and short name
/api/articles/ {id} Add new Article with defined ID Get Article Update Article Delete Article Get ID and short name of Article
/api/articles/ {id}/images Add new image to Article List all images for Article Replace all images with supplied set Delete all images for this Article List only image paths

This pattern is obviously extendable to tags, Manufacturers, Suppliers, Orders, Customers, Customer Groups, Stats etc.

Stay tuned for more on this as I get further!

Learning more

Here’s some great resources that helped me out in a big way, check them out!

Trek: Get Your API Right
Mailchimp’s nice API
Flickr’s really basic and nice API
Shopify has evolved their API in a great way
Wikipedia’s page on REST has some good content as well
HTTP-code reference in a non-painful way
Creating a REST API with PHP
Test-client for REST API (available for Mac as well)

Find Java classes in the classpath

My brother Oskar needed to read some Java classes from disk, so I whipped up some code to show a possible way. Then I figured you might like it as well.

Here it is (and find the latest version at GitHub where you can submit your improved fork)

Ain’t java short and sweet? ;)

[java]

package com.palbrattberg.classpathtools;

import java.io.File;
import java.io.FilenameFilter;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
* Find classes in the classpath (reads JARs and classpath folders).
*
* @author P&aring;l Brattberg, brattberg@gmail.com
* @see http://gist.github.com/pal
*/
@SuppressWarnings("unchecked")
public class ClasspathInspector {
static boolean DEBUG = false;

public static List<Class> getAllKnownClasses() {
List<Class> classFiles = new ArrayList<Class>();
List<File> classLocations = getClassLocationsForCurrentClasspath();
for (File file : classLocations) {
classFiles.addAll(getClassesFromPath(file));
}
return classFiles;
}

public static List<Class> getMatchingClasses(Class interfaceOrSuperclass) {
List<Class> matchingClasses = new ArrayList<Class>();
List<Class> classes = getAllKnownClasses();
log("checking %s classes", classes.size());
for (Class clazz : classes) {
if (interfaceOrSuperclass.isAssignableFrom(clazz)) {
matchingClasses.add(clazz);
log("class %s is assignable from %s", interfaceOrSuperclass, clazz);
}
}
return matchingClasses;
}

public static List<Class> getMatchingClasses(String validPackagePrefix, Class interfaceOrSuperclass) {
throw new IllegalStateException("Not yet implemented!");
}

public static List<Class> getMatchingClasses(String validPackagePrefix) {
throw new IllegalStateException("Not yet implemented!");
}

private static Collection<? extends Class> getClassesFromPath(File path) {
if (path.isDirectory()) {
return getClassesFromDirectory(path);
} else {
return getClassesFromJarFile(path);
}
}

private static String fromFileToClassName(final String fileName) {
return fileName.substring(0, fileName.length() – 6).replaceAll("/|\\\\", "\\.");
}

private static List<Class> getClassesFromJarFile(File path) {
List<Class> classes = new ArrayList<Class>();
log("getClassesFromJarFile: Getting classes for %s", path);

try {
if (path.canRead()) {
JarFile jar = new JarFile(path);
Enumeration<JarEntry> en = jar.entries();
while (en.hasMoreElements()) {
JarEntry entry = en.nextElement();
if (entry.getName().endsWith("class")) {
String className = fromFileToClassName(entry.getName());
log("\tgetClassesFromJarFile: found %s", className);
loadClass(classes, className);
}
}
}
} catch (Exception e) {
throw new RuntimeException("Failed to read classes from jar file: " + path, e);
}

return classes;
}

private static List<Class> getClassesFromDirectory(File path) {
List<Class> classes = new ArrayList<Class>();
log("getClassesFromDirectory: Getting classes for " + path);

// get jar files from top-level directory
List<File> jarFiles = listFiles(path, new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".jar");
}
}, false);
for (File file : jarFiles) {
classes.addAll(getClassesFromJarFile(file));
}

// get all class-files
List<File> classFiles = listFiles(path, new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".class");
}
}, true);

// List<URL> urlList = new ArrayList<URL>();
// List<String> classNameList = new ArrayList<String>();
int substringBeginIndex = path.getAbsolutePath().length() + 1;
for (File classfile : classFiles) {
String className = classfile.getAbsolutePath().substring(substringBeginIndex);
className = fromFileToClassName(className);
log("Found class %s in path %s: ", className, path);
loadClass(classes, className);
}

return classes;
}

private static List<File> listFiles(File directory, FilenameFilter filter, boolean recurse) {
List<File> files = new ArrayList<File>();
File[] entries = directory.listFiles();

// Go over entries
for (File entry : entries) {
// If there is no filter or the filter accepts the
// file / directory, add it to the list
if (filter == null || filter.accept(directory, entry.getName())) {
files.add(entry);
}

// If the file is a directory and the recurse flag
// is set, recurse into the directory
if (recurse && entry.isDirectory()) {
files.addAll(listFiles(entry, filter, recurse));
}
}

// Return collection of files
return files;
}

public static List<File> getClassLocationsForCurrentClasspath() {
List<File> urls = new ArrayList<File>();
String javaClassPath = System.getProperty("java.class.path");
if (javaClassPath != null) {
for (String path : javaClassPath.split(File.pathSeparator)) {
urls.add(new File(path));
}
}
return urls;
}

// todo: this is only partial, probably
public static URL normalize(URL url) throws MalformedURLException {
String spec = url.getFile();

// get url base – remove everything after ".jar!/??" , if exists
final int i = spec.indexOf("!/");
if (i != -1) {
spec = spec.substring(0, spec.indexOf("!/"));
}

// uppercase windows drive
url = new URL(url, spec);
final String file = url.getFile();
final int i1 = file.indexOf(‘:’);
if (i1 != -1) {
String drive = file.substring(i1 – 1, 2).toUpperCase();
url = new URL(url, file.substring(0, i1 – 1) + drive + file.substring(i1));
}

return url;
}

private static void log(String pattern, final Object… args) {
if (DEBUG)
System.out.printf(pattern + "\n", args);
}

private static void loadClass(List<Class> classes, String className) {
try {
Class claz = Class.forName(className, false, ClassLoader.getSystemClassLoader());
classes.add(claz);
} catch (ClassNotFoundException cnfe) {
log("ClassNotFoundException: Could not load class %s: %s", className, cnfe);
} catch (NoClassDefFoundError e) {
log("NoClassDefFoundError: Could not load class %s: %s", className, e);
}
}

public static void main(String[] args) {
// find all classes in classpath
List<Class> allClasses = ClasspathInspector.getAllKnownClasses();
System.out.printf("There are %s classes available in the classpath\n", allClasses.size());

// find all classes that implement/subclass an interface/superclass
List<Class> serializableClasses = ClasspathInspector.getMatchingClasses(Serializable.class);
for (Class clazz : serializableClasses) {
System.out.printf("%s is Serializable\n", clazz);
}
System.out.printf("There are %s Serializable classes available in the classpath\n", serializableClasses.size());
}
}
[/java]

Gimme closures baby!