← KlugDialog

Documentation

KlugDialog documentation

com.klug.dialog.Dialog

Native alerts, input dialogs and progress dialogs on Android, iOS and macOS: setup, every option per platform, the full API and the callbacks.

AndroidiOSmacOS Version 1.2.1 Product page · Buy

Introduction

KlugDialog shows the operating system's own dialogs in AIR apps on Android, iOS and macOS: alerts with up to three buttons, dialogs with any number of buttons and text fields, and a progress dialog that your code updates and closes.

Each platform uses its native component: an AlertDialog on Android, a UIAlertController on iOS and an NSAlert sheet attached to the app window on macOS. Text fields stay visible above the on-screen keyboard, passwords are never auto-capitalised, and every dialog is asynchronous, so the AIR runtime never blocks while a dialog is open.

Extension IDcom.klug.dialog.Dialog
Current version1.2.1
PlatformsAndroid (arm, arm64, x86, x86_64), iOS (devices, arm64), macOS (Apple silicon and Intel). On every other platform, including Windows and the iOS Simulator, the extension loads, isSupported is false and all calls are safe no-ops.
ActionScript APIOne class: com.klug.dialog.Dialog
Deliverycom.klug.dialog.Dialog.ane (the extension), the ActionScript library (SWC) for code completion, and this documentation

Requirements

AIR SDK51.x (HARMAN)
AndroidAndroid 6 or later
iOSiOS 13 or later, real devices
macOSmacOS 11 or later
PermissionsNone on any platform
SigningNo entitlements needed; dialogs also work in unsigned or development-signed local builds

Add the extension

Self-contained: no core library ANE and no dependency ANEs are needed. One file, one extensionID.

Copy com.klug.dialog.Dialog.ane into your project's extension folder (the folder you pass to ADT with -extdir, or the one your IDE links as native extensions) and add the extension ID to the application descriptor:

app.xml
<extensions>
    <extensionID>com.klug.dialog.Dialog</extensionID>
</extensions>

That is all. KlugDialog needs no Android manifest additions, no iOS InfoAdditions or Entitlements and no macOS entitlements.

Getting started

Get the instance, check support, show an alert. The call returns at once; the chosen button arrives later in your callback.

Main.as
import com.klug.dialog.Dialog;

var dialog:Dialog = Dialog.getInstance();   // singleton: never call new Dialog() yourself

if ( dialog.isSupported )                   // Android, iOS and macOS
{
    dialog.show( "Saved", "Your settings were saved." );   // one "OK" button

    dialog.show( "Delete recording?", "This cannot be undone.", [ "Delete", "Keep" ],
        function( index:int ):void
        {
            if ( index == 0 ) deleteRecording();   // your app function
        } );
}
else
{
    showAppAlert( "Saved" );   // your app's own fallback, e.g. on Windows
}

Guides

Simple alerts with show()

show() is the quick way to inform or ask: a title, a message and up to three buttons (default: one "OK" button). The callback receives the position of the chosen button in your array, starting at 0. Buttons beyond the third are ignored.

AndroidNative AlertDialog. The dialog can also be closed with the back key or a tap outside; the callback then receives -1. The style parameter is not used.
iOSUIAlertController alert, all buttons in the default style. The style parameter is not used. In the rare case that the alert cannot be presented, the callback receives -1.
macOSNSAlert sheet attached to the app window, with the app icon. The first button is the default button (Return key). style selects the alert style.
Confirm.as
dialog.show( "Leave the call?", "The other side will be disconnected.",
    [ "Leave", "Stay" ],
    function( index:int ):void
    {
        if ( index == 0 ) leaveCall();   // your app function
        // 1 = Stay, -1 = closed without a button (Android back key / tap outside)
    },
    Dialog.STYLE_WARNING );          // macOS alert style; ignored on Android and iOS

macOS sheet styles

On macOS the last parameter of show() sets the NSAlert style of the sheet. All three show your app icon; the critical style adds the system's caution badge.

Dialog.STYLE_INFO (0)Informational sheet. The default.
Dialog.STYLE_WARNING (1)Warning sheet.
Dialog.STYLE_CRITICAL (2)Critical sheet, for actions that cannot be undone.

Sheets slide down from the title bar of the app's main window. If no window is visible yet, for example very early during startup, macOS shows a separate modal alert instead.

Dialogs with tagged buttons: present()

present() takes any number of buttons. Each button is an object {label:String, style:int, tag:int}. The callback receives the tag of the chosen button, so the result does not depend on the order in which a platform lays out the buttons. style is a hint for the platform:

styleAndroid · iOS · macOS
0 defaultAndroid: confirming button, right side · iOS: default action · macOS: normal button
1 positiveAndroid: confirming button, right side · iOS: default action · macOS: normal button
2 negativeAndroid: left side · iOS: default action · macOS: normal button
3 destructiveAndroid: red text, left side · iOS: destructive action (red) · macOS: normal button
4 cancelAndroid: left side · iOS: cancel action, placed by the system (only the first cancel button gets this style) · macOS: normal button
5 neutralAndroid: left side · iOS: default action · macOS: normal button

Layout on Android: up to two buttons with labels of at most 18 characters sit in one row, with styles 2 to 5 on the left and styles 0 and 1 on the right. With three or more buttons, or a longer label, the buttons are stacked full width in the order of your array; use this for action lists. macOS adds the buttons in array order, and the first one is the default button. iOS places the buttons according to the system's alert rules.

Choice.as
var buttons:Array = [
    { label:"Record video",  style:0, tag:1 },
    { label:"Take a photo",  style:0, tag:2 },
    { label:"Delete all",    style:3, tag:3 },   // destructive
    { label:"Cancel",        style:4, tag:0 }    // cancel
];

dialog.present( "Camera", "What would you like to do?", true, buttons, null,
    function( tag:int, values:Array ):void
    {
        switch ( tag )
        {
            case 1: startRecording(); break;   // your app functions
            case 2: takePhoto(); break;
            case 3: confirmDeleteAll(); break;
        }
    } );

Text input fields

Pass an array of field objects as the fifth parameter. The callback receives the entered texts in values, in the order of your fields, together with the tag of the button. Every field object may contain:

placeholder:StringHint text shown while the field is empty. All platforms.
secure:BooleanPassword field with hidden input. All platforms. Secure fields are never auto-capitalised or auto-corrected.
text:StringInitial text, e.g. the last used e-mail address. All platforms.
keyboardType:intKeyboard for the field (table below). Android and iOS.
autocorrect:BooleanSuggestions and auto-correction. Off unless set to true. Android and iOS.
autocap:intAuto-capitalisation: 0 none (default), 1 sentences, 2 words, 3 all characters. Android and iOS.
keyboardTypeiOS · Android
0Default keyboard · text
1ASCII-capable keyboard · text
2Numbers and punctuation · text
3URL keyboard · URL keyboard
4Number pad · numbers
5Phone pad · phone keyboard
6Name and phone pad · phone keyboard
7E-mail keyboard · e-mail keyboard
8Decimal pad · decimal numbers
9Twitter keyboard · text
10Web search keyboard · text

Android: dialogs with fields open near the top of the screen, the first field gets the focus and the keyboard opens; the message and the fields scroll while the buttons stay visible above the keyboard, also in landscape on tablets. The keyboard shows "Next" on each field and "Done" on the last one, and it is closed again when the dialog closes. A secure field with keyboard type 4 or 8 becomes a numeric PIN field. iOS: the system keeps the alert above the keyboard; secure fields are marked as password fields. macOS: the fields are stacked below the message and the first one gets the focus; keyboard type, auto-correction and auto-capitalisation do not apply.

SignIn.as
var buttons:Array = [
    { label:"Cancel",  style:4, tag:0 },
    { label:"Sign in", style:1, tag:1 }
];
var fields:Array = [
    { placeholder:"E-mail",   text:lastEmail, keyboardType:7 },   // lastEmail: your app's value
    { placeholder:"Password", secure:true }
];

dialog.present( "Sign in", "Please enter your account.", false, buttons, fields,
    function( tag:int, values:Array ):void
    {
        if ( tag != 1 ) return;
        var email:String = values[0];
        var password:String = values[1];
        if ( email == "" || password == "" ) showSignInAgain();   // your app
        else signIn( email, password );                         // your app
    } );

Cancelable dialogs

The third parameter of present(), cancelable, is used on Android: when true, the back key and a tap outside close the dialog without a button. Your callback is then called with tag -1, an empty array and cancelled = true, but only if it declares this third parameter. A callback with two parameters is simply not called in that case.

On iOS and macOS the user always closes a dialog with one of its buttons, so include a cancel button there. On iOS the same cancelled call also happens in the rare case that the dialog cannot be presented at all.

Rename.as
dialog.present( "Rename camera", null, true,
    [ { label:"Cancel", style:4, tag:0 }, { label:"Save", style:1, tag:1 } ],
    [ { placeholder:"Name", text:cameraName, autocap:2 } ],   // capitalise words
    function( tag:int, values:Array, cancelled:Boolean = false ):void
    {
        if ( cancelled || tag != 1 ) return;   // back key, tap outside or Cancel
        renameCamera( values[0] );               // your app function
    } );

Close a dialog from code

Pass your own id as the seventh parameter of present(). presentDismiss(id) then closes that dialog; its callback is not called. Use this when the reason for the dialog disappears, for example when the other side cancels a request while your question is still open. On Android and iOS, presenting a new dialog with the same id replaces the open one; use a new id for each dialog that is open at the same time.

Request.as
private const REQUEST_ID:String = "incomingRequest";

public function onIncomingRequest( from:String ):void
{
    dialog.present( "Video call", from + " would like to start a video call.", false,
        [ { label:"Decline", style:2, tag:0 }, { label:"Accept", style:1, tag:1 } ],
        null,
        function( tag:int, values:Array ):void
        {
            if ( tag == 1 ) acceptCall(); else declineCall();   // your app
        },
        REQUEST_ID );
}

public function onRequestWithdrawn():void
{
    dialog.presentDismiss( REQUEST_ID );   // closes it; no callback
}

Progress dialog

progressShow() opens a dialog with a spinner, a title and a message, and no buttons. You choose its id; with the same id you change the message (progressUpdate) and close it (progressDismiss). The optional closeCallback is called once, when the dialog is really gone. While a progress dialog is open, change its text with progressUpdate() rather than calling progressShow() again with the same id; to show a new one, dismiss the old one first or use a new id.

AndroidModal dialog with a spinner and the message beside it. With cancelable = true the back key closes it (a tap outside does not); closeCallback fires in both cases.
iOSA spinner box above the app, also above native video views. It does not block touches: taps reach your app below. Alerts and dialogs opened while a spinner is visible wait until it is gone (at most 60 seconds), so the user sees one at a time. cancelable is not used.
macOSA sheet on the app window with the app icon, spinner, title and message. cancelable is not used; your code closes it. Show it only when the app window is visible.
Connect.as
private const PROGRESS_ID:String = "connect";

public function connect():void
{
    dialog.progressShow( PROGRESS_ID, "Connecting", "Contacting the server…", true,
        function():void
        {
            // once: closed by progressDismiss() or, on Android, by the back key
            if ( !connected ) cancelConnect();   // your app
        } );
    startConnect();   // your app; calls onStep() and onConnected() / onFailed()
}

public function onStep( text:String ):void
{
    dialog.progressUpdate( PROGRESS_ID, text );   // e.g. "Signing in…"
}

public function onConnected():void
{
    connected = true;
    dialog.progressDismiss( PROGRESS_ID );
}

public function onFailed( reason:String ):void
{
    dialog.progressDismiss( PROGRESS_ID );
    dialog.show( "Connection failed", reason );   // on iOS shown after the spinner is gone
}

Light and dark theme

The last parameter of present() and progressShow() is theme: "light" or "dark".

Android"dark" uses the device's dark dialog theme; anything else, including the default "", uses the light theme.
iOS"dark" or "light" force that appearance; "" follows the system setting.
macOSNot used: dialogs always follow the system appearance.

show() has no theme parameter: it uses the light theme on Android and follows the system on iOS and macOS.

Theme.as
var theme:String = appUsesDarkMode() ? "dark" : "light";   // your app's setting

dialog.present( "Night mode", "Switch the camera to night mode?", true,
    [ { label:"No", style:2, tag:0 }, { label:"Yes", style:1, tag:1 } ], null,
    function( tag:int, values:Array ):void { if ( tag == 1 ) enableNightMode(); },
    null,      // no id needed
    theme );

dialog.progressShow( "wait", "", "Please wait…", false, null, theme );

App lifecycle and screen changes

Dialogs are native windows of your app. They stay open while the app is in the background, and the callback fires when the user returns and chooses a button. Because every call is asynchronous, keep the state your callback needs (for example in member variables) instead of relying on what was on screen when the dialog opened. If the situation changes while a dialog is open, close it with presentDismiss() or progressDismiss().

On Android the dialog keeps the app's immersive full-screen mode; dialogs without fields are centred, dialogs with fields sit at the top and adapt to the space above the keyboard when it opens or the device rotates. On iOS, alerts are centred on the screen, also on devices whose safe area is uneven, such as an iPhone in landscape.

Diagnostics on iOS

log(msg) writes a line prefixed with "KlugDialog:" to the iOS device log, which you can read in the Console app on a Mac while the device is connected. It is meant for diagnostics in development builds and does nothing on Android and macOS.

Debug.as
dialog.log( "login screen shown, user=" + userName );   // appears as "KlugDialog: login screen shown, …"

API reference

com.klug.dialog.Dialog

Extends flash.events.EventDispatcher. Singleton. All dialog methods are asynchronous and return at once.

getInstance()public static function getInstance():Dialog
Returns the single instance and creates it on the first call. Calling new Dialog() a second time throws an Error.
isSupportedpublic function get isSupported():Boolean
True on Android, iOS devices and macOS; false elsewhere (Windows, iOS Simulator, other platforms).
show()public function show(title:String, message:String, buttons:Array = null, callback:Function = null, style:int = 0):Boolean
Alert with up to three buttons. buttons: array of labels, default ["OK"]. callback(index:int): position of the chosen button, or -1 if closed without a button (Android back key or tap outside; iOS if the alert could not be presented). style: one of the STYLE constants, used on macOS. Returns true if the dialog was handed to the native side, false if unsupported.
present()public function present(title:String, message:String, cancelable:Boolean, buttons:Array, fields:Array, callback:Function, reqIdOverride:String = null, theme:String = ""):Boolean
Dialog with any number of buttons and text fields. buttons: array of {label:String, style:int, tag:int}; null or empty gives one "OK" button with tag 0. fields: array of {placeholder:String, secure:Boolean, text:String, keyboardType:int, autocorrect:Boolean, autocap:int}, or null. callback(tag:int, values:Array) or callback(tag:int, values:Array, cancelled:Boolean = false). cancelable: back key and tap outside close the dialog (Android). reqIdOverride: your id for presentDismiss(). theme: "light" or "dark" (Android, iOS). Returns true if handed to the native side, false if unsupported.
presentDismiss()public function presentDismiss(reqId:String):void
Closes the present() dialog shown with this id. Its callback is not called. No effect if no such dialog is open.
progressShow()public function progressShow(reqId:String, title:String, message:String, cancelable:Boolean, closeCallback:Function = null, theme:String = ""):Boolean
Progress dialog with a spinner and no buttons. closeCallback() is called once when it has closed. cancelable: back key closes it (Android). theme: "light" or "dark" (Android, iOS). Returns true if handed to the native side, false if unsupported.
progressUpdate()public function progressUpdate(reqId:String, message:String):void
Changes the message of the open progress dialog with this id. An empty message hides the line (Android, iOS).
progressDismiss()public function progressDismiss(reqId:String):void
Closes the progress dialog with this id; its closeCallback follows.
log()public function log(msg:String):void
Writes msg to the iOS device log. No effect on Android and macOS.
STYLE_INFOpublic static const STYLE_INFO:int = 0
Informational sheet (macOS).
STYLE_WARNINGpublic static const STYLE_WARNING:int = 1
Warning sheet (macOS).
STYLE_CRITICALpublic static const STYLE_CRITICAL:int = 2
Critical sheet (macOS).

Button object (present)

label:StringButton text. Default "OK".
style:int0 default, 1 positive, 2 negative, 3 destructive, 4 cancel, 5 neutral. See the guide for the effect per platform.
tag:intValue passed to the callback when this button is chosen.

Field object (present)

placeholder:StringHint text. All platforms.
secure:BooleanHidden input. All platforms.
text:StringInitial text. All platforms.
keyboardType:int0–10, see the keyboard table. Android and iOS.
autocorrect:BooleanDefault false. Android and iOS.
autocap:int0 none (default), 1 sentences, 2 words, 3 all. Android and iOS.

Events

KlugDialog does not dispatch public events. All results arrive through the callbacks you pass, asynchronously and on the ActionScript thread:

show() callbackcallback(index:int): position of the chosen button (0 = first), or -1 if closed without a button.
present() callbackcallback(tag:int, values:Array): tag of the chosen button and the field texts in field order (an empty array without fields).
present() cancelledcallback(-1, [], true): closed without a button. Only for callbacks that declare a third parameter.
progressShow() closeCallbackcloseCallback(): once, when the progress dialog has closed (by code, or by the back key on Android).

Each callback fires at most once. After presentDismiss() the callback of that dialog does not fire.

Troubleshooting

isSupported is false on iOS

The native part is built for iOS devices. In the iOS Simulator the fallback is used, so isSupported is false there. Test dialogs on a device, and keep a fallback for platforms without support.

My present() callback is not called when the user presses the back key

Cancellation is reported only to callbacks that declare a third parameter: function(tag:int, values:Array, cancelled:Boolean = false):void. With two parameters the callback is skipped for a cancel.

The button order differs between platforms

Each platform arranges buttons by its own rules. Use the tag in present() to identify the chosen button rather than its position. In show() the index always refers to your array, whatever the visual order.

The style parameter of show() has no effect on Android or iOS

That is intended: the alert styles exist on macOS. For a red destructive button on Android and iOS use present() with button style 3.

On iOS my alert appears only after a delay

A dialog waits while a progress spinner is visible, so the user does not see both at once. Close the spinner with progressDismiss() before you show the next dialog, or expect the dialog to appear when the spinner is gone (at most 60 seconds later).

Taps go through the progress spinner on iOS

By design: on iOS the spinner does not block your app. If the user must not interact during the operation, disable your own controls while it runs.

The progress sheet does not appear on macOS at startup

A sheet needs a visible app window. Show the progress dialog after your main window is visible.

The closeCallback of my progress dialog fires too early

Calling progressShow() a second time with an id that is still open can end the first dialog's life cycle and call the callback, notably on Android. Keep one progressShow() per id and change the text with progressUpdate(); after progressDismiss(), wait for the closeCallback before you reuse the id, or use a new id.

The values array contains empty strings

An empty field returns ""; the array always has one entry per field. Validate the input in your callback and show the dialog again if needed, for example with the entered text as the field's text.

The first letter of user names or e-mail addresses is capitalised

Auto-capitalisation is off unless you set autocap. If you see capitals, check that the field object does not set autocap or autocorrect, and use keyboardType:7 for e-mail addresses.

Changelog

1.2.1

  • iOS: alerts are centred on the screen also when the left and right safe areas differ
  • Android: the keyboard closes when a dialog with text fields closes, by button, cancel or presentDismiss()

1.2.0

  • iOS support: alerts, text fields and button lists as native UIAlertController, progress spinner above native video views
  • iOS: dialogs wait while a progress spinner is visible; log() for diagnostics
  • Android support: native dialogs with fields that stay above the keyboard, progress dialog, stacked button lists, dismiss from code, cancel callback for the back key and taps outside
  • present() and progressShow() take a light or dark theme; fields take keyboard type, auto-correction and auto-capitalisation

1.1.x

  • macOS: alert sheets show the app icon
  • macOS: progress sheet (progressShow, progressUpdate, progressDismiss) and closing present() dialogs from code with your own id
  • First release for macOS: native alert sheets with up to three buttons and three styles, present() with tagged buttons and text fields