Thursday, 25 August 2011

Android version getting programatically

            try{
                     int currentapiVersion = android.os.Build.VERSION.SDK_INT;
                     if(currentapiVersion>=7){
                       //write your logic
                     }
                 }catch (Exception e) {
                    // TODO: handle exception
                }

HttpGet Conncetion in andorid

public static InputStream openHttpConnectionn(String urlStr) {

        InputStream in = null;

        int resCode = -1;

        try {
        if(Constants.LOG)Log.d("openHttpConnection", "URL:"+urlStr);
        URL url = new URL(urlStr);
        URLConnection urlConn = url.openConnection();//Opening Connection
                    
        if (!(urlConn instanceof HttpURLConnection)) {

                      throw new IOException ("URL is not an Http URL");
        }
        if(Constants.LOG)Log.d("openHttpConnection", "httpConn:1");

        HttpURLConnection httpConn = (HttpURLConnection)urlConn;

        httpConn.setAllowUserInteraction(false);

        httpConn.setInstanceFollowRedirects(true);
        httpConn.setRequestMethod("GET");
        httpConn.setRequestProperty("User-Agent", "Mozilla/5.0 (X11; U; Linux "+"i686; en-US; rv:1.8.1.6) Gecko/20061201 Firefox/2.0.0.6 (Ubuntu-feisty)");
        httpConn.setRequestProperty("Content-Type", "image/*");
//        HttpConnectionParams.setConnectionTimeout((HttpParams) httpConn, Constants.CONNECTIONTIMEOUT);
//        HttpConnectionParams.setSoTimeout((HttpParams) httpConn, Constants.CONNECTIONTIMEOUT);
        httpConn.setConnectTimeout(Constants.CONNECTIONTIMEOUT);
        httpConn.setReadTimeout(Constants.CONNECTIONTIMEOUT);
       
        if(Constants.LOG)Log.d("openHttpConnection", "setting requests and properties");
       
        httpConn.connect();
       
        if(Constants.LOG)Log.d("openHttpConnection", "Connected successfully");
       
        resCode = httpConn.getResponseCode();  
       
        if(Constants.LOG)Log.d("openHttpConnection", "resCode"+resCode);
       
        if (resCode == HttpURLConnection.HTTP_OK) {
       
        in = httpConn.getInputStream();
       
        if(Constants.LOG)Log.d("openHttpConnection", "Fetchinf data done");
       
        }       

        } catch (MalformedURLException e) {

                      e.printStackTrace();

                      if(Constants.LOG)Log.d("openHttpConnection", "1"+e);

        } catch (IOException e) {

                      e.printStackTrace();

                      if(Constants.LOG)Log.d("openHttpConnection", ""+e);

        }catch (Exception e) {
            // TODO: handle exception
            return null;
        }

        return in;
}

Validating Xml in andorid

Some times the response ,came from webservice may contains &lt,&gt.

Inthis case You have to convert '&lt' as '<' and '&gt' as '>'.

public static String validateXml(String xml)
    {
        int offset=0;
        StringBuffer temp=new StringBuffer();
        try{
            while(xml.indexOf("&lt;",offset)!=-1||xml.indexOf("&gt;",offset)!=-1&&offset<xml.length()){
                // if(Constants.LOG)Logger.info("Into &lt and & rt");
                if((xml.indexOf("&lt;",offset)<xml.indexOf("&gt;",offset))&&(xml.indexOf("&lt;",offset)!=-1)||((xml.indexOf("&gt;",offset)==-1)&&(xml.indexOf("&lt;",offset)!=-1))){
                // if(Constants.LOG)Logger.info("Converting &lt into <");
                temp.append(xml.substring(offset, xml.indexOf("&lt;",offset)));
                temp.append("<");
                offset=xml.indexOf("&lt;",offset)+"&lt;".length();
        }
        else if((xml.indexOf("&gt;",offset)<xml.indexOf("&lt;",offset)&&(xml.indexOf("&gt;",offset)!=-1))||((xml.indexOf("&lt;",offset)==-1)&&(xml.indexOf("&gt;",offset)!=-1))){
            // if(Constants.LOG)Logger.info("Converting &lt into >");
            temp.append(xml.substring(offset, xml.indexOf("&gt;",offset)));
            temp.append(">");
            offset=xml.indexOf("&gt;",offset)+"&gt;".length();
        }
        // if(Constants.LOG)Logger.info("offset is : "+offset);
        }
        if(offset<xml.length()){
            temp.append(xml.substring(offset));
        }
        }catch (Exception e) {
        // TODO: handle exception
            //if(Constants.LOG)Logger.info("Array out of bound of exception");
        }
            xml=temp.toString();
        return xml;
    }

Httppost method in android

    private  static HttpClient httpClient;
    private  static String ret;
    private  static HttpPost httpPost;
    private  static HttpResponse response;
    private  static HttpContext localContext;
public static InputStream postStage(String url, String data,String soapAction) {
        // TODO Auto-generated method stub
        InputStream is = null;
        ret = null;
        httpClient=new DefaultHttpClient();
        httpClient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.RFC_2109);
        httpPost = new HttpPost(url);
        response = null;
        StringEntity tmp = null;    
        if(Constants.LOG)Log.d("url is"+url,"poststage"+data);
        httpPost.setHeader("User-Agent", "Mozilla/5.0 (X11; U; Linux " +
            "i686; en-US; rv:1.8.1.6) Gecko/20061201 Firefox/2.0.0.6 (Ubuntu-feisty)");
       
        httpPost.setHeader("Accept", "text/html,application/xml," +
            "application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5");
       
        httpPost.setHeader("Content-Type", " text/xml; charset=utf-8");
//       httpPost.setHeader("Content-Length",""+data.getBytes().length);
        httpPost.setHeader("SOAPAction", soapAction);
           try {
               tmp = new StringEntity(data,"UTF-8");
           } catch (UnsupportedEncodingException e) {
               if(Constants.LOG)Log.d("HTTPHelp : UnsupportedEncodingException : ",""+e);
           }
           httpPost.setEntity(tmp);
          
           HttpParams httpParameters = new BasicHttpParams();
           HttpConnectionParams.setConnectionTimeout(httpParameters, Constants.CONNECTION_TIMEOUT);
           HttpConnectionParams.setSoTimeout(httpParameters, Constants.CONNECTION_TIMEOUT);
           try {
               DefaultHttpClient httpClient = new  DefaultHttpClient(httpParameters);
               response =(BasicHttpResponse) httpClient.execute(httpPost);
           }catch(UnknownHostException e){
               if(Constants.LOG)Log.d("HTTPHelp : ClientProtocolException : ",""+e);
               e.printStackTrace();
           }catch(ConnectTimeoutException e){
               if(Constants.LOG)Log.d("HTTPHelp : ClientProtocolException : ",""+e);
               e.printStackTrace();
           }catch (ClientProtocolException e) {
               if(Constants.LOG)Log.d("HTTPHelp : ClientProtocolException : ",""+e);
               e.printStackTrace();
           }catch (IOException e) {
               if(Constants.LOG)Log.d("HTTPHelp : IOException : ",""+e);
               e.printStackTrace();
           }catch (Exception e) {
                // TODO: handle exception
               e.printStackTrace();
               return null;
           }
           try {
               ret = response.getStatusLine().toString();
               HttpEntity he = response.getEntity();
               InputStream inStream = he.getContent();
               is = inStream;
               Log.d("input stream is",""+inStream);
           } catch (IllegalStateException e) {
             // TODO Auto-generated catch block
               e.printStackTrace();
           } catch (IOException e) {
             // TODO Auto-generated catch block
               e.printStackTrace();
           }
           catch (Exception e) {
               // TODO: handle exception
               return null;
           }
           if(Constants.LOG)Log.d("in", "post"+is);
   
           return is;
    }//postStage()

Geeting Device ip in andorid

For Emulator You will get ip as 10.0.0.1

Write following code in your Util and use it any where.

public  String getLocalIpAddress() {
            try {
                for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
                    NetworkInterface intf = en.nextElement();
                    for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
                        InetAddress inetAddress = enumIpAddr.nextElement();
                        if (!inetAddress.isLoopbackAddress()) {
                            return inetAddress.getHostAddress().toString();
                        }
                    }
                }
            } catch (SocketException ex) {
                Log.e("Exception", ex.toString());
                return "";
            }
            return "";
        }


capturing screen in android

        setContentView(R.layout.main);
        bn=(Button)findViewById(R.id.button1);
        iv=(ImageView)findViewById(R.id.imageView1);
       
        bn.setOnClickListener(new OnClickListener(){

            public void onClick(View v) {
            // TODO Auto-generated method stub
            View v1=bn.getRootView(); //gives total view
            v1.setDrawingCacheEnabled(true);
            Bitmap bm=v1.getDrawingCache();
            if(bm!=null){
            iv.setImageBitmap(bm);
            }
            }

        });

Checking Internet Connection in android

 In mainfest you need to add the following permissions
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
 <uses-permission android:name="android.permission.INTERNET"></uses-permission>

And write following piece of code in your Util class an use it Whereever you required

 private boolean checkInternetConnection() {

            ConnectivityManager conMgr = (ConnectivityManager) getSystemService (Context.CONNECTIVITY_SERVICE);
             NetworkInfo info= conMgr.getActiveNetworkInfo(); 
            if (info != null && info.isConnected()) {
            return true;
            } else {
            return false;
            }
        }



Drawable Mutations

Android’s drawables are extremely useful to easily build applications.
A Drawable is pluggable drawing container that is usually associated with a 
View. For instance, a BitmapDrawable is used to display images, a ShapeDrawable 
to draw shapes and gradients, etc. You can even combine them to create 
complex renderings.
 
 
Drawable star = context.getResources().getDrawable(R.drawable.star);
if (your specific condition) {
  star.setAlpha(255); // opaque
} else {
  star.setAlpha(70); // translucent
}
 
Here cathodal of specific sea item in the following screen is opaque remaning
are transculacent 
 
drawable_mutations_02
 

Tuesday, 23 August 2011

Scale animation in android

 ScaleAnimation for making things bigger and smaller

[?xml version="1.0" encoding="utf-8"?]
[scale xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXScale="0.5"
android:toXScale="2.0"
android:fromYScale="0.5"
android:toYScale="2.0"
android:pivotX="0%"
android:pivotY="50%"
android:startOffset="0"
android:duration="400"
android:fillBefore="true" /]

Exploring Android Charting and Graphs solutions

1. Google chart API
2. Achartengine
3. ChartDroid

I tried creating a Pie Chart while using the solutions. Google charting API being the easiest one to integrate is taken first

Google Charting API support

Using Google charting API, we can create charts and graphs directly by providing data to the Google charting service
The call to the Google's charting API looks like below:
More on Google charting API can be found here
For creating a pie chart using Google charting solution, I created the following URI
1
http://chart.apis.google.com/chart?cht=p3&amp;chd=t:30,60,10&amp;chs=250x100&amp;chl=cars|bikes|trucks
Within my activity's onCreate() I included the code below
1
2
3
4
5
6
7
8
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WebView googleChartView = new WebView(this);
setContentView(googleChartView);
String mUrl = "http://chart.apis.google.com/chart?cht=p3&amp;chd=t:30,60,10&amp;chs=250x100&amp;chl=cars|bikes|trucks";
googleChartView.loadUrl(mUrl);
}
In the onCreate() method, I am creating a WebView and just loading it with the URL response.
It needs an internet access so I included the code below within AndroidManifest.xml
1
<uses-permission android:name="android.permission.INTERNET" />
That's it!!! we are done with the first approach. The screen shot for the application I build using Google chart API looks like below:
Google Charting API PIE Chart
Google Charting API PIE Chart
The only issue I had with this approach is that the device should have internet access available which in my case may not be always there. This small limitation made me look into other approaches and I came across another solution Achartengine.

Achartengine

This Achart's charting support comes in the form of a download-able Jar.
I added this into the lib folder of my application.
Library Folder
Android App Structure
I created a class with a method that gives out a Pie chart intent as below
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class AChartExample {
 
public Intent execute(Context context) {
int[] colors = new int[] { Color.RED, Color.YELLOW, Color.BLUE };
DefaultRenderer renderer = buildCategoryRenderer(colors);
 
CategorySeries categorySeries = new CategorySeries("Vehicles Chart");
categorySeries.add("cars ", 30);
categorySeries.add("trucks", 20);
categorySeries.add("bikes ", 60);
 
return ChartFactory.getPieChartIntent(context, categorySeries, renderer);
}
 
protected DefaultRenderer buildCategoryRenderer(int[] colors) {
DefaultRenderer renderer = new DefaultRenderer();
for (int color : colors) {
SimpleSeriesRenderer r = new SimpleSeriesRenderer();
r.setColor(color);
renderer.addSeriesRenderer(r);
}
return renderer;
}
}
Within the execute method a SimpleSeriesRender is created with the required color that is required in the pie chart.
These renderers are then added to the DefaultRender.
Next step is to create a CategorySeries with the appropriate data. Once this is done we can get achartingengine specific intent back with the following call

ChartFactory.getPieChartIntent(context, categorySeries, renderer);

With this intent we launch our activity and the code goes as below
1
2
Intent achartIntent = new AChartExample().execute(this);
startActivity(achartIntent);
The screen shot below shows the generated PIE chart using Achart library
Achart

The other charting solution I explored is chartDroid

Chartdroid

This solution needs the ChartDroid Application to be installed along with the application which intends to use the charting solution.
Although, we can install the application programmaticly from the Android apps market place, I directly installed the application into my device to explore it further.To install it download the apk from here and install it to the device.
The Chartdroid's charting solution requires a content provider to be created. Below is the content provider's code that I created
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ChartDroidDataProvider extends ContentProvider {
 
static final String AUTHORITY =  "com.xyz.contentprovider.chardroid";
 
@Override
public String getType(Uri uri) {
return "vnd.android.cursor.dir/vnd.com.googlecode.chartdroid.graphable";
}
 
public static final Uri PROVIDER_URI = new Uri.Builder().scheme(
ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build();
 
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
 
// Fetch the actual data
 
MatrixCursor c = new MatrixCursor(new String[]                  { BaseColumns._ID,
"COLUMN_AXIS_INDEX", "COLUMN_SERIES_INDEX",
"COLUMN_DATUM_VALUE", "COLUMN_DATUM_LABEL" });
 
c.newRow().add(1).add(0).add(1).add(30).add(null);
c.newRow().add(2).add(0).add(1).add(10).add(null);
c.newRow().add(3).add(0).add(1).add(60).add(null);
 
return c;
}
 
}
In the code above, I have excluded some methods which are the default IDE's implementations. The query method provides the data along with other details which are required to build a chart. The data can come from database as well. In the simplistic case of generating a PIE chart I created a MatixCursor to serve as my PIE chart data. In the cursor, I am adding three rows which takes data values as 30,10 and 60 respectively and the labels as null.
For detailed information on the other parameters please look here
Now, to display the Chartdroid activity with the PIE Chart backed by data from the content provider created above an Intent is to be created.
The implementation goes like below:
1
2
3
4
5
6
Intent chartDroidIntent = new Intent(Intent.ACTION_VIEW,
ChartDroidDataProvider.PROVIDER_URI);
chartDroidIntent.putExtra(Intent.EXTRA_TITLE, "Chart droid");
chartDroidIntent
.addCategory("com.googlecode.chartdroid.intent.category.PIE_CHART");
startActivity(chartDroidIntent);
In the code above adding category is important as it decides what form of charting is required. In my case it's PIE_CHART.
Also, we can pass in most of the data in form of Intent's extras, except the actual data. In example above the title information is going as the extra.
Launching the ChartDroid's activity gives the results as below:
Chartdroid
Chartdroid
Complete source code for the application can be downloaded here

Translate animation in android

 TranslateAnimation for moving things around

<?xml version="1.0" encoding="utf-8"?>
<translate
    android:fromXDelta="200%"
    android:toXDelta="0%"
    android:fromYDelta="200%"
    android:toYDelta="0%"
    android:duration="3000"
    android:zAdjustment="top" />

How to Handle progress dialogs and orientation changes

Introduction

ProgressDialogs in Android are easy. At least, that was my initial thought. As long as you don’t change the screen orientation that is. Once you start moving your phone around after – or worse, during – an active progress dialog, you’ll soon find that out that your application isn’t behaving the way it should.
Luckily for us, there are several ways to tackle this.I’ve created a sample application, available on GitHub outlining the problem, and some solutions of dealing with it gracefully. The repository is located here.
ProgressDialogs in Android are easy. At least, that was my initial thought. As long as you don’t change the screen orientation that is. Once you start moving your phone around after – or worse, during – an active progress dialog, you’ll soon find that out that your application isn’t behaving the way it should. Luckily for us, there are several ways to tackle this.I’ve created a sample application, available on GitHub outlining the problem, and some solutions of dealing with it gracefully. The repository is located here.

handling_progress_orientation_01

When starting the application, you’ll get the following screen:

handling_progress_orientation_02

Each button launches an activity that populates a listview. We’ve simulated a long running operation by introducing a SystemClock.sleep(LONG_RUNNING_TIME). The goal of each Activity is to populate a list while informing the user that populating that list might take a while. This is accomplished by presenting the user with a progress dialog.

The app allows you to launch the following activities.
  • AllOnUIThread: A sample where a progress dialog is created and dismissed on the UI thread. Added to illustrate that the progress dialog will never be shown.
  • AsyncTaskSimple: Implementation using an AsyncTask, where the progress dialog will be shown, but causes problems during screen orientation.
  • AsyncTaskSimpleStateSaving: Implementation based on the previous AsyncTask, but now with a simple state saving mechanism, again causing problems during screen orientation.
  • AsyncTaskSimpleConfigured: Implementation based on android:configChanges=”keyboardHidden|orientation”, where the activity is not re-created after a screen orientation change in order to handle screen orientations gracefully.
  • AsyncTaskComplex: A more complex implementation that handles screen orientations gracefully without resorting to the android:configChanges “fix”.
We’re going to see how these activities behave in general, and more specific when the screen changes orientation.



1. Doing everything on the main UI thread

The topic of dialogs is outlined in the Android Developer SDK Dev Guide. Although it mentions how to create different kind of dialogs, it doesn’t explicitly mention where these dialogs should be created, shown and dismissed. There is a brief topic on threads, but it doesn’t really stand out in the article.

So, the first time I tried to create a dialog, I did it like this:

1
2
3
4
5
6
7
8
@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
   	showDialog(LOADING_DIALOG);
   	this.listItems = retrieveListLongRunning();
   	refreshListView();
   	dismissDialog(LOADING_DIALOG);
}

To my surprise, no dialog was showing on the screen (as can be verified using the sample app). Although the dialog got created and dismissed, I never got to see it on my screen. The reason for this is is the fact that the code you’re seeing here is executed on the main UI thread. This thread is responsible for keeping the Activity alive, and allowing the user to interact with it. As a rule of thumb, you should never do long running tasks here, as they basically block up the UI, and can cause an application to go unresponsive. Android even has a built-in mechanism to warn the user that an application has gone unresponsive, by showing the following dialog:

handling_progress_orientation_03

Conclusion here is that want to avoid this dialog from popping up at all cost. The reason our progress dialog wasn’t showing up was due to the fact that we told it to show up while the main UI thread was busy doing other things.



2. Using an Async Task to do the heavy lifting.

Android has the concept of an AsyncTask, where background tasks can be executed in order to avoid the UI to become unresponsive. It does so by spawning a background thread where that background task is executed. Implementing an AsyncTask is fairly easy, and a common implementation will look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
 * Our background task split up into 3 sections
 *
 * onPreExecute - showing the dialog in the UI thread (just before doInBackground is called).
 * doInBackground - launches a seperate thread to refresh the list data
 * onPostExecute - dismisses the dialog and refreshes the list control.
 *
 */
private class ListRefresher extends AsyncTask<Uri, Void, Void> {
 
	/**
	 * This is executed in the UI thread. The only place where we can show the dialog.
	 */
	@Override
	protected void onPreExecute() {
		showDialog(LOADING_DIALOG);
	}
 
	/**
	 * This is executed in the background thread.
	 */
	@Override
	protected Void doInBackground(Uri... params) {
		refreshListLongRunning();
		return null;
	}
 
	/**
	 * This is executed in the UI thread. The only place where we can show the dialog.
	 */
   @Override
      protected void onPostExecute(Void unused) {
   	   dismissDialog(LOADING_DIALOG);
   	   refreshListView();
      }
}

As can be seen in the included JavaDoc, there are 3 main hooks that we get when using AsyncTasks
  • onPreExecute
  • doInBackground
  • onPostExecute
The first and last hook are executed on the main UI thread. This is the place where we’ll respectively show and dismiss the dialog. In between (doInBackground), we’ll do the heavy lifting in the background thread that is provided by the AsyncTask.
A good way to illustrate (and comprehend) is by using a debugger. Put a breakpoint in these 3 methods.

When the onPreExecute breakpoint is suspended, we can see that it was called on the main UI thread (identified by thread nr 3 – main).

handling_progress_orientation_04


When the breakpoint on the doInBackground is suspended in the debugger, we can see that it is not in main UI thread, but in a seperate thread provided to us by the AsyncHandler ((identified by thread nr 25 – AsyncTask #5)

handling_progress_orientation_05

Our main UI thread is basically sitting idle, leaving it with plenty of  room to show the progress dialog. This wasn’t the case in our first example, as the UI thread was also blocked as it was busy retrieving the items for the list.

Once the background processing is ended, our third hook will be called, onPostExecute, back on the main UI thread.

handling_progress_orientation_06

So far so good…. with this approach, we’re able to show the progress dialog before the background processing starts, and dismiss it when the background processing has finished.


BUT, there is one big catch though …. if we change the orientation of our phone after the data is initially shown, we’ll start to see the strange behavior I mentioned in the introduction.

What happens is that during the screen orientation our Activity is re-created, the onCreate method is called again, triggering the execution of the AsyncTask. After the screen orientation, our Activity shows an initial empty list, followed by our progress dialog. Our list gets refreshed in the background, but what we’re seeing is that the progress dialog remains on the screen. So even after dismissing it (during the onPostExecute), the dialog remains on the screen.

To make matters worse, when changing the orientation multiple times in a row, your application will eventually crash with the following exception:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java.lang.IllegalArgumentException: no dialog with id 0 was ever shown via Activity#showDialog
     at android.app.Activity.missingDialog(Activity.java:2521)
     at android.app.Activity.dismissDialog(Activity.java:2511)
     at com.ecs.android.listview.sample.AsyncTaskSimple$ListRefresher.onPostExecute(AsyncTaskSimple.java:92)
     at com.ecs.android.listview.sample.AsyncTaskSimple$ListRefresher.onPostExecute(AsyncTaskSimple.java:1)
     at android.os.AsyncTask.finish(AsyncTask.java:417)
     at android.os.AsyncTask.access$300(AsyncTask.java:127)
     at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:429)
     at android.os.Handler.dispatchMessage(Handler.java:99)
     at android.os.Looper.loop(Looper.java:123)
     at android.app.ActivityThread.main(ActivityThread.java:4363)
     at java.lang.reflect.Method.invokeNative(Native Method)
     at java.lang.reflect.Method.invoke(Method.java:521)
     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860)
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
     at dalvik.system.NativeStart.main(Native Method)

By changing the orientation multiple times, a race condition occurs where a certain Activity will try and dismiss the dialog that was created by the previous Activity. This s a result of the fact that during screen orientation changes, the current Activity is destroyed, and a new Activity is created.

One option of dealing with this is to avoid refreshing the data after a screen orientation change by properly saving your activity state.



3. Using an Async Task with state saving.

So everytime the screen orientation changes, Android will re-creates the activity, meaning that the Activity will go through the entire lifecycle again. This means that the onCreate method will be executed again. Android provides a method called onRetainNonConfigurationInstance to help us deal with this situation.

There are 3 important things we need to know about this method:
  • Called between onStop() and onDestroy().
  • A new instance of the activity will always be immediately created after this one’s onDestroy() is called.
  • The object you return here will always be available from the getLastNonConfigurationInstance() method of the following activity instance as described there.
1
2
3
4
5
6
7
8
/**
 * Cache the already fetched listItems.
 * This will be picked up in the onCreate method.
 */
   @Override
   public Object onRetainNonConfigurationInstance() {
   	return listItems;
   }

During the onCreate, we can check for saved state, and use that instead of refreshing the values again. Basically, after changing the screen orientation, the retained object will contain our list items, so instead of launching the AsyncTask again, we can simply capture the previous list items from the getLastNonConfigurationInstance().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * Intelligent onCreate that takes into account the last known configuration instance.
 * When an activity is destroyed (as a result of a screen rotate), the onRetainNonConfigurationInstance
 * method is called, where we can return any state that we want to restore here.
 */
@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	Object retained = getLastNonConfigurationInstance();
	if (retained==null) {
		new ListRefresher().execute();
	} else {
		this.listItems = (List<Map<String, String>>) retained;
		refreshListView();
	}
}

The code above is a simple state saving mechanism, that only saves our listItems. Again, problems will arise when changing the screen orientation.
  • Changing the screen orientation after the asynctask has completed, won’t cause any issues, as our listItems have already been retrieved and can be restored by the method above. The screen will change orientation and the same items will be shown. The progressdialog won’t get triggered because the AsyncTask completed.
  • When changing the screen orientation during the asynctask execution we’ll get the same result as the previous sample without state saving. Our progress dialog remains on the screen forever


4. Configuring the Activity to not re-create itself after screen orientation change

By now it should be clear that when the screen is oriented, the Activity is re-created, meaning that the old one is destroyed, and a new instance is created. As a consequence of this behavior, we need to ensure that our onCreate method is always implemented properly. Long running operations, and heavy initialization should not be done in the onCreate, as each time the user will change the screen orientation, this method will be executed. Usually, when the user changes his screen orientation, he does so because he might feel that a landscape / portrait mode is more suitable to view the current activity. He basically wants a new screen layout, but what he doesn’t necessarily want is that his Activity is re-created or re-initialized.
Android provides an option for this, by configuring the activity like this in the manifest file.

1
2
3
<activity android:name=".AsyncTaskSimpleConfigured"
          android:configChanges="keyboardHidden|orientation"
          android:label="AsyncTaskSimpleConfigured"/>

What this does is that it allows the developer to listen for these configChanges, and act on them appropriately by implementing the following method:

1
2
3
4
5
@Override
public void onConfigurationChanged(Configuration newConfig) {
  super.onConfigurationChanged(newConfig);
  setContentView(R.layout.myLayout);
}

When running the sample “Using AsyncTask Manifest Configured”, we see that the progress dialog is show properly, and that the Activity survives all screen orientations. The AsyncTask keeps running in the background, and informs the UI that it has finished it work, refreshing the listview and dismissing the dialog. The onCreate method isn’t called during the screen orientation change.



5. Implement a more intelligent AsyncTask dealing with screen orientation

There are several reasons why you wouldn’t want to implement the solution above. Many people seem to consider this more of a hack then anything else, and even Google is not in favor of this approach. An alternative is to implement more robust logic inside your Activity / AsyncTask to gracefully handle the situation where a screen rotate occurs.
This solution was discussed at length in the Android developers forum, and I’ve provided a working example in my github repository based on that solution in the sample application. The idea here is that we associate our Activity with our AsyncTask. In the Commonsguy github repository you’ll find a similar example with a progress bar.
In the AsyncTask implementations above, the AsyncTask is completely decoupled from the Activity, meaning that the AsyncTask has no reference to the Activity, and as such is unaware what Activity has launched it. Obviously, Android internally is aware of this, but this isn’t exposed to the developer. In this solution, we’ll create a reference to the Activity in the AsyncTask by passing the Activity in the constructor of the AsyncTask.

We’ll also expose a flag to the activity that indicates if the task has completed, and if the progress dialog is visible on the screen. These 3 items (the reference to the Activity from within the AsyncTask, and the 2 flags) allow us to gracefully handle a screen orientation.

Our new AsyncTask looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
   /**
    * Our complex async task that holds a reference to the Activity that started it,
    * and a boolean to determine if the task completed.
    */
private class ListRefresher extends AsyncTask<Uri, Void, Void> {
 
	private AsyncTaskComplex activity;
        private boolean completed;
 
        private List<Map<String, String>> items = new ArrayList<Map<String, String>>();
 
        private ListRefresher(AsyncTaskComplex activity) {
                this.activity = activity;
        }
 
        public List<Map<String, String>> getItems() {
		return items;
	}
 
       /**
        * Showing the dialog on the UI thread.
        */
	@Override
	protected void onPreExecute() {
		activity.showDialog(LOADING_DIALOG);
	}
 
       /**
        * Performing the heavy lifting in the background thread thread.
        */
	@Override
	protected Void doInBackground(Uri... params) {
		items = retrieveListLongRunning();
		return null;
	}
 
	/**
	 * When the task is completed, notifiy the Activity.
	 */
       @Override
       protected void onPostExecute(Void unused) {
               completed = true;
               notifyActivityTaskCompleted();
       }
 
       private void setActivity(AsyncTaskComplex activity) {
               this.activity = activity;
               if ( completed ) {
               	notifyActivityTaskCompleted();
               }
       }
 
       /**
        * Helper method to notify the activity that this task was completed.
        */
       private void notifyActivityTaskCompleted() {
               if ( null != activity ) {
               	activity.onTaskCompleted();
               }
       }
}

Important to note is that
  • The AsyncTask has a reference to the Activity that created it.
  • The Activity is associated to the AsyncTask using the constructor when the activity is first created, and through a setter when the activity is re-created after a screen orientation change.
  • The AsyncTask has a flag indicating if it completed execution.
  • The AsyncTask can notify the Activity when the task is completed.
When performing a screen orientation change, we make sure that our AsyncTask is saved through the following method (in order for it to be picked up again in the onCreate method.
We also remove the association with the current activity at this point, as it is in the process of being destroyed.

1
2
3
4
5
6
7
8
9
10
/**
 * After a screen orientation change, this method is invoked.
 * As we're going to state save the task, we can no longer associate
 * it with the Activity that is going to be destroyed here.
 */
   @Override
   public Object onRetainNonConfigurationInstance() {
           mTask.setActivity(null);
           return mTask;
   }

In our Activity we have the following onCreate method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
   /**
    * After a screen orientation change, we associate the current ( = newly created) activity
    * with the restored asyncTask.
    * On a clean activity startup, we create a new task and associate the current activity.
    */
@Override
   public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
 
       Object retained = getLastNonConfigurationInstance();
       if ( retained instanceof ListRefresher ) {
               Log.i(TAG, "Reclaiming previous background task.");
               mTask = (ListRefresher) retained;
               mTask.setActivity(this);
       } else {
               Log.i(TAG, "Creating new background task.");
               mTask = new ListRefresher(this);
               mTask.execute();
       }
   }

When the screen orientation changes, the getLastNonConfigurationInstance() method returns our AsyncTask. We now need to associate the current Activity with this task (as the previous Activity was destroyed due to the screen orientation change). Keep in mind that during the screen orientation, the AsyncTask keeps on processing (doInBackGround).

When the AsyncTask finishes its work, it will notify the activity by calling its onTaskCompleted() method. That method is implemented like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * When the aSyncTask has notified the activity that it has completed,
 * we can refresh the list control, and attempt to dismiss the dialog.
 */
   private void onTaskCompleted() {
       Log.i(TAG, "Activity " + this + " has been notified the task is complete.");
       this.listItems = mTask.getItems();
       refreshListView();
       //Check added because dismissDialog throws an exception if the current
       //activity hasn't shown it. This Happens if task finishes early enough
       //before an orientation change that the dialog is already gone when
       //the previous activity bundles up the dialogs to reshow.
       if ( mShownDialog ) {
               dismissDialog(LOADING_DIALOG);
               Toast.makeText(this, "Finished..", Toast.LENGTH_LONG).show();
       }
   }