Andy Heydon

The Android Engagement Paradox

Horace Dediu over at Asymco has an interesting post today about IBM’s Digital Analytics Benchmark on US Black Friday sales, particularly the traffic generated by the mobile sector. With regards to tablets, the iPad is absolutely dominating it’s class, which is not a real surprise and not worth talking about. But when you consider phones, where US Android sales are up in absolute terms over the iPhone, the survey reports that it is the iPhone that is being used far more for online shopping. Horace goes on the review previous years and this trend is actually increasing — the proportion of iPhone usage to Android usage has increased from 2-to-1 in 2010 to 3-to-1 this year.

So the question is this, if the numbers of Android phones out there are increasing, why is their usage seemingly falling?

This engagement issue is an important one for app developers because we need to know where to concentrate our scarce development time and dollars. If a platform has limited coverage or a poor usage profile, then we don’t really want to put any effort into it. This study of online shopping is interesting because it involves a core function of web browsing, a major tenant of a smartphone. Dediu didn’t really offer any reasons for the disparity, other than to point out that because the installed base of each type of phone is so large then differences in demographics probably are not be a factor, but I can suggest a few reasons.

It would appear that Android users don’t realize that they have a web browser in their pocket. Maybe they purchased an Android phone when their contract was up and because smartphones are what the carriers push then that is what they buy. But in the process they are never sold on what a smartphone is and can do. They just want to call and text people, and seeing the latest weather forecast flash by on the screen is a piece of superficial smartness.

There are a lot of old devices out there, and with the very spotty history of operating systems upgrades there are a lot of Android devices running “very old” (in web years) versions. Those older versions had a very poor browser, making surfing the web an unpleasant experience; who wants to suffer through that in the heat of the battle on Black Friday? In contrast, Apple offers upgrades for a very large proportion of their devices (is there any-one still using a iPhone or iPhone 3G?) and users do upgrade, so they have the support for the latest web technology.

The iPhone UI is often criticized for being simple — a basic grid of icons spread across several side-scrollable pages, whereas Android has a more complex model of widgets, home pages and app drawers all arguing for our attention. There is just a lot more going on with the Android UI and I think people get overwhelmed by that complexity, so they learn a couple of things and give up on the rest. Complexity is one of the most difficult for we IT professionals to comprehend. We spend our lives using computing devices and have no fear of them, we use 4 key combinations to perform personalized actions, we remember rafts of options to bizarrely named command line operations, we intuitively soak up new user interfaces, but I’ve seen users stumped at pressing a button when there are only three choices on the screen. We regularly over-estimate what is complex and what is simple.

The seemingly shrinking engagement of Android users is troubling for app developers looking to target that market. People are not being tempted to use the browser, a function that is front and center of a smartphone’s existence. They are not motivated to shop, via phone, on Black Friday — the very peak of US shopping tradition. Google is intently interested in steering people to the browser and their cash engines of search and advertising. But if web surfing is failing to gain traction, if people are not using their smartphones as smartphones, then what chance does an independent app have of succeeding?

Advertisement

App Tour : Part 3 – Handling full screen video

This is the third of a four part series. The first part discussed dynamically injecting video into a UIWebView and followed on in the second part with how to detect scrolling to the end of the HTML page.

Back in the first part of this series, I discussed how to dynamically insert a video into a HTML page hosted in a UIWebView control. By utilizing HTML5 video tags, we can offload the actual playback to the UIWebView control, with it providing all of the playback controls and chrome. On an iPhone any HTML5 video will automatically play in full screen, which is just perfect – we are showing video of using the iPhone version of the app, so showing it full screen is exactly what we want.

But on an iPad, the video is constrained by the size of the UIWebView. In my app, the iPad version of the tour is presented in a popover so the video is only 320px wide. Part of the video player chrome includes a full screen button, which does indeed expand the video to full screen, obscuring everything in the main view hierarchy, however the popover remains visible on top of the video because it is not part of the standard view hierarchy. This is definitely not what we want. So our next challenge in the app tour is how to hide the popover while a video is playing full screen and restore that popover when the video is done.

The video playback is actually controlled by the UIWebView object and not a MPMoviePlayerController. This means that you cannot observe any of the standard MPMoviePlayer* movie player notifications (well you can observe them, you just won’t receive any notifications!). However, when the UIWebView control plays video, it generates equivalent UIMoviePlayerController* notifications, and so there are UIMoviePlayerControllerDidEnterFullscreenNotification and UIMoviePlayerControllerDidExitFullscreenNotification (note the lowercase ‘s’ in screen) notifications that we can observe and receive notifications for.

In the tour view controller that is being presented in the popover, I can be told when the user decides to go full screen and react accordingly:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(videoEnterFullScreen:) name:@"UIMoviePlayerControllerDidEnterFullscreenNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(videoExitFullScreen:) name:@"UIMoviePlayerControllerDidExitFullscreenNotification" object:nil];

Once I receive the notifications, I need to pass on that fact to the view controller that initially presented the popover so that it can dismiss or re-present the popover. I do that through another set of application specific notifications. I use two sets of notifications because the decision to hide/show a popover might come from more scenarios than just full screen video playback. The tour view controller knows it is going to be showing video, so that is what it looks for. But the main view controller has no idea what is going on in the popover’s content, so it just looks for generic instructions to show and hide the popover.

- (void)videoEnterFullScreen:(NSNotification *)notification {
// Need to inform whoever is listening to hide my popover
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_POPOVER_HIDE object:nil];
}
- (void)videoExitFullScreen:(NSNotification *)notification {
// Need to inform whoever is listening to show my popover
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_POPOVER_SHOW object:nil];
}
view raw gistfile1.m hosted with ❤ by GitHub

Once the main view controller receives the NOTIFICATION_POPOVER_HIDE notification, then it can dismiss the popover via something like [self._myPopover dismissPopoverAnimated:YES] and the video can play full screen unencumbered by any overlays.

The next challenge comes about when the video is finished and the video exits full screen. Again, the main view controller receives a notification to show the popover again, which it can do easily, but I would really like to return to the same state that the user last saw in the popover. That is, I want to return to a view potentially several layers deep in a view hierarchy. I can do that if I retain a reference to the content view before dismissing the popover, and using that reference when I present the popover again.

// Temporarily hide a popover
- (void)hidePopover:(NSNotification *)notification {
self.hiddenPopoverContent = nil;
if (self.hiddenPopoverController) {
// Save some details of the current state
// Holding on to the content view means we can go back to the same state
self.hiddenPopoverContent = [self.hiddenPopoverController contentViewController];
self.hiddenPopoverContentSize = [self.hiddenPopoverController popoverContentSize];
id <UIPopoverControllerDelegate> popoverDelegate = [self.hiddenPopoverController delegate];
self.hiddenPopoverDelegate = popoverDelegate;
[self.hiddenPopoverController dismissPopoverAnimated:YES];
self.hiddenPopoverHidingInProgress = YES;
if (popoverDelegate) {
[popoverDelegate popoverControllerDidDismissPopover:self.hiddenPopoverController];
}
self.hiddenPopoverHidingInProgress = NO;
self.hiddenPopoverController = nil;
}
}
// Show a popover that had previously been hidden
- (void)showPopover:(NSNotification *)notification {
if (self.hiddenPopoverContent) {
UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:self.hiddenPopoverContent];
if ([popover respondsToSelector:@selector(popoverBackgroundViewClass)]) {
[popover setPopoverBackgroundViewClass:[ZingPopoverBackgroundView class]];
}
CGSize popSize = self.hiddenPopoverContentSize;
[popover setPopoverContentSize:popSize animated:YES];
[popover setDelegate:self.hiddenPopoverDelegate];
if (self.hiddenPopoverPresentingButton) {
[popover presentPopoverFromBarButtonItem:self.hiddenPopoverPresentingButton permittedArrowDirections:self.hiddenPopoverArrowDirection animated:self.hiddenPopoverAnimated];
} else {
[popover presentPopoverFromRect:self.hiddenPopoverPresentingRect inView:[self view] permittedArrowDirections:self.hiddenPopoverArrowDirection animated:self.hiddenPopoverAnimated];
}
self.hiddenPopoverController = popover;
}
}
view raw gistfile1.m hosted with ❤ by GitHub

The hidePopover: method is called as a result of receiving NOTIFICATION_POPOVER_HIDE, it retains some useful information about the popover before dismissing it. The showPopover: method uses those retained values and a couple of others that were saved when the popover was initially presented, to show the popover again. Because the popover’s content view was retained, it’s state has been preserved and the user sees the exact same view from before the video playback.

I actually implemented the hidePopover: and showPopover: in a category on UIViewController, but the details of that implementation will be the subject of the fourth part of this series.

  • Part 1: Dynamically injecting video into a UIWebView
  • Part 2: Detecting scrolling to the end of a UIWebView page
  • Part 4: Mimicking instance variables for a category

App Tour – Part 2: Detecting scrolling to the end of a UIWebView page

This is the second of a four part series. The first part discussed dynamically injecting video into a UIWebView.

In the latest version of Taste ZiNG! are a set of pages that offer a tour on the concepts behind ZiNG! and on how to use the app. As an encouragement for reading these pages, I offer a reward in the form of publishing credits1. If you complete a section of the tour, then you receive a free credit.

The tour content is implemented as a set of HTML pages hosted inside a UIWebView control. To earn the credit I wanted the user to actually read the content, so just bringing up the page was not enough, I really wanted to know if they read to the bottom. Now obviously I have no way of knowing if they actually did read (all developers know, to our endless frustration, that users don’t read!), but I could at least hold off on granting the credit until they scrolled to the bottom of the page. I did consider adding a time component to that, but decided that was a bit too much. Maybe next time.

So the problem in hand is how do we know when the user has scrolled to the bottom on a HTML page in a UIWebView control?

Detecting scrolling in HTML is fairly straightforward JavaScript included into each page:

document.addEventListener("DOMContentLoaded",
window.addEventListener("scroll",
function() {
var b = document.body
if (b.clientHeight + b.scrollTop >= b.scrollHeight) {
// ...
}
})
if (b.clientHeight >= b.scrollHeight) {
// ...
}
})
view raw gistfile1.js hosted with ❤ by GitHub

When the document is loaded, set up an event listener for the scroll event, and if the height of the body plus the scrolled offset point is greater or equal than the entire height of the scrollable area, then we’ve reached the bottom. The extra test outside of adding the event listener is to handle cases when the HTML page fits onto a single screen and there is unlikely to be any scrolling.

When we detect the user has reached the bottom of the page, we want to communicate that back to our Objective-C code. The way to do that is to use -webView:shouldStartLoadWithRequest:navigationType: method on the UIWebView delegate. This method is invoked whenever the HTML document is about to navigate to another link, giving a delegate a chance to respond with a YES or a NO. If the method returns a YES the navigation proceeds, but if the response is a NO then the HTML page is not changed.

Initiating navigation in JavaScript is simply a case of setting the window’s location to a new URL. But one of the things we can do is invent our own scheme to differentiate our actions from standard http: links.

if (b.clientHeight + b.scrollTop >= b.scrollHeight) {
    window.location.href='rb://scrolled.bottom'
}

The rb://scrolled.bottom URL is completely made up, it just serves as a signal for our code to perform a specific action.

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSString *scheme = [[[request URL] scheme] lowercaseString];
// Allow standard URLs through
NSSet *stdSchemes = [NSSet setWithObjects:@"http", @"file", nil];
if ([stdSchemes containsObject:scheme]) {
return YES;
}
// We do something with the special RB scheme
if ([scheme isEqualToString:@"rb"]) {
if (!delegate) {
return NO;
}
NSString *event = [[[request URL] host] lowercaseString];
if ([event isEqualToString:@"scrolled.bottom"]) {
if ([delegate respondsToSelector:@selector(webPageScrolledToBottom:)]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
[delegate webPageScrolledToBottom:_fileName];
});
}
}
}
return NO;
}
view raw gistfile1.m hosted with ❤ by GitHub

In the shouldStartLoadWithRequest method, we allow the standard http and file requests through by returning YES. (The file: requests need to be handled because the HTML pages link to stylesheet and javascript files in the app bundle). But if we see the rb: scheme then we need to decode the rest of the URL to determine what particular action to perform, in this case that we’ve scrolled to the bottom of the page.

One thing that we need to be careful with is that the shouldStartLoadWithRequest method should return quickly, not only from a user experience point of view but also because the UIWebView control only waits 10 seconds for a response before it gives up and continues. Therefore as soon as we determine a delegate is interesting in knowing that we’ve scrolled to the bottom then we use Grand Central Dispatch to push the invocation on to an asynchronous queue and inform the UIWebView to stop loading.

With an event listener, the UIWebView signals a scroll to the bottom by initiating a navigation, that navigation is intercepted and refused, but not before signaling back up the view hierarchy. In this specific instance of the tour, I ultimately record the scroll event in a NSUserDefaults item, but the aspects of that little detail are not relevant to this post.

  • Part 1: Dynamically injecting video into a UIWebView
  • Part 3: Handling full screen video
  • Part 4: Mimicking instance variables for a category
  1. In Taste ZiNG! you can publish your wine and food information to the cloud, the monthly cost for that publishing is handled through an in-app currency called credits.

App Tour – Part 1: Dynamically injecting video into a UIWebView

I didn’t intend to kick off this blog with a hefty four part series on a deep iOS topic, but it is fresh in my mind and in the recent implementation of this functionality I encountered several interesting topics that are not well covered elsewhere on the web.

The headline feature in the latest version of my app1 is a tour – a set of pages that describe the underlying concepts of ZiNG! and how to use the app. These pages are a set of HTML documents presented in a UIWebView, and for the actual help I decided to supplement those pages with screencasts. I use videos on an external support center site so it was nice to get double duty out of them.

Incidentally, I create the videos with the Reflection app that uses AirPlay to mirror a device’s screen to a desktop. The app has a built in recorder to capture whatever you do on the device.

Taste ZiNG! is a universal app with slightly different UIs on the iPhone and iPad, which means that any screencasts will vary from device to device. I did not want to embed the videos in the app because that would have bloated the download significantly and I wanted the capability to modify the videos. That meant that I needed, at run-time, to dynamically determine which video to show in which situation, based on device, app version and tour page, and to splice that specific video into the HTML.

Therefore each HTML document contains just the basic text and I inject the appropriate video tags when the page loads. The HTML documents are shipped with the app so that the tour still has content even if the user is offline.

For the first task of determining which video page to serve up, I use a class in Parse pre-populated with data about the video URLs from my video hosting service. Parse is literally a mobile developer’s best friend. I use it all the time to store data in the cloud, without the need to build out a server infrastructure, resulting in huge savings in time, effort and money. Using the Parse SDK to fetch data is super simple:

PFQuery *query = [PFQuery queryWithClassName:@"Videos"];
[query whereKey:@"videoId" equalTo:[NSNumber numberWithInteger:[[item objectForKey:@"videoID"] integerValue]]];
[query whereKey:@"model" equalTo:model];
[query whereKey:@"zingVersion" lessThanOrEqualTo:[NSNumber numberWithInteger:CURRENT_VERSION]];
[query orderByDescending:@"zingVersion"];
// Go get the name of the video
[query getFirstObjectInBackgroundWithBlock:^(PFObject *video, NSError *error) {
if (error) {
// ...
} else {
NSString *videoSource = [video objectForKey:@"videoURL"];
if ([videoSource length] > 0) {
NSString *thumbnail = [video objectForKey:@"thumbnailURL"];
NSString *script = [NSString stringWithFormat:@"createVideo('%@','%@')", thumbnail, videoSource];
// ...
}
}
}];
view raw gistfile1.m hosted with ❤ by GitHub

I instantiate a PFQuery class, set the retrieval parameters and fetch the data in a background thread. A successful result returns a PFObject, which is just a dictionary from which I can extract the columns I am interested in. I use that data to create a little snippet of Javascript, which is part of the second task of adding the video to the HTML document.

An Objective-C class can interact with a UIWebView through the -stringByEvaluatingJavaScriptFromString: method. I invoke this when the UIWebView’s delegate receives a webViewDidFinishLoad: request.

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    if (onLoadScript) {
        [webView stringByEvaluatingJavaScriptFromString:onLoadScript];
    }
}

This script invokes a Javascript function with the video URL and poster thumbnail URL as parameters. The definition of that function is included into each of the tour’s HTML documents.

function createVideo( thumbnail, videoSource ) {
var root = document.body
var vid = document.createElement("video")
vid.setAttribute("controls","controls")
vid.setAttribute("poster",thumbnail)
var src = document.createElement("source")
src.setAttribute("src",videoSource)
src.setAttribute("type","video/mp4")
vid.appendChild(src)
root.appendChild(vid)
}
view raw gistfile1.js hosted with ❤ by GitHub

The UIWebView control supports HTML5’s video tag and because this is an iOS device I only need to be concerned with MP4 video.

So now I can dynamically determine which video to show for a particular tour topic, inject that video URL into a HTML document and let iOS handle the video playback.

  • Part 2: Detecting scrolling to the end of a UIWebView page
  • Part 3: Handling full screen video
  • Part 4: Mimicking instance variables for a category
  1. Taste ZiNG! v1.3, available Nov 5, 2012

Hello world!

So why in the age of microblogging, am I creating a blog? Aren’t those so passé these days? Well maybe, but I believe there is still a lot of life left in the long form. One hundred and forty characters is great for a witty observation or quick retort, but to build a cogent argument and defend it, the larger medium is necessary.

So I going to use this blog to develop larger ideas, to do a little bit of self-promotion, and to give back to the development community. I have read countless articles in the quest of solving some problem, but I have never had the capability of returning the favor until now. It is unlikely that I’ll be blogging on some of the technology I used to use because I have now moved on to different pastures, but there will still plenty to talk about, and I have a few new tricks up my sleeve. The tech industry is nothing if not constantly changing. I think the military has the term “target rich environment”.

So thank-you for popping in, and I hope you return soon.