Monday, 22 September 2014

Handling Orientation Changes and dismissal of Alert dialog


An alert dialog is basically used for displaying an alert to the user. It can also be used for displaying a custom view to the user. The most important difficulty developers face is that of orientation changes i.e when a device is rotated i.e. from portrait to landscape or landscape to portrait, then the alert dialog is dismissed. Sometimes it may also happens that the alert dialog window during orientation changes may leak memory. So, in order to prevent all these issues from making your app unstable, we need to handle it. I will show you how to do it.

First of all i will post the whole code, and then i will explain how to handle configuration changes and memory leak in alert dialog.

activity_c.xml


    <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
                 android:id="@+id/tbllyt"
                 android:layout_width="match_parent"
                 android:layout_height="300dp"
                 android:layout_centerInParent="true"
                 android:stretchColumns="0,1"
                 android:shrinkColumns="2"
                 android:collapseColumns="3"
                 android:layout_span="2"
                 android:background="@color/tbllytbckgrnd" >
     
       <TableRow >
           <Button android:id="@+id/btn"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:text="BUTTON"
                   android:textSize="25sp"/> 
            <Button android:id="@+id/alertbtn"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="ALERT" 
                    android:textSize="25sp"/>        
       </TableRow>
    </TableLayout>

ActivityC.java
package com.anroid.androprac;

import java.util.ArrayList;
import java.util.Arrays;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;


public class ActivityC extends Activity  {

Context context = this;
TextView tv;
int checkedItem;
String selectedItem;
int configChangeFlag=0;
AlertDialog alertDialog;
CharSequence [] countries;
boolean [] selectedCountries;
ArrayList<String> checkedCountries = new ArrayList<String>();
boolean submitBtnClicked, isDialogCancelled;
Button alertbtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_c);
countries = getResources().getStringArray(R.array.Countries);

if(savedInstanceState != null){
configChangeFlag = savedInstanceState.getInt("configChangeFlag");
selectedCountries = savedInstanceState.getBooleanArray("checkedCountries");
submitBtnClicked = savedInstanceState.getBoolean("submitBtnClicked");
isDialogCancelled = savedInstanceState.getBoolean("isDialogCancelled");
} else {
selectedCountries = new boolean[countries.length];
}
if(configChangeFlag==1 && submitBtnClicked == false && isDialogCancelled == false){
invokeAlert();
 } 
Button tb = (Button)findViewById(R.id.btn);
tb.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ActivityC.this, DynamicCheckboxes.class);
startActivity(intent);
}
});
alertbtn = (Button)findViewById(R.id.alertbtn);
alertbtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
invokeAlert();

}
});
}
    
public void invokeAlert(){
AlertDialog.Builder builder = new AlertDialog.Builder(this, AlertDialog.THEME_HOLO_LIGHT);
builder.setTitle("Choose Countries");
builder.setMultiChoiceItems(R.array.Countries, selectedCountries , new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
if(isChecked == true){
selectedCountries[which] = isChecked;
}
Toast.makeText(context, countries[which], Toast.LENGTH_SHORT).show();
}
}).setCancelable(true);
builder.setPositiveButton("SUBMIT", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
submitBtnClicked = true;
final Toast displaySelectedCountry = Toast.makeText(context, "", Toast.LENGTH_SHORT);
for(int i=0;i<selectedCountries.length;i++){
if(selectedCountries[i]==true){
checkedCountries.add(countries[i]+"");
}
}
 for(CharSequence checkedCountry : checkedCountries){
 System.out.println(checkedCountry);  
 displaySelectedCountry.setText(checkedCountry);
 displaySelectedCountry.show();
 }
}
}).setCancelable(true);
submitBtnClicked = false;
builder.setInverseBackgroundForced(true);
alertDialog = builder.create();
alertDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
  if(submitBtnClicked)
    Arrays.fill(selectedCountries, false);
  else
isDialogCancelled = true;
}
});
alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
  isDialogCancelled = false;
}
});
alertDialog.show();
}
public void onSaveInstanceState(Bundle bundle){
super.onSaveInstanceState(bundle);
configChangeFlag = 1;
bundle.putInt("configChangeFlag", configChangeFlag);
bundle.putBooleanArray("checkedCountries", selectedCountries);
bundle.putBoolean("submitBtnClicked", submitBtnClicked);
bundle.putBoolean("isDialogCancelled", isDialogCancelled);
}

public void onPause(){
super.onPause();
if(alertDialog!=null)
  alertDialog.dismiss();
}
}

The first thing we need to handle is memory leak in alert dialog.
We can handle it by overriding onPause callback method of the activity life cycle. What we do is we check whether the alert dialog object is null or not and then we dismiss the alert dialog if the alert dialog object is not null. The code for doing this is :--

public void onPause(){
super.onPause();
if(alertDialog!=null)
   alertDialog.dismiss();
}

The 2nd thing we need to handle is configuration changes due to change in orientation. In this code we are displaying a list of countries with checkbox. Now if we have checked some countries 
and rotate the device and if we have not handled the orientation changes then the countries you have checked are lost. But not to worry , we can save the changes we have made in the onSaveInstanceState method and can retrieve it in the onCreate callback method.
public void onSaveInstanceState(Bundle bundle){
super.onSaveInstanceState(bundle);
configChangeFlag = 1;
bundle.putInt("configChangeFlag", configChangeFlag);
bundle.putBooleanArray("checkedCountries", selectedCountries);
bundle.putBoolean("submitBtnClicked", submitBtnClicked);
bundle.putBoolean("isDialogCancelled", isDialogCancelled);
}
In the above method we are saving the state of the alert dialog i.e the array of countries checked  and a variable that helps us to determine whether orientation change has occurred or not in a bundle and the other things that we are saving i will explain it later.  

We can retrieve the saved changes in onCreate method by

if(savedInstanceState != null){
configChangeFlag = savedInstanceState.getInt("configChangeFlag");
selectedCountries = savedInstanceState.getBooleanArray("checkedCountries");
submitBtnClicked = savedInstanceState.getBoolean("submitBtnClicked");
isDialogCancelled = savedInstanceState.getBoolean("isDialogCancelled");
} else {
selectedCountries = new boolean[countries.length];
}

Now if the submit button is clicked and the alert dialog is dismissed then during changes in orientation the alert dialog shouldn't be displayed. This is done by setting the variable submitBtnClicked to true in on onClick method of  builder.setPositiveButton and saving it's state in onSaveInstanceState. Now if the ALERT dialog is dismissed by clicking on submit button of alert dialog then the alert dialog will not be displayed during orientation changes. But if the alert dialog is dismissed by clicking the back button then also the alert dialog is not displayed during configuration changes but the countries that are checked are not lost, and if we click on ALERT button then the alert dialog is displayed with the previously checked countries.

builder.setPositiveButton("SUBMIT", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
submitBtnClicked = true;
final Toast displaySelectedCountry = Toast.makeText(context, "", Toast.LENGTH_SHORT);
for(int i=0;i<selectedCountries.length;i++){
if(selectedCountries[i]==true){
checkedCountries.add(countries[i]+"");
}
}
  for(CharSequence checkedCountry : checkedCountries){
  System.out.println(checkedCountry);  
  displaySelectedCountry.setText(checkedCountry);
  displaySelectedCountry.show();
  }
}
}).setCancelable(true);
submitBtnClicked = false;
builder.setInverseBackgroundForced(true);
alertDialog = builder.create();
alertDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
   if(submitBtnClicked)
     Arrays.fill(selectedCountries, false);
   else
isDialogCancelled = true;
}
});
alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
   isDialogCancelled = false;
}
});
alertDialog.show();
}
Finally the alert dialog is displayed during orientation changes only if the orientation changes have happened and alert dialog is not dismissed due to the clicking of the submit button of alert dialog and finally if alert dialog is not dismissed due to the clicking of the back button.  These conditions are checked by using the following code:--

if(configChangeFlag==1 && submitBtnClicked == false && isDialogCancelled == false){
invokeAlert();
  }

Output:--

In Portrait Mode:--

In landscape mode



So this is all about how to handle the orientation changes and dismissal of alert dialog without loosing data.

Happy Coding :)

No comments:

Post a Comment