Android’s Fragmentation Problem Is Being Replaced by the Support Library Problem

Android has long labored under the shadow of a “fragmentation problem” that makes it tough to write apps that work reliably across the many devices and platform versions. I’ve written before that this problem is overblown, yet it’s not without merit.

Google has done a lot to address the fragmentation problem, including creating support libraries that make new features available on older devices. These libraries have been a huge help to developers like myself.

But I’ve noticed my fellow devs getting less enthusiastic with each support library release. Sure, we look forward to the new features and bug fixes – but we’ve learned that with each release comes a new set of problems, some of which are specific to certain versions of Android. We’ll spend time coding workarounds for these problems, then have to revisit them the next time a support library is updated.

This situation is starting to resemble the fragmentation problem the support libraries were designed to address. Instead of writing code to deal with problems in specific Android versions, we’re writing code to deal with issues in specific support library versions.

Mobile Apps Are Scary

In 2006 I wrote about how the fear of installing desktop software accelerated the move to the web. Security warnings, firewall alerts and antivirus popups made installing software feel like an incredibly risky thing to do.

We’re seeing a similar situation with mobile apps now. The seemingly simple act of installing an app requires you to first approve a scary list of permissions, and while some may approve them the same way they dismiss a EULA, others find them daunting. Add to that the spammy notifications and addiction-feeding of popular games plus the privacy violations of popular social apps, and it feels like I’m watching a rerun from eight years ago.

If this trend continues, the whole debate about mobile apps vs. web apps will be pointless: users will feel safer with web apps so that’s what they’ll choose, and developers will follow.

I Hate the Command Line

mac-terminal

Back when I was a Windows developer, I learned all sorts of arcane things about the platform. I felt I had to in order to be a good developer, and much of it was necessary to support customers running into problems.

Four years ago I bought a Mac, and three years ago I ditched Windows entirely and started learning Android development on my Mac.

I decided I really didn’t want to learn all the innards of the Mac. I never liked dealing with all that on Windows, and since I wasn’t a Mac developer I figured I could skip it.

Which means, of course, that I’m totally useless with the command line. Terminal? No thanks. Bash? Forget it. Git? I’ll use SourceTree instead. If it doesn’t have a GUI, I don’t want to touch it.

Sure, there are probably a ton of cool things I could do from the command line. But I’m happier not feeling I need to know all that stuff.

Using Android’s ViewPager PageTransformer

A recent addition I made to the Reader in WordPress for Android is the ability to swipe through articles, which is a pretty standard thing on Android thanks to the ViewPager.

But after using it a while, I decided I wasn’t happy with the default swipe animation, so I created a custom PageTransformer to provide a different one.

I then added the ability to swipe through photos in an article, and again I wasn’t happy with the default animation. Rather than create another PageTransformer, I changed the one I’d already coded so it could provide a number of different animations.

Here’s a video demo:

The source code for this custom PageTransformer, which is part of the open source WordPress for Android app, is below.

package org.wordpress.android.ui.reader;

import android.support.v4.view.ViewPager;
import android.view.View;

/*
 * ViewPager transformation animation invoked when a visible/attached page is scrolled - before
 * changing this, first see https://code.google.com/p/android/issues/detail?id=58918#c5
 * tl;dr make sure to remove X translation when a page is no longer fully visible
 *
 * Usage: viewPager.setPageTransformer(false, new ReaderViewPagerTransformer(TransformType.FLOW));
 */
class ReaderViewPagerTransformer implements ViewPager.PageTransformer {
    static enum TransformType {
        FLOW,
        DEPTH,
        ZOOM,
        SLIDE_OVER
    }
    private final TransformType mTransformType;

    ReaderViewPagerTransformer(TransformType transformType) {
        mTransformType = transformType;
    }

    private static final float MIN_SCALE_DEPTH = 0.75f;
    private static final float MIN_SCALE_ZOOM = 0.85f;
    private static final float MIN_ALPHA_ZOOM = 0.5f;
    private static final float SCALE_FACTOR_SLIDE = 0.85f;
    private static final float MIN_ALPHA_SLIDE = 0.35f;

    public void transformPage(View page, float position) {
        final float alpha;
        final float scale;
        final float translationX;

        switch (mTransformType) {
            case FLOW:
                page.setRotationY(position * -30f);
                return;

            case SLIDE_OVER:
                if (position < 0 && position > -1) {
                    // this is the page to the left
                    scale = Math.abs(Math.abs(position) - 1) * (1.0f - SCALE_FACTOR_SLIDE) + SCALE_FACTOR_SLIDE;
                    alpha = Math.max(MIN_ALPHA_SLIDE, 1 - Math.abs(position));
                    int pageWidth = page.getWidth();
                    float translateValue = position * -pageWidth;
                    if (translateValue > -pageWidth) {
                        translationX = translateValue;
                    } else {
                        translationX = 0;
                    }
                } else {
                    alpha = 1;
                    scale = 1;
                    translationX = 0;
                }
                break;

            case DEPTH:
                if (position > 0 && position < 1) {
                    // moving to the right
                    alpha = (1 - position);
                    scale = MIN_SCALE_DEPTH + (1 - MIN_SCALE_DEPTH) * (1 - Math.abs(position));
                    translationX = (page.getWidth() * -position);
                } else {
                    // use default for all other cases
                    alpha = 1;
                    scale = 1;
                    translationX = 0;
                }
                break;

            case ZOOM:
                if (position >= -1 && position <= 1) {
                    scale = Math.max(MIN_SCALE_ZOOM, 1 - Math.abs(position));
                    alpha = MIN_ALPHA_ZOOM +
                            (scale - MIN_SCALE_ZOOM) / (1 - MIN_SCALE_ZOOM) * (1 - MIN_ALPHA_ZOOM);
                    float vMargin = page.getHeight() * (1 - scale) / 2;
                    float hMargin = page.getWidth() * (1 - scale) / 2;
                    if (position < 0) {
                        translationX = (hMargin - vMargin / 2);
                    } else {
                        translationX = (-hMargin + vMargin / 2);
                    }
                } else {
                    alpha = 1;
                    scale = 1;
                    translationX = 0;
                }
                break;

            default:
                return;
        }

        page.setAlpha(alpha);
        page.setTranslationX(translationX);
        page.setScaleX(scale);
        page.setScaleY(scale);
    }
}

Why I Left Indie Development

indiana-jones

In what feels like a different life now, I used to be a fairly well-known indie developer. I spoke at events like SXSW, and my blog had a large audience of people who liked hearing about my work on HomeSite, TopStyle and FeedDemon.

I parted ways with that life several years ago, in part because it’s extremely difficult to be a one-man show and still have time to be a husband and father. I was also burned out from years of doing my own support. I had lovely customers, of course, but I spent so much time supporting them that I wasn’t able to spend as much time feeding my code addiction. Almost everything I do professionally is so I can enjoy the bliss of getting lost in writing software.

But I also gave up pursuing the indie life because I wanted to make the switch to mobile development, and I didn’t see much future for indie mobile developers. The economics of the various app stores coupled with the plethora of free software didn’t paint a rosy picture for one-person companies building consumer apps. In fact, I didn’t make the leap to mobile until I was offered a full-time job as an Android dev.

So it’s been interesting reading the latest round of blog posts about the state of indie mobile development. While there are success stories, there certainly aren’t many of them. Making a decent living as an indie developer writing mobile apps is ridiculously hard – and I don’t see it getting better anytime soon.

A lot of mobile developers have left the indie ship and done as I have and joined a larger company, many of which look at mobile apps as a free (or nearly free) complement to their other offerings.1 There’s plenty of opportunity here for mobile developers, and I think that opportunity will continue to grow for a while.


  1. As an aside, I’m not convinced the “mobile as a complement” strategy is the right one long term. Mobile is becoming the primary way people access their information, to the point that web and desktop software are turning into the complement.

Android’s Overblown Fragmentation Problem Revisited

Two years ago I declared that Android’s supposed “fragmentation problem” was overblown.

I was working on Glassboard when I wrote that post, and Glassboard hadn’t become a Google Play “staff pick” yet so it still had a fairly small audience. These days I work on WordPress for Android which has a much larger audience, so I figured I’d revisit the fragmentation topic.

I’ve seen a lot more fragmentation-related problems since working on WordPress, but I still maintain that fragmentation is less of an issue than is commonly believed.

That’s not to say, of course, that it isn’t a problem at all.

I think the biggest problem is that unless your app is relatively new, you probably have to continue supporting Android 2.3. Making sure your app works on that ugly, buggy OS is a massive pain. Every Android developer I know will dance in the streets the day they can drop support for pre-ICS versions of Android.

Another problem is the number of inexpensive, low-powered Android devices in use – especially outside the US. If you want your apps to run well on them, you have to be extra-cautious about memory consumption and performance (but then, you should be anyway).

Overall, though, I haven’t found the number of devices to be as big an issue as the number of OS versions. Here’s a breakdown of the different Android versions our customers are running:

WP Android installs (March 2014)

This isn’t as big a deal as you might think, but it’s not unusual to find your app works flawlessly on the latest version of Android yet breaks on a previous version due to a bug fixed between releases. The Android Issue Tracker is a big help in these situations.

The only other oft-recurring problem I’ve encountered is differences between phone and tablet versions of your app, but these are usually self-inflicted (often caused by having too many screen-specific layouts and not synching changes between them).

I’m sure these issues look horrific to iOS developers, who are blessed by only needing to support a few devices and OS versions. But they’re certainly not as horrific as the press often makes fragmentation sound, and they’re far easier to deal with than all the fragmentation problems I encountered back when I developed for Windows.

 

 

Android Tip: Avoid Hard-coded Sizes

I often run across Android layouts which use hard-coded margins, padding, and font sizes, like this:

android-layout-hardcoded-sizes

Doing this often leads to inconsistency. Different layouts end up hard-coding different font sizes and margins, making the app appear less polished.

To avoid this, I recommend defining the sizes in values.xml like this:

android-layout-values

And then referring to these sizes in your layouts like this:

android-layout-declared-sizes

If every layout uses only the sizes defined in values.xml, then not only will your app appear more consistent but it will also make it much easier to adjust sizes application-wide.

Un-jank Your Android ListView

Making a ListView scroll smoothly is a bit of a black art for Android developers, but there are a few simple rules:

  1. Use the View Holder pattern
  2. Do as little work as possible in getView()
  3. Reduce unnecessary overdraw
  4. Flatten the view hierarchy

The View Holder pattern is well known, but that second rule often gets ignored despite having the biggest impact.

Because getView() is called every time a cell is drawn, any extra work done there will slow things down. Things like formatting dates, manipulating bitmaps, querying SQLite, or loading image resources should be moved out of getView() and instead computed and cached prior to the list being populated.

Unnecessary overdraw can be caused when one view covers another one. If both views have a background, both backgrounds will be drawn even if the first is completely covered. The simplest way to find overdraw issues is to enable “Show GPU Overdraw” in the device’s developer options. If you see dark red blocks behind your ListView text after enabling that option, it’s time to work on overdraw. Simply getting rid of unnecessary background colors/drawables can make a huge difference. If you have an activity whose background is completely covered by another view, you may be able to get rid of one layer of overdraw by calling getWindow().setBackgroundDrawable(null) after setContentView() in the activity’s creation.

Flattening the view hierarchy in your layouts not only helps reduces overdraw but also means less work has to be done when the layout is inflated and drawn. When I create an XML layout, I’ll often use a ton of parent views (RelativeLayouts, LinearLayouts, etc.), to make it easier to group things and move them around. But once I’m happy with the layout, I try to remove as many of those parent views as possible.

Optimistic Connectivity and Sleight of Hand

A huge pet peeve of mine is mobile apps which make you to wait after you do something that requires a server API call. For example, you tap a “Like” button and are forced to look at a modal “Liking…” dialog that doesn’t go away until the app knows the like has been received by the backend.

Apps which do this are unnecessarily pessimistic about connectivity. Why make the user wait when the most likely outcome will be that the API call succeeds? I think it’s better for the app to show the new “Like” state immediately, and then deal with the rare failure in the background.

This optimistic approach is one that the folks at Instagram have touted, and I’ve followed it in all of my mobile apps.

Sometimes, though, you really do want to make the user wait, at least a little bit. In these cases, it’s often better to resort to sleight of hand which distracts the user instead of blocks them the way a modal dialog does.

I’ve found animation to be the most effective form of this trickery.

Suppose it really is important to make the user wait a couple of seconds after clicking a “Like” button to give the API call a little time. In this situation, animate the “like” button for a second or two – have it bounce, or spin, or zoom in and out.

Users will wait for the animation to end before doing something else in the app, and by the time the animation is done there’s a good chance the API call has completed. The user still waited, but because they waited by choice it doesn’t feel the same as being so obviously blocked by an annoying modal dialog.

PS: I should add that I only recommend this approach when making very lightweight requests. For heavier requests (especially ones that upload large amounts of data), a background service is a far better choice.