Tuesday, September 27, 2011

Converting a phone app to Android tablets and the iPad

Just as I thought I was winding down my Android and iPhone development the Doctors tell us they have bought iPads and Android Tablets to use to enter all the data. Can't blame them as it is much easier to enter data on a tablet. The problem is we were going to target those devices on the next release. Time to quickly switch gears.

The app ran fine on the Xoom but the home screen needed to scale better. I searched the web and found new graphics for that area. With more room using larger fonts also seemed the way to go so I tweaked a few other XML files. I ended up creating a new values-xlarge directory under the res directory to hold a styles.xml file for the tablet. I find the way Android handles this to be super easy. I had to go back into a few of my other XML files to make sure they were using the named styles but that was all. I only have a few files in my layout-xlarge and layout-xlarge-land directories to handle all the changes I wanted to make on the Android Tablet.

Android does handle your app getting kicked out out memory in a way that screwed with our app. We have some global static data (I know wrist slap in order) but I need it for the SQLite database and some other login credentials. When you get booted from memory the OS tries to run your onCreate method of the visible view again. For most apps this would be great, you just pretend you started fresh and go. But with the global variables this is not so fun. I added enough checks that I don't [Force Close] any more but tell the user that the session has expired and they need to login in again. I kick them back to the login screen to get going with a fresh database and session variables.

Initially testing this case was a royal pain. You needed to leave your phone on overnight or run so many apps that it got kicked out of memory. Of course that is stupid and a few internet searches pointed me in the right direction. Fire up the emulator, run your app, use "adb shell" from the command line then "ps" then "kill #id" of your process. This will cause same sequence of events to occur. I brought up each possible activity / view and did that over and over until they all exiting clean to the login screen. You can't do this when running on a device but you can use the DDMS view in Eclipse and hit the red [stop] button to achieve the same results.

Once you start running on a real device, especially a dual core tablet, you hate to go back to the emulator. Of course QA came and took the tablet a few days after I had it in my hot little hands so I am still missing it. So much nicer to type on it and code changes are sent over to it and are up and running in a blink of an eye.

Now for my iPad experience. You can set up a universal binary in Xcode. Once you do that it will automatically switch you to the latest version of iOS as your target which is not a good thing. I wanted to target 3.0x as the minimum iOS version. Since Xcode is smarter than me things quit working on my iTouch until I figured out what it did. Simple enough to change that back.

Initial runs on the iPad looked like initial Android tablet runs. Most things looked pretty good as I was using list views and text editors. The home screen looked crappy though. I was able to use my @2x images for the retina display on the iPad. I used the stretchableImageWIhtLeftCapWidth to simulate the nine-patch image I was using for the shelves on the Android.

topShelf = [[UIImageView alloc] initWithImage:[shelf stretchableImageWithLeftCapWidth:25 topCapHeight:0]];

Worked like a charm and was much easier than I assumed it would be. I had to put in manual positioning code for each of the icons on the shelves for the iPad but there are only 6 images so that was easy enough.

I am adjusting for the height of the status bar in my code too. On the iPhone the status bar height is different between portrait and landscape but it is one height on the iPad. I put code in place to handle those special situations and everything looks pretty darn nice.

Since I have more title bar room I wanted to show the practice name on the home screen. Of course that changed the "back" button on any screen that launched from the home screen which is not what I wanted. Turns out you can do this:

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {

    self.navigationItem.title = [NSString stringWithFormat:@"Home - %@", [RESTCaller practiceName]];
    UIBarButtonItem *backButton = [[[UIBarButtonItem alloc] initWithTitle:@"Home" style:UIBarButtonItemStylePlain target:nil action:nil] autorelease];
    [self.navigationItem setBackBarButtonItem:backButton];
}

That code allows you to set the title text to one thing but the back button used by all other areas to something else. Not the way my mind thinks but I understand the logic behind it now that it is in place.

I want to increase the font size on various screen on the iPad. Since there is no real layout manager for iOS, everything is hard coded for widths / heights in the XIB file, this is a royal pain. You can either do secondary XIB files where you have to tie everything exactly back to the code via drag and drop or you can try and tweak things in code playing with bounds and what not. Both are sorry excuses for layout editing. I have made slow progress in this area but find the amount of work involved to be stupid.

The iPad cannot use the same call to show the picture gallery as the iPhone. I had to update the code to make the new call to show a pop-up image picker instead of a full screen gallery picker. I hope that is the only API I am using that they changed on me. Rough one to find, good thing we have QA to beat on it.

UIActivitySheets don't exactly work the same on the iPad either. They show up nice and centered but if you rotate the device while you are displaying one it will not stay centered. It stays relative to its original position. Looks ugly but this will be a really rare case so I did not fix it. There are some suggestions on the web as to how to fix it but nothing worked for me. The only thing left to consider is killing the sheet when you get a rotation notice and recreating it so it shows up in new orientation centered again. Of course if you do that really quickly you will have a new set of issues. Not worth it at this point. I see Apple cheated in this respect. Long tap a link on a web page. It pops up a menu. Rotate the screen. The Screen will not rotate until you dismiss the menu. Not what I would call a particularly user friendly way of doing things.

Camera support on iOS devices is totally hit or miss. If you want to determine if a camera is there you need to use iOS 3.2 and above calls. Seems Apple is not very forward thinking on these things and a lot of stuff gets added in later releases but I can't trust a user to be up to date. Instead I am using a call to get the machine name and basing the availability of a camera off of that. Crappy way to do things but it is working for me so far. I know iPad v1 does not have a camera and you have to be up to iPod (the iTouch) version 4 before you have a camera. The simulator never has a camera which sucks. On the Android side it simulates the camera by showing interface then returning a stock image. I had to test camera usage on an actual iOS device and I have to make sure it has a camera otherwise you get a nice crash. I don't even want to show the [Take picture with Camera] button in the UI if you don't have one.

Which brings me to the another WTF? for Apple. Why are Action Sheets index based? If I remove a button from my Action Sheet it shifts the index of all the buttons below that. Hard to write generic code and you always have to edit two places for each action sheet change you make - once where you create the buttons on once where you respond to their presses. Each button should call its own method or at least you should be able to assign tags to them so you can have one case statement. Honestly assigning tags to UI items seems pretty stinking crappy too. I do it where I can to keep track of things but it is not the most object oriented way of programming.

Some of my buttons had the dreaded "..." on them depending on what I ran it on. No device fragmentation here boys, move along. They were working find on the iPad 2 and both iTouch devices but not on the iPad 1 or an iPhone with an older version of iOS. I had to shorten the text on all devices to make sure it fits.

I have no idea if the Doctors will keep their phones up to date. At this point - pre iOS 5 - there are not OTA updates. If a Doctor never connects his iOS device back to a PC running iTunes he will have no idea there is an update out there. Will be interesting to see what iOS versions we have connecting once we roll this thing out into the wild. I have tried to test on as many iOS flavors as possible and each time I seem to find a new issue.

One other big area I can into was the 1 meg blob limit of SQLite. We are taking pictures with the built in cameras and they can easily be over 1 meg on an Android. The lackluster camera on the iPad and the iTouch don't get you into trouble but an iPhone can. I use JPEG compression to shrink the image down as needed to avoid SQLite issues. It is a shame the iPad camera is so limiting in resolution. It is an unusable solution for our doctors, the images of sheets of medical paperwork are not readable with that camera. We also allow you to choose images from the device gallery which could easily be from another device and over the 1 meg limit.

Overall it was not very difficult to get a universal app running on either the Android or iOS platforms. We are not taking full advantage of the tablet form factor but at least we are not just doing a 2X version of our iPhone app, the images scale up nicely without being blurry, you get to utilize the extra space of the device and things look native in general. 

QA is doing final testing then it will be off to the beta clients. It will be really interesting to get their feedback. Now I just need a little time to destress and I get ready to head back into Java + Swing + MigCalendar land to fix some issues in our appointment application.

3 comments:

  1. Hi,
    I was looking for something and came across with this site. I have a question. How can I disable an UIActionSheet take photo with camera button when the device doesn't have a camera. Please answer me ASAP.

    ReplyDelete
  2. Also please help me with some reference if possible. Thanks

    ReplyDelete
  3. Madhuri, I think I understand your question. You want to know what code I am using to detect a camera under iOS. It is NOT the best routine but it is working for me.


    // Based on really crappy testing guess if we don't have a camera
    + (BOOL) hasCamera {
    struct utsname u;
    uname(&u);
    BOOL haveCamera = YES;
    if (!strcmp(u.machine, "iPad1,1")) {
    haveCamera = NO;
    } else if (!strcmp(u.machine, "iPod1,1")) {
    haveCamera = NO;
    } else if (!strcmp(u.machine, "iPod2,1")) {
    haveCamera = NO;
    } else if (!strcmp(u.machine, "iPod3,1")) {
    haveCamera = NO;
    } else if (!strcmp(u.machine, "i386")) {
    haveCamera = NO;
    } else if (!strcmp(u.machine, "x86_64")) {
    haveCamera = NO;
    }
    return haveCamera;
    }

    ReplyDelete