Profile cover photo
Profile photo
Taylor Perkins (hwrdprkns)
181 followers -
Let whoever can win glory before death. When a warrior is gone that will be his best, and only bulwark.
Let whoever can win glory before death. When a warrior is gone that will be his best, and only bulwark.

181 followers
About
Taylor's posts

Post has attachment
Clam caking

Post has attachment
Yummy east coast food

Post has attachment

Post has attachment
Time for a new work computer? Make life easy for yourself by using this script to export all of your brew (and cask) packages.

Then just copy the file to your new computer. Bundle the Brewfile and profit!

Post has shared content
People just need to be convinced it's worth the time!
Android App Polishing: Reducing User Frustration

With the release of Capitaine Train for Android[0], several people recently asked me how we implemented some of the tips & tricks available in the application (some of these tricks are barely visible but remember, I love details :p). In order to showcase some of the most interesting application bits of code, I thought it could be helpful to Android developers to start a small series of posts. Feel free to comment this article if you want me to describe something you found nice in the Capitaine Train application.

Capitaine Train Android relies on an internal "search" API that, in turn, also relies on some carriers APIs we don't have control on. For instance, when a user is looking for a train, the Android app asks Capitaine Train servers. Then, Capitaine Train asks all supported carriers for their available trains for the requested search params. Once carriers respond to our servers, we find the best prices, combine results, do whatever-we-think-is-the-best-to-filter-and-sort-results and finally send results to the Android application. While the process is simple (at least in theory…), it also implies, by transitivity, Android apps relies on some - sometimes slow - servers we don't control: carriers' servers.

Because of the potentially long "search" API calls we had to find a way to indicate the user something was loading. In the early - internal only - alpha versions of Capitaine Train Android, we quickly integrated a circular/indeterminate ProgressBar. But users (i.e. Capitaine Train employees) were not satisfied with it. Indeed, most of them were annoyed by the loading time. This mutiny quickly demonstrated displaying a simple loading indicator was not enough. We also had to reduce user frustration. We finally decided to completely redesign the loading screen by using a determinate ProgressBar and adding a fancy animation.

The main idea behind the ProgressBar is obviously to indicate the user the approximate amount of time remaining before the results are displayed. The current version of Capitaine Train is based on an hardcoded average search duration (based on our experience). But we have an opened issue in our issue tracker to enhance the search duration approximation using previous device/connectivity searches. The ProgressBar is animated thanks to an ObjectAnimator:

private static final TimeInterpolator GAUGE_ANIMATION_INTERPOLATOR = new DecelerateInterpolator(2);
private static final int MAX_LEVEL = 10000;

ObjectAnimator.ofInt(mProgressBar, "progress", 0, MAX_LEVEL)
    .setDuration(GAUGE_ANIMATION_DURATION)
    .setInterpolator(GAUGE_ANIMATION_INTERPOLATOR)
    .start();

The ProgressBar is entirely customized using a LayerDrawable[1] defined in XML. As ProgressBar requires, the two layers are tagged with some special framework-defined identifiers: @android:id/background for the background and @android:id/progress for the actual progress. Note you can also use @android:id/secondaryProgress if you use the "secondary progress" capabilities from ProgressBar:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:id="@android:id/background">
        <shape>
            <solid android:color="@color/ct_dark_gray_10p"/>
            <corners android:radius="8dp" />
        </shape>
    </item>

    <item android:id="@android:id/progress">
        <clip>
            <shape>
                <solid android:color="@color/ct_green"/>
                <corners android:radius="8dp" />
            </shape>
        </clip>
    </item>

</layer-list> 

We also added a fancy and funny gif-like animation to the loading screen. We wanted to reduce user frustration by attraction his/her attention on something different that the painful loading indicator. The current animation is rather simple and simulates a train going forward. I would have loved to make something more polished but I'll be honest with you: I did this in a rush :s. I clearly need to find time to implement some other nice ideas on this screen.

The implementation of the animation is a little bit trickier and relies on both a LayerDrawable[1] containing both a background (the train image with no rails) and an AnimationDrawable for the animated rails. We could have implemented it with a single AnimationDrawable[2] (it was actually the case in the first version) but the current implementation reduces our APK size quite significantly:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/background"
        android:drawable="@drawable/ic_train_no_rails"/>

    <item
        android:id="@+id/rails"
        android:drawable="@drawable/animated_rails"/>

</layer-list>

The @drawable/animated_rails Drawable is defined as follows:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:duration="50">
        <inset
            android:drawable="@drawable/animated_rails_1"
            android:insetBottom="6dp"
            android:insetLeft="66dp"
            android:insetRight="66dp"
            android:insetTop="150dp" />
    </item>

    <item android:duration="50">
        <inset
            android:drawable="@drawable/animated_rails_2"
            android:insetBottom="6dp"
            android:insetLeft="66dp"
            android:insetRight="66dp"
            android:insetTop="150dp" />
    </item>

    <item android:duration="50">
        <inset
            android:drawable="@drawable/animated_rails_3"
            android:insetBottom="6dp"
            android:insetLeft="66dp"
            android:insetRight="66dp"
            android:insetTop="150dp" />
    </item>

</animation-list>

In general, always try to avoid loading indicators/screens. When you are in control of the entire API chain, this can be done fairly easily. Unfortunately we are not living in an ideal world and it may be necessary sometimes to overcome/hide some issues with loading screens. If you end up implementing a loading screen in your application, do everything you can to reduce users frustration. 

#gde   #android   #AndroidAppPolishing  

[0]: https://play.google.com/store/apps/details?id=com.capitainetrain.android
[1]: http://developer.android.com/reference/android/graphics/drawable/LayerDrawable.html
[2]: http://developer.android.com/reference/android/graphics/drawable/AnimationDrawable.html
PhotoPhotoPhotoPhotoPhoto
2014-05-05
5 Photos - View album

Thanks for the great okhttp library! I'm curious as to what the timeline is for the new async functionality. I know its in master currently, but is there a release timeline for it?

Post has attachment
Steph curry needs to stop turning the ball over. 
Photo

Post has attachment

Post has shared content
Vault: an advanced Storage Access Framework example

Recently, I’ve been working on the new Android Storage Access Framework which introduces an abstraction layer between apps that work with files and apps that can store those files. This is powerful because apps like QuickOffice can now use simple intents to open and save documents using any storage app that implements a DocumentsProvider.  The DocumentsProvider API is designed to support a wide range of storage types, including cloud storage, physical media, etc.

https://developer.android.com/guide/topics/providers/document-provider.html

As an advanced DocumentsProvider example, I wrote “Vault” which encrypts all files that a user saves inside.  It keeps data encrypted with AES-128 while at rest, and uses Unix pipes to encrypt/decrypt data on the fly.  It protects the encryption key by wrapping it using a hardware-backed KeyStore, if available.  It also uses a new feature of ParcelFileDescriptor to detect when a remote process has encountered an error or crashed, indicating that any partially written data should be ignored and rolled back.

https://android.googlesource.com/platform/development/+/master/samples/Vault/

Hopefully this advanced example is helpful as you look at implementing your own DocumentsProvider.  :)

Post has attachment
Wait while more posts are being loaded