Tuesday, 13 August 2013

DialogFragment - A basic guide of usage and improvement

Update: I have detected a problem using the dialog fragment in this way with ActionBarSherlock. The problem has been solved by updating the android support v4 library of ActionBarSherlock.

From the last versiĆ³n of the API Demo of Android, we can find the follow code about the AlertDialog
ApiDemos(4.2.2) -> src -> com.example.android.apis.view -> ProgressBar3.java:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.progressbar_3);

        Button button = (Button) findViewById(R.id.showIndeterminate);
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                showDialog(DIALOG1_KEY);
            }
        });

Since the method showDialog(int) has been deprecated, an alternative must be used. It seems that the best way to use the the loading dialog is based on DialogFragment(Android activity).

Based on this code, I have modified the improve the example. You can find the source code in my GitHub account.

1. Layout
First of all, here is the basic layout for the DialogFragment:
It contains a progress bar and a textView which shows the "Loading..." text

<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/simple_progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
android:background="#000000"
>

    <ProgressBar
        android:id="@+id/progressBar1"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="10dp"
        />

    <RelativeLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_centerVertical="true"
    android:layout_toRightOf="@id/progressBar1"
    >
    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/loading"
        android:layout_centerHorizontal="true"
        android:textColor="#FFFFFF"
        />
   
    </RelativeLayout>
</RelativeLayout>

2. Java code
The first part of the java code is the class LoadingDialog (src-> com.jiahaoliuliu.android.dialogfragment -> LoadingDialog.java)

It overrides the method onCreateView to creates the view based on the layout. It also contains the code to remove the title. This is because I have set the background of the view as black, but no the background of the title (it could be white). Because we are not using the title, the easiest way is remove it.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInsntaceState) {
View view = inflater.inflate(R.layout.fragment_loading, container);

// Remove the title
getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
return view;
}

The second part of the java code is the class BaseActivity(src-> com.jiahaoliuliu.android.dialogfragment -> BaseActivity.java)

This class links a button in the basic layout, which by pressing it, the loading dialog will appears.

showDialogButton = (Button)findViewById(R.id.showDialog);
showDialogButton.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
loadingDialog.show(fm, "loadingFragmentDialog");
}
});

The loading dialog will dissapears if the user press on back button or if the activity has been resumed.
@Override
protected void onResume() {
super.onResume();
loadingDialog.dismiss();
}

3. Further improvent
Based on the source code, there are two method used to show the dialog:
- public void show(FragmentManager manager, String tag)
- public int show(FragmentTrasaction transaction, String tag)

Both of them adds the DialogFragment to the FragmentTransaction and commit it. The problem here is that "the commit does not happen immediately. It will be scheduled as work on the main thread to be done the next time that thread is ready."

This means even if we call the methods such as
- loadingDialog.getDialog()
- findFragmentByTag(String) of FragmentManager

both methods could return null until the commit happens. This could be a serious problem because if we commit two times the same DialogFragment, it will shows an exception, as well if we dismiss the same DialogFragment after it has been dismissed.

My solution to this problem is creating a boolean inside the class LoadingDialog, which save the state of the DialogFragment.

private boolean isDialogShownBool;

And then I override the both method of show and the method dismiss to include the states of the DialogFragment.

@Override
public int show(FragmentTransaction transaction, String tag) {
int result = -1;
if (!isDialogShownBool) {
result = super.show(transaction, tag);
isDialogShownBool = true;
}
return result;
}

@Override
public void show(FragmentManager manager, String tag) {
if (!isDialogShownBool) {
super.show(manager, tag);
isDialogShownBool = true;
}
}

@Override
public void dismiss() {
if (isDialogShownBool) {
super.dismiss();
}
}

Finally, there is a special situation which must be handler. This is if the dialog fragment is cancelable, when the user cancel it (press on the back button), it will dissapear and we must update the status of the dialog fragment.

@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
isDialogShownBool = false;
}

Source code:
GitHub - SimpleDialogFragment

Source:
StackOverFlow - showDialog deprecated. What's the alternative?
Developers - Activity
Developers - DialogFragment
Android developers Blog - Using DialogFragments
GrepCode - Android.app.DialogFragment

2 comments:

  1. Nice work solving the double transaction issue - I suspected the same thing and your blog just confirmed it (and gave me the workaround!)

    ReplyDelete
    Replies
    1. Hi:

      To be honest, there is some problem with the lifecycle. For example, when the app is in the foreground and the mobile device receives a push message as Line do.

      When I have some free time, I will post the solution and update this post and the code.

      Delete