Skip to content

ComposeDialogs#

Open in GitHub

Latest Release Last Update License

Stars Forks

This library offers you an easily extendible compose framework for modal dialogs and allows to show them as a dialog or a bottom sheet.

📷 Screenshots#

Info Dialog
Demo Demo Demo Demo
Input Dialog
Demo Demo
Number Dialog
Demo Demo Demo
Date Dialog
Demo Demo Demo
Time Dialog
Demo Demo
Color Dialog
Demo Demo Demo Demo
List Dialog
Demo Demo Demo Demo
Demo Demo Demo
Progress Dialog
Demo Demo

Features#

  • with this ibrary you get a very simple way to show dialogs
  • supports showing dialogs as dialogs or bottom sheets
  • easily extendible - creating a new dialog is just a few lines of code

All features are splitted into separate modules, just include the modules you want to use!

🔗 Dependencies#

Compose

Dependency Version Infos
Compose BOM 2024.04.00 Mapping
Material3 1.2.1

Library

Module Dependency Version
core -
dialog-info -
dialog-input -
dialog-number -
dialog-list -
dialog-progress -
dialog-time -
dialog-date -
dialog-color -
dialog-billing KotBilling 0.7

Setup Gradle#

This library is distributed via JitPack.io.

1/2: Add jitpack to your project's build.gradle
repositories {
    maven { url "https://jitpack.io" }
}
2/2: Add dependencies to your module's build.gradle
// use the latest version of the library
val composedialogs = "<LATEST-VERSION>" 

// include necessary modules

// core module
implementation("com.github.MFlisar.ComposeDialogs:core:$composedialogs")

// dialog module
implementation("com.github.MFlisar.ComposeDialogs:dialog-info:$composedialogs")
implementation("com.github.MFlisar.ComposeDialogs:dialog-input:$composedialogs")
implementation("com.github.MFlisar.ComposeDialogs:dialog-number:$composedialogs")
implementation("com.github.MFlisar.ComposeDialogs:dialog-list:$composedialogs")
implementation("com.github.MFlisar.ComposeDialogs:dialog-progress:$composedialogs")
implementation("com.github.MFlisar.ComposeDialogs:dialog-time:$composedialogs")
implementation("com.github.MFlisar.ComposeDialogs:dialog-date:$composedialogs")
implementation("com.github.MFlisar.ComposeDialogs:dialog-color:$composedialogs")
implementation("com.github.MFlisar.ComposeDialogs:dialog-billing:$composedialogs")

⌨ Usage#

// create and remember a state
val state = rememberDialogState()

// show a dialog if necessary
if (state.showing)
{
    DialogInfo(
        state = state,
        // Custom - Required
        info: String,
        // Custom - Optional
        infoLabel: String = "",
        // Base Dialog -  Optional - all options can be set up with custom attributes, following are just the default examples
        title: (@Composable () -> Unit)? = null,
        icon: (@Composable () -> Unit)? = null,
        style: DialogStyle = DialogDefaults.styleDialog(), // DialogDefaults.styleBottomSheet() => both have a few settings...
        buttons: DialogButtons = DialogDefaults.buttons(),
        options: Options = Options(),
        onEvent: (event: DialogEvent) -> Unit = {
            // optional event handler for all dialog events
        }
    )
}

// show the dialog inside a button press event or similar
Button(onClick = { state.show() }) {
    Text("Show Dialog")
}

Alternatively, if you want to use one dialog with many items (e.g. for list items) you can do following:

// create and remember a state with data (e.g. an Integer)
val state = rememberDialogState<Int>(data = null)

// show a dialog if necessary
if (state.showing)
{
    val data = state.requireData()
    DialogInfo(
        state = state,
        // Custom - Required
        info = "Data = $data"
    )
}

// a list that uses the dialog
val items = 1..100
LazyColumn {
    items.forEach {
        item(key = it) {
            Button(onClick = { state.show(it) }) {
                Text("Item $it")
            }
        }
    }
}

🧬 Demo#

A full demo is included inside the demo module, it shows nearly every usage with working examples.

Modules and Extensions#

Info Dialog
Preview Module
Preview Preview info

This shows a simple dialog with some informational text.

DialogInfo.kt
fun DialogInfo(
    state: DialogState,
    // Custom - Required
    info: String,
    // Custom - Optional
    infoLabel: String = "",
    // Base Dialog - Optional
    title: (@Composable () -> Unit)? = null,
    icon: (@Composable () -> Unit)? = null,
    style: DialogStyle = DialogDefaults.styleDialog(),
    buttons: DialogButtons = DialogDefaults.buttons(),
    options: Options = Options(),
    onEvent: (event: DialogEvent) -> Unit = {}
) {
Input Dialog
Preview Module
Preview Preview input

This shows a dialog with a InputField. All its parameters are exposed via the compose function as you can see below, which allows you to simply adjust the InputFields behaviour. Additinally you can attach a validator which ensures, that the dialog will only return a valid input and can't be closed otherwise.

DialogInput.kt
fun DialogInput(
    // Base Dialog - State
    state: DialogState,
    // Custom - Required
    input: MutableState<String>,
    inputLabel: String = "",
    // Custom - Optional
    inputPlaceholder: String = "",
    singleLine: Boolean = false,
    maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
    minLines: Int = 1,
    keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),
    enabled: Boolean = true,
    clearable: Boolean = true,
    prefix: String = "",
    suffix: String = "",
    textStyle: TextStyle = LocalTextStyle.current,
    validator: DialogInputValidator = rememberDialogInputValidator(),
    requestFocus: Boolean = false,
    selectionState: DialogInput.SelectionState = DialogInput.SelectionState.Default,
    onTextStateChanged: (valid: Boolean, text: String) -> Unit = { _, _ -> },
    // Base Dialog - Optional
    title: (@Composable () -> Unit)? = null,
    icon: (@Composable () -> Unit)? = null,
    style: DialogStyle = DialogDefaults.styleDialog(),
    buttons: DialogButtons = DialogDefaults.buttons(),
    options: Options = Options(),
    onEvent: (event: DialogEvent) -> Unit = {}
) {
Number Dialog
Preview Module
Preview Preview number

This shows a number picker dialog. You can always use the Input Dialog for numbers as well and change its options to accept numbers only and even attach an validator. But this one is meant for picking numbers with the help of one or two increase and decrease buttons.

DialogNumberPicker.kt
fun <T : Number> DialogNumberPicker(
    // Base Dialog - State
    state: DialogState,
    // Custom - Required
    value: MutableState<T>,
    setup: NumberPickerSetup<T>,
    iconDown: @Composable () -> Unit = {
        Icon(imageVector = Icons.Default.KeyboardArrowLeft, contentDescription = null)
    },
    iconUp: @Composable () -> Unit = {
        Icon(imageVector = Icons.Default.KeyboardArrowRight, contentDescription = null)
    },
    iconDown2: @Composable () -> Unit = {
        Icon(imageVector = Icons.Default.KeyboardDoubleArrowLeft, contentDescription = null)
    },
    iconUp2: @Composable () -> Unit = {
        Icon(imageVector = Icons.Default.KeyboardDoubleArrowRight, contentDescription = null)
    },
    formatter: (value: T) -> String = { it.toString() },
    // Custom - Optional
    textStyle: TextStyle = MaterialTheme.typography.bodyMedium,
    onValueStateChanged: (value: T) -> Unit = { },
    // Base Dialog - Optional
    title: (@Composable () -> Unit)? = null,
    icon: (@Composable () -> Unit)? = null,
    style: DialogStyle = DialogDefaults.styleDialog(),
    buttons: DialogButtons = DialogDefaults.buttons(),
    options: Options = Options(),
    onEvent: (event: DialogEvent) -> Unit = {}
) {
Date Dialog
Preview Module
Preview Preview date

This shows a date selector dialog. First day of week, labels, and style can be adjusted to your needs.

fun DialogDate(
    state: DialogState,
    // Custom - Required
    date: MutableState<LocalDate>,
    // Custom - Optional
    dateRange: DialogDate.Range = DialogDateDefaults.dateRange(),
    setup: DialogDate.Setup = DialogDateDefaults.setup(),
    // Base Dialog - Optional
    title: (@Composable () -> Unit)? = null,
    icon: (@Composable () -> Unit)? = null,
    style: DialogStyle = DialogDefaults.styleDialog(),
    buttons: DialogButtons = DialogDefaults.buttons(),
    options: Options = Options(),
    onEvent: (event: DialogEvent) -> Unit = {}
) {
Time Dialog
Preview Module
Preview time

This shows a time selector dialog. 24h mode is optional.

DialogTime.kt
fun DialogTime(
    state: DialogState,
    // Custom - Required
    time: MutableState<LocalTime>,
    // Custom - Optional
    setup: DialogTime.Setup = DialogTimeDefaults.setup(),
    // Base Dialog - Optional
    title: (@Composable () -> Unit)? = null,
    icon: (@Composable () -> Unit)? = null,
    style: DialogStyle = DialogDefaults.styleDialog(),
    buttons: DialogButtons = DialogDefaults.buttons(),
    options: Options = Options(),
    onEvent: (event: DialogEvent) -> Unit = {}
) {
Color Dialog
Preview Module
Preview Preview color

This shows a color selector dialog. A table with predefined material colors as well as a customisation page will be shown. Alpha support can be enabled optionally.

fun DialogColor(
    // Base Dialog - State
    state: DialogState,
    // Custom - Required
    color: MutableState<Color>,
    // Custom - Optional
    texts: DialogColor.Texts = DialogColorDefaults.texts(),
    alphaSupported: Boolean = true,
    shape: Shape = MaterialTheme.shapes.small,
    gridSize: Int = if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE) 6 else 4,
    labelStyle: DialogColor.LabelStyle = DialogColor.LabelStyle.Value,
    // Base Dialog - Optional
    title: (@Composable () -> Unit)? = null,
    icon: (@Composable () -> Unit)? = null,
    style: DialogStyle = DialogDefaults.styleDialog(),
    buttons: DialogButtons = DialogDefaults.buttons(),
    options: Options = Options(),
    onEvent: (event: DialogEvent) -> Unit = {}
) {
List Dialog
Preview Module
Preview Preview list
Preview Preview list

This shows a dialog with a list of items. Rendering, selection mode and more is adjustable.

Here you can create a dialog based on static list data like following:

DialogList.kt
fun <T> DialogList(
    state: DialogState,
    // Custom - Required
    items: List<T>,
    itemIdProvider: (item: T) -> Int,
    itemContents: DialogList.ItemContents<T>,
    selectionMode: DialogList.SelectionMode<T>,
    // Custom - Optional
    divider: Boolean = false,
    description: String = "",
    filter: DialogList.Filter<T>? = null,
    // Base Dialog - Optional
    title: (@Composable () -> Unit)? = null,
    icon: (@Composable () -> Unit)? = null,
    style: DialogStyle = DialogDefaults.styleDialog(),
    buttons: DialogButtons = DialogDefaults.buttons(),
    options: Options = Options(),
    onEvent: (event: DialogEvent) -> Unit = {}
) {

But you can also create list with an asynchronous loader function like following:

DialogList.kt
fun <T> DialogList(
    state: DialogState,
    // Custom - Required
    itemsLoader: suspend () -> List<T>,
    itemIdProvider: (item: T) -> Int,
    itemContents: DialogList.ItemContents<T>,
    selectionMode: DialogList.SelectionMode<T>,
    // Custom - Optional
    itemSaver: Saver<MutableState<List<T>>, out Any>? = null,
    loadingIndicator: @Composable () -> Unit = {
        Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
            CircularProgressIndicator()
        }
    },
    divider: Boolean = false,
    description: String = "",
    filter: DialogList.Filter<T>? = null,
    // Base Dialog - Optional
    title: (@Composable () -> Unit)? = null,
    icon: (@Composable () -> Unit)? = null,
    style: DialogStyle = DialogDefaults.styleDialog(),
    buttons: DialogButtons = DialogDefaults.buttons(),
    options: Options = Options(),
    onEvent: (event: DialogEvent) -> Unit = {}
) {
Progress Dialog
Preview Module
Preview Preview progress

This shows a simple loading dialog with a progress indicator.

DialogProgress.kt
fun DialogProgress(
    state: DialogState,
    // Custom - Required
    // ...
    // Custom - Optional
    label: String = "",
    progressStyle: DialogProgress.Style = DialogProgress.Style.Indeterminate(),
    // Base Dialog - Optional
    title: (@Composable () -> Unit)? = null,
    icon: (@Composable () -> Unit)? = null,
    style: DialogStyle = DialogDefaults.styleDialog(),
    buttons: DialogButtons = DialogDefaults.buttons(),
    options: Options = Options(),
    onEvent: (event: DialogEvent) -> Unit = {}
) {
Billing Dialog

This shows a dialog with the prices and names of your products. It also shows if a product is already owned and allows to buy unowned prodcuts by clicking them.

DialogBilling.kt
fun DialogBilling(
    state: DialogState,
    // custom settings
    products: List<DialogBilling.BillingProduct>,
    texts: DialogBilling.Texts = DialogBillingDefaults.texts(),
    // Base Dialog - Optional
    title: (@Composable () -> Unit)? = null,
    icon: (@Composable () -> Unit)? = null,
    style: DialogStyle = DialogDefaults.styleDialog(),
    //buttons: DialogButtons = DialogDefaults.buttons(),
    //options: Options = Options(),
    onEvent: (event: DialogEvent) -> Unit = {}
) {
Custom Dialog
Dialog.kt
fun Dialog(
    state: DialogState,
    title: (@Composable () -> Unit)? = null,
    icon: (@Composable () -> Unit)? = null,
    style: DialogStyle = DialogDefaults.styleDialog(),
    buttons: DialogButtons = DialogDefaults.buttons(),
    options: Options = Options(),
    specialOptions: SpecialOptions = SpecialOptions(),
    onEvent: (event: DialogEvent) -> Unit = {},
    content: @Composable ColumnScope.() -> Unit
) {
Dialog(state, title, icon, style, buttons, options, onEvent = onEvent) {
    Text("Text in custom dialog")
    // ...
}

Advanced Usage#

Check out the dialog state and the dialogs to find out what settings you can use and especially the demo app for a working example.

Dialog State (simple with a boolean flag or complex with a data object)

In case of the simple state true means that the dialog is visible and false that it's not. In case of the complex state holding an object means the dialog is visible and null means it's not visible.

fun rememberDialogState(
    showing: Boolean = false,
    buttonPositiveEnabled: Boolean = true,
    buttonNegativeEnabled: Boolean = true,
    dismissAllowed: Boolean = true,
    swipeAllowed: Boolean = true
): DialogState {

In case of the complex state simply use state.show(data) to show the dialog and then inside your dialog call val data = state.requireData() to get the data from the state.

CAUTION: the state must be saveable by Bundle, if it is not, provide a custom saver!

fun <T : Any> rememberDialogState(
    data: T?,
    saver: Saver<MutableState<T?>, out Any> = autoSaver(),
    buttonPositiveEnabled: Boolean = true,
    buttonNegativeEnabled: Boolean = true,
    dismissAllowed: Boolean = true,
    swipeAllowed: Boolean = true
): DialogStateWithData<T> {