7

My app's launcher activity checks a bunch of requirements like whether GPS is enabled, there's a network connection, the app has device administrator privileges, etc. Some of these checks display dialogs or take the user to another activity to resolve the unsatisfied requirements. I'm looking for a way of doing this that's easy to extend with more requirements and easy for others to read and understand.

My original solution was to perform the checks sequentially, each in its own method, with each method either taking remedial action or calling the next method in the sequence. The sequence is started in onResume(), so upon returning from another activity it picks up with the first check that doesn't pass. This got messy fast because some of the requirements are conditional. For example, on Lollipop it checks if the app is the device owner, and whether it might be able to become the device owner. If it can, it tells the user to run the appropriate adb shell command. Naturally, this whole sequence of checks is skipped on KitKat. So the chain of methods is actually more like a tangled braid. It's hard to trace the logic of it or verify that each step eventually calls the next under all possible conditions. I'm reluctant to make any changes to it, and I just wrote it myself a couple weeks ago.

My other idea was to number the steps (or associate them with the elements of an enum) and pass the last-completed step between activity instances. But I anticipate this getting messy, too.

How would you structure this?

1

2 Answers 2

1

I'm not sure this is the best solution, but this is what I ended up doing:

I put all my requirements checks in the onResume() of a headless fragment, and added it to my activity in onCreate(...). If any of the requirements aren't met, the headless fragment displays dialog fragments that explain what it needs the user to do before taking them to the system settings. When all the requirements are met, it calls a method on the activity. (I may change this to broadcasting an event to completely decouple the fragment from the activity.)

This doesn't make the logic of the sequence of checks any less messy, but at least it moves all that code out of the activity, which makes both parts easier to navigate and understand.

Edit: Six months later, I refactored the above solution into something much more OO.

public interface Requirement {
    /**
     * Checks a system requirement and calls {@link
     * StartActivity#nextRequirement()} when it's satisfied.
     *
     * @param activity the current activity
     */
    void check(final StartActivity activity);
}

A List<Requirement> is injected into the activity using Dagger2. I can easily add more requirements, and their order is easy to see and change in the Dagger module.

@Inject List<Requirement> mRequirements;
private Iterator<Requirement> mRequirementsIterator;

void checkRequirements() {
    mRequirementsIterator = mRequirements.iterator();
    nextRequirement();
}

void nextRequirement() {
    if(mRequirementsIterator.hasNext()) {
        mRequirementsIterator.next().check(this);
    }
    else {
        onRequirementsSatisfied();
    }
}

I call checkRequirements() from onResume(). Any dialog fragments displayed by a Requirement dismiss themselves in their own onPause(), since they will be displayed again if the requirement is still not met the next time the activity resumes.

0
0

We used put together a single method that did nothing but determine what things had to be set up and returned a value that was passed into a second method which was really just a giant switch statement. Each element (such as isGpsAvailable) had its own method that was placed in the appropriate case depending on the environment.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.