https://kotlinlang.org logo
#compose
Title
# compose
m

Mark

01/19/2023, 12:55 PM
Migrating
AlertDialogBuilder
to compose. I have a multi-module application which I would like to migrate slowly to Compose. In the existing code, one module has a function that takes a
Context
and shows a dialog. This function is called from another module (not app module). I can declare the
AlertDialog
composable, but am lost (sorry, I’m a newbie in Compose) as to how to call this from non-composable code. The only examples I see are using
setContent { … }
(or declaring
ComposeView
in existing content XML) neither of which are really applicable here. Any pointers please?
r

robercoding

01/19/2023, 3:09 PM
Does the function that takes context do something else? Or is it only to trigger the
AlertDialog
? Some code examples would be nice to visualize what you're trying to do Note: I haven't migrated from XML to Compose, so I'm also not an expert in this field, but might be able to help
m

Mark

01/20/2023, 1:30 AM
I have dialogs of differing complexity. Some just show a simple message (i.e.
Context
is enough), others require launching coroutines to grab a list of items to display in the dialog. But really, once I figure out how to show a simple dialog, the others should be easy.
Maybe I need to use
navigation-compose
?
I’ve tried a different approach which is to use a legacy
AlertDialogBuilder
(1.5.1) and pass a programmatically instantiated
ComposeView
to
setView
. However, this gives:
Copy code
java.lang.IllegalStateException: Composed into the View which doesn't propagateViewTreeSavedStateRegistryOwner!
    at androidx.compose.ui.platform.AndroidComposeView.onAttachedToWindow(AndroidComposeView.android.kt:1135)
After searching around, I found various suggestions like this:
Copy code
val decorView = dialog.window?.decorView ?: return
ViewTreeLifecycleOwner.set(decorView, context as AppCompatActivity)
ViewTreeViewModelStoreOwner.set(decorView, context)        decorView.setViewTreeSavedStateRegistryOwner(decorView.findViewTreeSavedStateRegistryOwner())
without success. I tried placing this just before and just after calling
AlertDialog.show
Any sample code out there for this?
r

robercoding

01/22/2023, 7:20 AM
I don't think you need to use
navigation-compose
for this, you should be able to use
AlertDialog
from Compose without having to pass context or anything Since I haven't worked with XML interoperability I have no idea 🤔
m

Mark

01/22/2023, 7:22 AM
Yes, this is what I’ve been trying to do, but with a
DialogFragment
but there seem to be so many bugs. For example, when displaying a TextField in the dialog, I need to switch to 1.4.0 alpha in order to get the keyboard to show at all. But even then, it’s below the dialog window and so key presses are not detected
t

Tepes Lucian Victor

01/27/2023, 6:05 PM
@Mark i’m also experiencing issues with a TextField inside a DialogFragment. Any luck for a workaround? Keyboard does not want to show up at all
m

Mark

01/28/2023, 1:53 AM
@Tepes Lucian Victor The keyboard started showing for me when I used the latest alpha release. But it’s no good because it’s shown behind the dialog window and so you can’t tap any of the keys!
t

Tepes Lucian Victor

02/01/2023, 7:42 AM
Copy code
/**
 * ComposeView (copied code since you can't extend from it) who implements DialogWindowProvider 
 * and returns a window from the attached dialog.
 */
class DialogComposeView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr), DialogWindowProvider  {

    constructor(dialog: Dialog): this(dialog.context) {
        this.dialog = dialog
    }

    private val content = mutableStateOf<(@Composable () -> Unit)?>(null)

    var dialog: Dialog? = null

    override val window: Window
        get() = dialog?.window ?: throw IllegalStateException("Not attached to a dialog or a window")

    @Suppress("RedundantVisibilityModifier")
    protected override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
        private set

    @Composable
    override fun Content() {
        content.value?.invoke()
    }

    override fun getAccessibilityClassName(): CharSequence {
        return javaClass.name
    }

    /**
     * Set the Jetpack Compose UI content for this view.
     * Initial composition will occur when the view becomes attached to a window or when
     * [createComposition] is called, whichever comes first.
     */
    fun setContent(content: @Composable () -> Unit) {
        shouldCreateCompositionOnAttachedToWindow = true
        this.content.value = content
        if (isAttachedToWindow) {
            createComposition()
        }
    }
}
@Mark I’ve found a workaround. Instead of
ComposeView
in the dialog fragment, use
DialogComposeView
^
make sure you pass the dialog from the fragment
m

Mark

02/01/2023, 10:33 AM
Thanks @Tepes Lucian Victor. I wasn’t quite sure what you meant by your last comment. Here’s what I tried, but the keyboard doesn’t show at all.
Copy code
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    val composeView = DialogComposeView(requireContext()).apply {
        setContent {
            // my composable
        }
    }
    return MaterialAlertDialogBuilder(requireContext()).apply {
        setView(composeView)
    }.create().also {
        composeView.dialog = it
    }
}
t

Tepes Lucian Victor

02/01/2023, 10:41 AM
I’m using a regular
AppCompatDialogFragment
+
Copy code
override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val dialog = requireDialog() as ComponentDialog
        
        return DialogComposeView(dialog).apply {
            layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
            id = composeViewId
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)

            setContent {
                CompositionLocalProvider(
                    LocalOnBackPressedDispatcherOwner provides dialog,
                ) {
                    MyTheme {
                        FragmentContent()
                    }
                }
            }
        }
    }
I haven’t tried on
AlertDialog
or the material one.
@Mark I’ve tested your code and yeah it doesn’t work. However somebody pointed out the issue in another thread https://kotlinlang.slack.com/archives/CJLTWPH7S/p1675241101939389?thread_ts=1674847783.928059&amp;cid=CJLTWPH7S
I’ve tested his solution and it works. Something is up with
AlertDialog
and custom views
m

Mark

02/01/2023, 12:36 PM
Yes, it works for me too. Same as for you, I couldn’t get the legacy
AlertDialog
to work, but it seems fine when using a compose
AlertDialog
and removing the
onCreateDialog
implementation.
Unfortunately
AlertDialog
doesn’t play nicely with
TextField
, particularly when the text does not fit onto one line (line breaks or wrapping). So, it looks like I have to use
Dialog
but I find styling
Dialog
to be really hard
t

Tepes Lucian Victor

02/01/2023, 1:27 PM
anything dialog related sucks so i know the pain
m

Mark

02/01/2023, 2:16 PM
The other thing is that the soft keyboard is still showing (actually it hides and then reshows) after the dialog is dismissed. Any ideas how to stop that? Sorry, that was my mistake BTW, as a workaround to the multi-line
TextField
issue I just mentioned, I just set the
minLines
and
maxLines
to be the same value (in my case, 3)
169 Views