Showing posts with label android. Show all posts
Showing posts with label android. Show all posts

Monday

Android - Reliable Alarms to Services

I love programming for Android. The APIs just make sense. You can tell that fluent Java developers designed this thing. Despite the fact that everyone's favorite criticism is that Android is "fragmented" I haven't had any significant cross-hardware issues. Follow the guidelines (What?!? I have to follow rules if I want my app to work? So much for freedom...) and 95% of apps should just work on any platform without much trouble. I'd say you're even a step ahead of iOS development because the platform has extremely simple ways to support multiple screen sizes, so your app can easily look good on any device. That said, there are occasional bugs, and this post is about one of them.

This may not even be a bug, but rather a case of poor documentation. The problem arrises when you use the AlarmManager to schedule a repeating alarm that is supposed to wake the phone and start a service. e.g.,

PendingIntent pi = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, timeToStart, 15 * 60 * 1000, pi);

It will probably work great during most of your testing as the phone will be awake. However, once you stick your phone in your pocket and forget about it for a while, you'll realize, "Hey, wasn't an alarm supposed to go off?" You'll pull your phone out, turn it on, and suddenly it will go off. The first few times you'll think it's odd, until you realize that the alarms aren't going off when the phone has been asleep for awhile, but they are delivered as soon as it wakes.

Hopefully you find this post or something like it and don't have to spend days Googling to figure out that only a BroadcastReceiver can reliably receive a wakeup alarm and then spend another day learning that the Context that onReceive gets can't start services. The solution is pretty simple, but you'd never figure it out from the documentation.

EDIT: I may be totally wrong about the context being a problem as you can see in the comments. Try it without the using getContext first and if you don't have any problems getting the service to start, avoid the Cargo Cult code I'm using below. If you do have problems and the context trick helps, please let me know in the comments.

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;


public class AlarmProxy extends BroadcastReceiver {

    Context getContext(Context ctx) {
        try {
            return ctx.createPackageContext("com.yourpackage.ofyourapp", 0);
        } catch (NameNotFoundException ignore) {}
    }
    
    
    @Override
    public void onReceive(Context ctx, Intent intent) {
        Context context = getContext(ctx);
        Intent newIntent = new Intent(context, YourService.class);
        newIntent.setData(intent.getData());
        newIntent.putExtras(intent);
        context.startService(newIntent);
    }
    
}

The context your receiver gets is probably a ReceiverRestrictedContext which isn't connected to your application and can't start services, so you may need to use createPackageContext to get your app context and start your service. You'd then set alarms by sending them to your receiver:

Intent intent = new Intent(this,AlarmProxy.class);
// ...
PendingIntent pi = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, timeToStart, 15 * 60 * 1000, pi);

Happy alarming!

EDIT: Don't forget to register the receiver in your Android manifest:

<receiver android:name=".AlarmProxy"> </receiver>

Wednesday

OAuth and Android - Dropbox is wrong; never ask for passwords

EDIT: Not having an iPhone and not having any dev experience with it (yet), I wasn't sure whether or not this was possible on an iPhone. However, I've had an iPhone dev assure me that it is and that several apps (TripIt being an example) do it. So there really is no excuse. If OAuth is good on the web, it's good on iPhone and Android too.

Dropbox recently announced their API, which is pretty exciting because it's a great service. It uses OAuth for authentication, but under the Authentication For Mobile Devices section, there is this statement:
While Dropbox uses OAuth for the base of its authentication strategy, OAuth doesn’t work very well for mobile devices. It’s much too difficult and error prone to have a mobile device switch context to a browser, do the OAuth handshake, and then magically return to the original application with the tokens in hand ready to use. It’s bad for user experience and it’s bad for developer sanity.
 I emphatically disagree with this statement, at least as far as Android is concerned. I think OAuth vastly improves the user experience, even from a mobile app and this attitude that it's too hard or too much trouble is damaging to both the experience and security of users.

Lets take their statement piece by piece.

It’s much too difficult and error prone to have a mobile device switch context to a browser

This is trivial on Android. I'll use the Gowalla OAuth flow in my examples because I think it's an elegantly simple implementation of OAuth, but this works for any OAuth system:



That's it. The browser is launched and the user will see the Gowalla authorization page. If they're already logged in, they don't even have to type their username and password. Now there is an activity that is difficult and error prone. I hate entering login credentials on my phone, and I can copy and paste them from LastPass. Even with Swype, typing on a mobile device is neither easy or error free. Save your users the headache and use OAuth.

It’s much too difficult and error prone to ... do the OAuth handshake, and then magically return to the original application with the tokens in hand ready to use.


Again, this "magic" is almost trivial for an Android app. You simply specify that your activity can handle the custom data URI scheme you specified in your redirect link:



Once the user clicks "Allow", your activity will be launched and the Intent data will have a parameter telling you the access code needed to complete the OAuth handshake. This isn't any more difficult and error prone than implementing OAuth in a web application. But it's not that difficult, because there are plenty of great OAuth libraries out there to help you on the web server, and you can use them on your Android device too.

It’s bad for user experience

If this is true for OAuth on mobile devices, then it's even more true for the web and we should abandon OAuth completely. An app launching a browser is not an unfamiliar thing. Receive an email with a link on a desktop, you click the link and it opens your browser. This is normal, good UX. I click a link to comment on your site and you take me to Facebook? That's weird. As a user I'm not sure what is going on. Suddenly everything looks different... I thought I was commenting on your blog? Why are you on my Facebook?

The response to this (for web applications) is that users simply need to become familiar with the concept and then it wont seem weird at all, it will seem normal. That argument also works for mobile devices. In fact, if you are worried about the user not knowing what is going on, just tell them! "You will now be taken to Gowalla to authorize this app. You may be asked to log in. After you authorize myApp, you will be returned here."

OAuth is Good for Dev, and Good for UX

The idea that OAuth isn't suited for mobile isn't in any way grounded in the Android mobile experience, as far as I can tell. That might explain why Dropbox has a lot to say about it's Objective-C library, but for Java they only offer two words: "Coming soon."

Anything you can truthfully say about OAuth being bad UX or bad for dev in regards to mobile is equally if not more true for the web. But nobody is suggesting that OAuth be abandoned on the web and we go back to asking users for their passwords. So don't do it in your mobile apps either.