Archive for December, 2009

Android Layouts Supporting Orientation

It’s been my experience that most developers don’t want to deal with screen rotation in their applications. They will either specifically code the app not to respond to rotation events (which can look weird when you have to slide the screen and use the keyboard), or they will forget that there are instances where the screen orientation rotates for you by default (and their portrait-centric layout goes nuts or gets cut off). This is one of those subtle details that can drag down the user experience in a big way if the developer is not careful.

If your platform of choice is Android, my only question if you’re afraid of screen rotation would be…why?  Android makes dealing with different orientations in your views very simple.  If you’ve opted to define your layouts with XML (which you probably should anyway), then all that is required is a second XML file defining the second orientation (probably landscape), and some resource syntax to describe to Android how and when to choose the proper layout.  Defining layouts in XML is so simple, adding another doesn’t requre significant effort.  The general steps are as follows:

  1. Create two folders in the resource bundle (“res” folder in the project): “layout” and “layout-land”
    • “layout” will hold all the XML layouts for portrait mode
    • “layout-land” will hold all the XML layouts for landscape mode
  2. Create an XML layout in both folders with the SAME name (i.e. main.xml)
    • There will now be a “res/layout/<name>.xml” and a “res/layout-land/<name>.xml”
  3. Build each layout to match the proper screen orientation
  4. POOF! You’re done.  Android will handle the rest at runtime.

So what’s happening?  Well, “land” is an alternate resource that Android recognizes for screen orientation.  Creating the resource directory “layout-land” tells Android specifics about how the contents of the directory are to be used…specifically layouts for landscape.

Let’s look at a quick example:

Let’s say I have a layout with four image buttons.  In portrait mode, I want those buttons to block in a 2×2 arrangement.  However, in landscape mode, that block runs off the screen, so I want them all in a single row.  I will define two layouts:

res/layout/main.xml

<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
        <TableRow>
            <ImageButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/image1"/>
            <ImageButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/image2"/>   
    </TableRow>
    <TableRow>
        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/image3"/>
        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/image4"/>
    </TableRow>
</TableLayout>

res/layout-land/main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <ImageButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/image1"/>
    <ImageButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/image2"/>
    <ImageButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/image3"/>
    <ImageButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/image4"/>
</LinearLayout>

Whenever the main.xml view is inflated, it will display either the TableLayout version or the LinearLayout version, depending on the screen orientation.

Adding Core Data Existing iPhone Projects

Core Data is a very powerful framework that Apple provides to developers for persisting data in their applications.  The primary advantage that is provided by Core Data is the ability to leverage efficient data storage technologies like SQLite, without forcing the developer to think in terms of query language; Core Data allows a developer to work with the data model in terms of objects.

There are many great books and articles on the proper use of Core Data; including Apple’s Core Data Tutorial.  The purpose of this article is simply to step the reader through adding the necessary piece to get Core Data into a previously created project; I defer to those other writings to describe the best techniques for Core Data use.

Apple has made the process of using Core Data in new projects very simple.  From the New Project… screen, many project templates include a checkbox titled Use Core Data for storage that tells Xcode to build the basics for Core Data into the project you create.  But what happens if you decide that Core Data’s advantages are going to help you, but you have already have your project set up in Xcode?  Where’s the button in the IDE to add Core Data to an existing project?

Below is a set of instructions on creating the context necessary to start using Core Data in your existing app.  In the code blocks, bold text is code that should already exist in your project.

Add the Missing Files

There are two files that must be added to your Xcode project in order to use Core Data; CoreData.framework and the .xcdatamodel

  • CoreData.framework
  1. From Xcode, control-click on the Frameworks folder inside the Groups & Files pane
  2. Select Add -> Existing Frameworks…
  3. Locate CoreData.framework, select it, and click the Add button
  • .xcdatamodel
  1. From Xcode, control-click on the Resources fold inside the Groups & Files pane
  2. Select Add -> New File…
  3. Select Resource from the iPhone OS group
  4. Select Data Model and click Next
  5. Give the file a name (the project name is a good choice) and click Next
  6. If your project does not have existing model classes that you would like to put into the data model, skip to step 7.  Otherwise, you may do so by selecting those classes on this screen and clicking Add to place them in the Selected Classes pane.
  7. Click Finish

Create the Missing Objects

Once all the necessary files are put into the project, the appropriate objects and methods need to be added to the application.

AppDelegate.h

Declare three new objects in the application delegate’s header file for the ManagedObjectModel, ManagedObjectContext, and PersistentStoreCoordinator. A convenience method, applicationDocumentsDirectory, is also defined to return the location of the application’s data files:

@interface AppDelegate : NSObject <UIApplicationDelegate> {
  NSManagedObjectModel *managedObjectModel;
  NSManagedObjectContext *managedObjectContext;
  NSPersistentStoreCoordinator *persistentStoreCoordinator;

  /* (...Existing Application Code...) */
}

@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

- (NSString *)applicationDocumentsDirectory;

/* (...Existing Application Code...) */
@end

AppDelegate.m

Implement applicationDocumentsDirectory, and explicitly write accessor methods for each new property as opposed to simply using the @synthesize keyword.  Note in the persistentStoreCoordinator accessor there is a location where you must name the SQLite file used for the store; this should most likely be your project name. Remember to properly release each object in dealloc:

@implementation AppDelegate
//Explicitly write Core Data accessors
- (NSManagedObjectContext *) managedObjectContext {
  if (managedObjectContext != nil) {
    return managedObjectContext;
  }
  NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
  if (coordinator != nil) {
    managedObjectContext = [[NSManagedObjectContext alloc] init];
    [managedObjectContext setPersistentStoreCoordinator: coordinator];
  }

  return managedObjectContext;
}

- (NSManagedObjectModel *)managedObjectModel {
  if (managedObjectModel != nil) {
    return managedObjectModel;
  }
  managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];

  return managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
  if (persistentStoreCoordinator != nil) {
    return persistentStoreCoordinator;
  }
  NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]
                      stringByAppendingPathComponent: @"<Project Name>.sqlite"]];
  NSError *error = nil;
  persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
                                 initWithManagedObjectModel:[self managedObjectModel]];
  if(![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                 configuration:nil URL:storeUrl options:nil error:&error]) {
    /*Error for store creation should be handled in here*/
  }

  return persistentStoreCoordinator;
}

- (NSString *)applicationDocumentsDirectory {
  return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}

/* (...Existing Application Code...) */

- (void)dealloc {
  [managedObjectContext release];
  [managedObjectModel release];
  [persistentStoreCoordinator release];

  /* (...Existing Dealloc Releases...) */
}
@end

ViewController.h

The following code needs to be added to the interface of whatever view controller will interact with the Core Data objects.  The FetchedResultsController and another instance of the ManagedObjectContext:

@interface ViewController : UIViewController <NSFetchedResultsControllerDelegate> {
  NSFetchedResultsController *fetchedResultsController;
  NSManagedObjectContext *managedObjectContext;

  /* (...Existing Application Code...) */
}

@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@end

ViewController.m

Don’t forget to synthesize the new properties for their accessor methods:

@implementation ViewController
@synthesize fetchedResultsController, managedObjectContext;

/* (...Existing Application Code...) */
@end

Add Links and Use Core Data

The managedObjectContext object has now been created as a property in both the AppDelegate and ViewController classes.  The declaration of managedObjectContext in the ViewController must reference the AppDelegate, which is the only place that object should be allocated.  Something like
ViewController.managedObjectContext = self.managedObjectContext;
should exist in the applicationDidFinishLaunching method of the AppDelegate to accomplish this.

Conclusion

Your project should now include all the necessary objects to perform Core Data operations within the application.  You can create the data model using Xcode’s built-in editor to define entities, properties, and relationships.  Then, work with the Core Data framework to store and fetch your persisted object data.