Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Android

Implementing Async Tasks in Android

4.89/5 (2 votes)
15 Feb 2019CPOL5 min read 8.8K  
How to implement async tasks in Android

Introduction

In an Android app, when we need to interact with external resources that may potentially take time such as to fetch data from external API or database, we would like the main UI to remain interactive and block the UI thread from functioning while long-running processes are active. Also note that by default, network task is not allowed to run in UI thread in Android.

If the main thread is used in fetching external data, then the main UI will not remain interactive while the data is being fetched and may show abnormal behavior if the data fetching process encounters an exception. In this case, the android's asynchronous task becomes handy especially for updating the portion of the UI using background threads.

An asynchronous task is one of the several ways to offload the work of main thread to some background thread. While AsyncTask is not the only option, it is simple and quite a common option.

To get started, I would like to visit Google's developer's page that contains information on the AsyncTask at https://developer.android.com/reference/android/os/AsyncTask to review some content related to implementing AsyncTasks.

The AsyncTask class is an abstract class. The implementation usually is the subclass of the class that runs on UI thread. The implementation of the AsyncTask, i.e., the subclass, will override at least one method and often two methods.

When an asynchronous task is executed, the task goes through 4 steps as described in the Android Developers page at https://developer.android.com/reference/android/os/AsyncTask:

  1. onPreExecute, invoked on the UI thread before the task is executed. This step is used to set up the task, for instance by making the spinner visible in the user interface.
  2. doInBackground(Params..., invoked on the background thread immediately after finishes executing. This step is used to perform background computation that can take a long time. The parameters of the asynchronous task are passed to this step. The result of the computation must be returned by this step and will be passed back to the last step. This step can also be used to publish one or more units of progress. These values are published on the UI thread, in the steponProgressUpdate(Progress...)
  3. onProgressUpdate(Progress..., invoked on the UI thread after a call to publishProgress(Progress... step. The timing of the execution is undefined. This method is used to display any form of progress in the user interface while the background computation is still executing. For instance, it can be used to animate a progress bar or show logs in a text field.
  4. onPostExecute(Result), invoked on the UI thread after the background computation finishes. The result of the background computation is passed to this step as a parameter.

I am going to go over the code to illustrate the working mechanism. The code is from the capstone project that I did for my Android Nano-degree Program at Udacity. The full code is available at https://github.com/benktesh/Capstone-Project. In this demo, I am using a lightweight version of the code as shown in the code block which is available below:

Java
package benktesh.smartstock;
import android.app.ActivityOptions;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import java.util.ArrayList;
import benktesh.smartstock.Model.Stock;
import benktesh.smartstock.UI.CommonUIHelper;
import benktesh.smartstock.UI.StockDetailActivity;
import benktesh.smartstock.Utils.MarketAdapter;
import benktesh.smartstock.Utils.NetworkUtilities;
import benktesh.smartstock.Utils.PortfolioAdapter;
import benktesh.smartstock.Utils.SmartStockConstant;

public class MainActivity extends AppCompatActivity implements
        MarketAdapter.ListItemClickListener, PortfolioAdapter.ListItemClickListener {

    private static final String TAG = MainActivity.class.getSimpleName();
    CommonUIHelper mCommonUIHelper;
    ArrayList<Stock> mMarketData;
    private Toast mToast;
    //The following are for market summary
    private MarketAdapter mAdapter;
    private RecyclerView mMarketRV;
    private ProgressBar spinner;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        spinner = findViewById(R.id.progressbar);

        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                Intent Email = new Intent(Intent.ACTION_SEND);
                Email.setType(getString(R.string.label_emailtype));
                Email.putExtra(Intent.EXTRA_EMAIL,
                        new String[]{getString
                           (R.string.label_developer_contat_email)});  //developer 's email
                Email.putExtra(Intent.EXTRA_SUBJECT,
                        R.string.label_feedback_subject); // Email 's Subject
                Email.putExtra(Intent.EXTRA_TEXT, 
                 getString(R.string.label_address_developer) + "");  //Email 's Greeting text
                startActivity(Intent.createChooser(Email, getString(R.string.label_send_feedback)));
            }
        });

        if (mCommonUIHelper == null) {
            mCommonUIHelper = new CommonUIHelper(this);
        }

        mMarketRV = findViewById(R.id.rv_market_summary);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        mMarketRV.setLayoutManager(layoutManager);
        mMarketRV.setHasFixedSize(true);
        mAdapter = new MarketAdapter(mMarketData, this);
        mMarketRV.setAdapter(mAdapter);
        LoadView();
    }

    private void LoadView() {
        Log.d(TAG, "Getting Market Data Async");
        new NetworkQueryTask().execute(SmartStockConstant.QueryMarket);
    }

    private void MakeToast(String msg) {
        Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
    }

    public boolean onCreateOptionsMenu(Menu menu) {
        return mCommonUIHelper.ConfigureSearchFromMenu(menu);

    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        if (mCommonUIHelper.MakeMenu(item)) return true;
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onListItemClick(Stock data) {
        if (mToast != null) {
            mToast.cancel();
        }
        Intent intent = new Intent(this.getApplicationContext(), StockDetailActivity.class);
        intent.putExtra(SmartStockConstant.ParcelableStock, data);
        Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(this).toBundle();
        startActivity(intent, bundle);
    }

    /*
    This is an async task that fetches data from network and new data is applied to adapter.
    Also makes a long toast message when fails to retrieve information from the network
    It takes void, void and returns ArrayList<?>
     */
    class NetworkQueryTask extends AsyncTask<String, Integer, ArrayList<Stock>> {

        private String query;

        @Override
        protected void onPreExecute() {
            if (spinner != null) {
                spinner.setVisibility(View.VISIBLE);
            }
        }

        @Override
        protected ArrayList<Stock> doInBackground(String... params) {
            query = params[0];

            ArrayList<Stock> searchResults = null;
            try {

                searchResults = NetworkUtilities.getStockData(getApplicationContext(), query);

                for (int i = 0; i <= 100; i = i + 25) {
                    Thread.sleep(500);
                    publishProgress(i);
                }

            } catch (Exception e) {
                Log.e(TAG, e.toString());
            }
            return searchResults;
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            super.onProgressUpdate(progress);
            Toast.makeText(getApplicationContext(), "Progress:  " + 
                           progress[0] + "(%)", Toast.LENGTH_SHORT).show();
        }

        @Override
        protected void onPostExecute(ArrayList<Stock> searchResults) {
            super.onPostExecute(searchResults);

            if (searchResults != null && searchResults.size() != 0) {
                mAdapter.resetData(searchResults);
            }

            if (spinner.getVisibility() == View.VISIBLE) {
                spinner.setVisibility(View.GONE);
            }
        }
    }
}

The class NetworkQueryTask implemented as a subclass of MainActivity and the subclass extends the Android's AsyncTask abstract class. The subclass can be defined as below:

Java
private class NetworkQueryTask extends AsyncTask<T1, T2, T3> {...}

The T1, T2, and T3 are datatypes of parameters and each of them has some specific meaning.

The task defined above can be executed as below:

Java
new NetworkAsyncTask().execute(param1);

The param1 is of the same type as the type of T1.

The MainActivity runs on UI thread. The 'onCreate(..)' method does setting up the UI. The setting up involves creating adapters for recycler view and so on and finally calls a LoadView(). The LoadView() method executes AsyncTask to fetch the data from the network and update the adapter of the view.

In doing so, we create a subclass NetworkQueryTask that extends from AsyncTask. The class takes three parameters, string, Void and ArrayList<Stock>. A stock is a simple class to store information about Stock. As soon as the process starts, we want the spinner to be visible that we can do in the doInBackground(..) method.

In the task above, the three parameters denote the type of input parameters used for doInBackground(T1 param1), onProgressUpdate(T2 param2) and onPostExecute(T3 param3). When the doInBackground) step finishes execution, the param3 will be the output of the doInBackground step and will become an input to the onPostExecute(param3) method.

The subclass in general overrides at least one method, most often, doInBackground(..) method and also a second method, i.e., onPostExecute(). The onProgressUpdate and onPreExecute() methods are optional and can be skipped. Thus, if there is nothing to be done regarding the progress update, then there is no need for overriding onProgressUpdate and then param2 can be of type Void in the class definition itself. For example, suppose there is a need to pass a string parameter to doInBackground() and no need for an onProgressUpdate() method and the onPostExecute() method takes a string parameter, then the class definition would look like below:

Java
private class NetworkQueryTask extends AsyncTask<String, Void, String> {...}

Therefore, we can say that the three parameters respectively represent input for doInBackground, input to onProgressUpdate(), and input to onPostExecute. The parameter type for output of doInBackground is the same as the input to onPostExectute(). Also, if the async task is to function as fire and forget such as trigger something, then all the parameters can be void. For example, the definition of the subclass, in this case, would look like the following:

Java
private class NetworkQueryTask extends AsyncTask<Void, Void, Void> {...}

and the above class is executed as below:

Java
new NetworkAsyncTask().execute();

AsyncTasks are not aware of the other activities in the app and therefore must be handled correctly when the activity is destroyed. Therefore, AsycnTask is not suitable for long running operations because if the app is in the background and when Android terminates the app that called the AsyncTask, the AsyncTask does not get killed and we have to manage the process of what to do with the results of the AsyncTask. Thus, AsyncTasks are useful in fetching data that is not long running. There are other alternatives to AyscTask which are IntentServices, Loader, and JobScheduler and many Java-based implementations.

A youtube video on with code demonstration is available at https://youtu.be/Yxg_janIavw.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)