Skip to content

Commit

Permalink
Provide fallback for new AlertDialog
Browse files Browse the repository at this point in the history
Summary:
In D57019423, the `AlertFragment` started using the `AlertDialog` from androidx. This could potentially trigger an `IllegalStateException` if an alert is displayed from an activity that does not have theming from AppCompat.

This diff provides a fallback to the original `android.app.AlertDialog` if the current activity is not an AppCompat descendant. In addition, it modifies the test case to ensure that the alert dialog still displays and can be dismissed

Differential Revision: D57113950
  • Loading branch information
Abbondanzo authored and facebook-github-bot committed May 8, 2024
1 parent 5e3652a commit bd26c31
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
Expand Down Expand Up @@ -40,6 +41,30 @@ public AlertFragment(@Nullable DialogModule.AlertFragmentListener listener, Bund

public static Dialog createDialog(
Context activityContext, Bundle arguments, DialogInterface.OnClickListener fragment) {
if (isAppCompatTheme(activityContext)) {
return createAppCompatDialog(activityContext, arguments, fragment);
} else {
return createAppDialog(activityContext, arguments, fragment);
}
}

/**
* Checks if the current activity is a descendant of an AppCompat theme.
*
* @returns true if the current activity is a descendant of an AppCompat theme.
*/
private static boolean isAppCompatTheme(Context activityContext) {
TypedArray attributes =
activityContext.obtainStyledAttributes(androidx.appcompat.R.styleable.AppCompatTheme);
return attributes.hasValue(androidx.appcompat.R.styleable.AppCompatTheme_windowActionBar);
}

/**
* Creates a dialog compatible only with AppCompat activities. This function should be kept in
* sync with {@link createAppDialog}.
*/
private static Dialog createAppCompatDialog(
Context activityContext, Bundle arguments, DialogInterface.OnClickListener fragment) {
AlertDialog.Builder builder =
new AlertDialog.Builder(activityContext).setTitle(arguments.getString(ARG_TITLE));

Expand All @@ -64,6 +89,39 @@ public static Dialog createDialog(
return builder.create();
}

/**
* Creates a dialog compatible with non-AppCompat activities. This function should be kept in sync
* with {@link createAppCompatDialog}.
*
* @deprecated non-AppCompat dialogs are deprecated and will be removed in a future version.
*/
private static Dialog createAppDialog(
Context activityContext, Bundle arguments, DialogInterface.OnClickListener fragment) {
android.app.AlertDialog.Builder builder =
new android.app.AlertDialog.Builder(activityContext)
.setTitle(arguments.getString(ARG_TITLE));

if (arguments.containsKey(ARG_BUTTON_POSITIVE)) {
builder.setPositiveButton(arguments.getString(ARG_BUTTON_POSITIVE), fragment);
}
if (arguments.containsKey(ARG_BUTTON_NEGATIVE)) {
builder.setNegativeButton(arguments.getString(ARG_BUTTON_NEGATIVE), fragment);
}
if (arguments.containsKey(ARG_BUTTON_NEUTRAL)) {
builder.setNeutralButton(arguments.getString(ARG_BUTTON_NEUTRAL), fragment);
}
// if both message and items are set, Android will only show the message
// and ignore the items argument entirely
if (arguments.containsKey(ARG_MESSAGE)) {
builder.setMessage(arguments.getString(ARG_MESSAGE));
}
if (arguments.containsKey(ARG_ITEMS)) {
builder.setItems(arguments.getCharSequenceArray(ARG_ITEMS), fragment);
}

return builder.create();
}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return createDialog(getActivity(), getArguments(), this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,35 +47,14 @@ class DialogModuleTest {

@Before
fun setUp() {
activityController = Robolectric.buildActivity(FragmentActivity::class.java)
activity = activityController.create().start().resume().get()
// We must set the theme to a descendant of AppCompat for the AlertDialog to show without
// raising an exception
activity.setTheme(APP_COMPAT_THEME)

val context: ReactApplicationContext = mock(ReactApplicationContext::class.java)
whenever(context.hasActiveReactInstance()).thenReturn(true)
whenever(context.currentActivity).thenReturn(activity)

dialogModule = DialogModule(context)
dialogModule.onHostResume()
setupActivity()
}

@After
fun tearDown() {
activityController.pause().stop().destroy()
}

@Test
fun testIllegalActivityTheme() {
val options = JavaOnlyMap()
activity.setTheme(NON_APP_COMPAT_THEME)

assertThrows(NullPointerException::class.java) { dialogModule.showAlert(options, null, null) }

activity.setTheme(APP_COMPAT_THEME)
}

@Test
fun testAllOptions() {
val options =
Expand Down Expand Up @@ -168,6 +147,39 @@ class DialogModuleTest {
assertEquals(DialogModule.ACTION_DISMISSED, actionCallback.args!![0])
}

@Test
fun testNonAppCompatActivityTheme() {
setupActivity(NON_APP_COMPAT_THEME)

val options = JavaOnlyMap()

val actionCallback = SimpleCallback()
dialogModule.showAlert(options, null, actionCallback)
shadowOf(getMainLooper()).idle()

getFragment()!!.dialog!!.dismiss()
shadowOf(getMainLooper()).idle()

assertEquals(1, actionCallback.calls)
assertEquals(DialogModule.ACTION_DISMISSED, actionCallback.args!![0])
}

private fun setupActivity(theme: Int = APP_COMPAT_THEME) {
activityController = Robolectric.buildActivity(FragmentActivity::class.java)
activity = activityController.create().start().resume().get()

// We must set the theme to a descendant of AppCompat for the AlertDialog to show without
// raising an exception
activity.setTheme(theme)

val context: ReactApplicationContext = mock(ReactApplicationContext::class.java)
whenever(context.hasActiveReactInstance()).thenReturn(true)
whenever(context.currentActivity).thenReturn(activity)

dialogModule = DialogModule(context)
dialogModule.onHostResume()
}

private fun getFragment(): AlertFragment? {
return activity.supportFragmentManager.findFragmentByTag(DialogModule.FRAGMENT_TAG)
as? AlertFragment
Expand Down

0 comments on commit bd26c31

Please sign in to comment.