Last Updated: February 25, 2016
·
10.66K
· jmsalcido

Proper Background Content Loading in Android

My first pro-tip needs to be Android driven for sure.

You know... developing Android apps is a cool task, but developing beautiful Android apps is a much cooler task.

It is not easy to achieve this, you need to do some research at the official Android Developer and Design website, look at beautiful apps and else.

But lets take a look to this picture:
Good Progress

Image from +Roman Nurik on Google+

The first one with an old Dialog looks clumsy and ugly and the second one looks classy, simple and beautiful.

Welp, How can I do this?

Easy piece, we just need to remember 3 things:

  1. we need a proper layout
  2. we need a proper activity
  3. AsyncTask is master race

Nothing else, look at this piece of code for the layout:

activity_onload.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".LoadedActivity" >

    <!-- another layout that will be wrapped inside our layout -->
    <include layout="@layout/loaded_content" />

    <ProgressBar android:id="@+id/loading_spinner"
        style="?android:progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />
</FrameLayout>

You only set the ProgressBar wrapped in a FrameLayout and include a content layout.

loaded_content.xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/content"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <!-- in my example I will load an image from the network -->
    <ImageView
        android:id="@+id/imageview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</ScrollView>

Again, another easy layout, I used an ImageView because I will load an image over the internet, so you should add the proper Internet permission to achieve this.

(I used an image from Victoria Justice because she looks amazing, don't judge me.)

LoadedActivity.java

public class LoadedActivity extends Activity {

private View mContentView;
private View mLoadingView;
private int mAnimationDuration;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_loaded);

    // save the content
    mContentView = findViewById(R.id.content);

    // save the loading spinner
    mLoadingView = findViewById(R.id.loading_spinner);

    // remove content
    mContentView.setVisibility(View.GONE);

    // save the animation duration
    mAnimationDuration = getResources().getInteger(android.R.integer.config_longAnimTime);

    // save the imageView (at our content layout)
    ImageView imgView = (ImageView) findViewById(R.id.imageview);

    // the tag will hold the url to our image
    imgView.setTag("http://i.imgur.com/8or6G.jpg");

    // download our image with an asynctask look at the "post" event to see the loading process
    new DownloadImageTask(mContentView, mLoadingView, mAnimationDuration).execute(imgView);
}

as you see, on the onCreate method I just set the visibility of the content view to View.GONE and this does the trick.

Look at DownloadImageTask postExecute method and there is the magic, the DownloadImageTask class will do the loading work and the animation.

DownloadImageTask.java

private class DownloadImageTask extends AsyncTask<ImageView, Void, Bitmap> {

    // reference to our imageview
    private ImageView mImage;
private View mContent;
private View mSpinner;
private int mDuration;

public DownloadImageTask(View content, View spinner, int duration) {
  mContent = content;
  mSpinner = spinner;
  mDuration = duration;
}

    /** 
     * The system calls this to perform work in a worker thread and
     * delivers it the parameters given to AsyncTask.execute()
     */
    protected Bitmap doInBackground(ImageView... images) {
        mImage = images[0];
        String url = (String)image.getTag();
        return loadImageFromNetwork(url);
    }

    @Override
    protected void onPostExecute(Bitmap result) {
        // Set the image view before the content is shown.
        mImage.setImageBitmap(result);

        // Set the "show" view to 0% opacity but visible, so that it is visible
        mContent.setAlpha(0f);
        mContent.setVisibility(View.VISIBLE);

        // Animate the "show" view to 100% opacity, and clear any animation listener set on the view.
        mContent.animate()
                .alpha(1f)
                .setDuration(mShortAnimationDuration)
                .setListener(null);

        // Animate the "hide" view to 0% opacity.
        mSpinner.animate()
                .alpha(0f)
                .setDuration(mShortAnimationDuration)
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        hideView.setVisibility(View.GONE);
                    }
                });
    }

    /**
     * download an image from the internet!
     * @param url
     * @return
     */
    private Bitmap loadImageFromNetwork(String url) {
        Bitmap bm = null;
        try {
            URL urln = new URL(url);
            bm = BitmapFactory.decodeStream(urln.openConnection().getInputStream());
        } catch (IOException e) {
            Log.e("HUE","Error downloading the image from server : " + e.getMessage().toString());
        } 
        return bm;
    }
}

And that's all what you need to load content in the background in a very simple, classy, beautiful way.

Remember: Keep It Simple, Stupid.

PS. Someday I will learn to format the code at coderwall.com, meanwhile I should use public Gist or something else.