Subbu Lakshmanan

Alert Dialogs with Custom Views

Alert dialogs using MVP

We have been using AlertDialogs to alert the user as any android app does. Alert Dialogs are great and easy way to notify the user about an issue or action that needed to be perform. However there are several times, I find myself looking at lots of duplication of alert dialogs creation and management logic and inconsistent styles across various activities and fragments. To solve the issue, I started a small module using MVP design pattern following some good articles from the web.

On completion the Error Control Module looks like this,

|  
|--- ErrorHandler  
|--- ErrorPresenter   
|--- ErrorPresenterImpl  
|--- ErrorInteractor  
|--- ErrorInteractorImpl  
|--- ErrorCodes  
|--- ErrorResources  
|--- MaterialDialog  
|--- DialogView  
|--- DialogType  

ErrorHandler is a singleton that handles the management of creating Alert Dialogs. The MVP design pattern is implemented using the ErrorInteractor(Model), DialogView(View), ErrorPresenter(Presenter) interfaces. I am working on creating a android library project based on the MVP implementation and I will write another blog about it.

At the end, the code snippet to create Alert Dialog looks like this,

 errorHandlingDialog = errorHandler.createDialog(DialogType.ERROR);
 errorHandlingDialog.setErrorCode(errorCode);        
 errorHandlingDialog.showOkayButton(true, okBtnTxt);
 errorHandlingDialog.setOnConfirmListener(okBtnlistener);        
 errorHandlingDialog.showCancelButton(true, cancelBtnTxt);
 errorHandlingDialog.setOnCancelListener(cancelBtnlistener);
 errorHandlingDialog.show();

And the dialog was created as below,

 private void createDialog(DialogType dialogType) {
    AlertDialog.Builder builder = new AlertDialog.Builder(mContext, R.style.error_dialog_style);

    View mDialogView = LayoutInflater.from(mContext).inflate(R.layout.material_dialog, null, false);
    mDialogTitleView = (AppCompatTextView) mDialogView.findViewById(R.id.AlertDialogTitle);
    mDialogContentView = (AppCompatTextView) mDialogView.findViewById(R.id.AlertDialogContent);
    mDialogExtraContentView = (AppCompatTextView) mDialogView.findViewById(R.id.AlertDialogExtraContent);

    mDialogCancelBtn = (AppCompatButton) mDialogView.findViewById(R.id.AlertCancelButton);
    mDialogOkayBtn = (AppCompatButton) mDialogView.findViewById(R.id.AlertConfirmButton);

    builder.setView(mDialogView);
    
    switch (dialogType) {
        case ERROR:
            builder.setCancelable(false);
            break;
        case WARNING:
            builder.setCancelable(true);
            break;
        case INFO:
            builder.setCancelable(true);
            break;
    }
    mDialog = builder.create();
    mDialog.getWindow().setLayout(500, WindowManager.LayoutParams.WRAP_CONTENT);
}

The layout file looks like this,

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@color/dialog_bgcolor"
    android:orientation="vertical"
    android:padding="@dimen/_30dp">
     <android.support.v7.widget.AppCompatTextView
        android:id="@+id/AlertDialogTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <android.support.v7.widget.AppCompatTextView
        android:id="@+idAlertDialogContent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <android.support.v7.widget.AppCompatTextView
        android:id="@+id/AlertDialogExtraContent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <android.support.v7.widget.AppCompatButton
        android:id="@+id/AlertCancelButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/error_handling_cancel_text"/>

    <android.support.v7.widget.AppCompatButton
        android:id="@+id/AlertConfirmButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/error_handling_okay_text"/>

</RelativeLayout>

To the obvious eyes, the code snippets do make sense and appear to be working. We thought so and it was working. It passed the Unit testing. However when we started integrating the code to the app and used it to replace the duplicated 'alert dialog, we started noticing a strange crash occurring.

java.lang.ClassCastException: android.widget.LinearLayout$LayoutParams cannot be cast to com.android.internal.widget.ActionBarOverlayLayout$LayoutParams

The stack trace didn't point to any code in the app, rather to android views. We realized that we were messing up somewhere but it didn't occur to us to review the dialog creation code.

After goofing around the app code and trying to recreate the issue, one of the developer found out the crash didn't happen until, the dialog was shown. i.e., Until errorHandlingDialog.show() was called.

Debugging the code for further, we found out that the below line was causing the crash.

mDialog.getWindow().setLayout(500, WindowManager.LayoutParams.WRAP_CONTENT);

With some help from google, I found out that the issue was related to incorrect usage of layout parameters and positioning.

Links that were helpful:

  1. Layout Parameters for Alert Dialog
  2. Specifying Custom Dialog View Size

I updated the code as below,

mDialog = dialogBuilder.create();
Window window = mDialog.getWindow();
if (window != null) {
    window.setLayout(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
    window.setGravity(Gravity.CENTER);
}

and modified the layout_width and layout_height from match_parent to wrap_content

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

</RelativeLayout>

Now the code made more sense and I thought that I'm done. However I was presented with another surprising and weird(Since I didn't understand the code first) issue.

java.lang.IllegalStateException: ActionBarView can only be used with android:layout_width="match_parent" (or fill_parent)

And again stack trace didn't point to the app, but this time I was ready for it.

I realized the issue is somewhere in setting the width of the view. I wasn't using any style for alert dialogs earlier and I decided to make use of it.

Link that was helpful: Provide valid parent theme for Alert Dialogs

I created a style as below and applied to alert dialogs.

<style name="AppTheme.Dialog" parent="android:Theme.Light">
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowFrame">@null</item>
    <item name="android:windowIsFloating">true</item>
    <item name="android:windowContentOverlay">@null</item>
    <item name="android:maxWidth">500dp</item>
    <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
</style>

<style name="error_dialog_style" parent="@style/AppTheme.Dialog">

</style>

AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext, R.style.error_dialog_style);

And that finally fixed the Illegal State Exception crash. Finally our app now has beautiful and uniform alert dialogs with additional benefit of reduced duplication of code. Hurray!!

This post is also available on DEV.

All rights reserved