Android guide

Show a TaleArt zone in an Android app with the Android SDK: a view for XML layouts, or a composable for Jetpack Compose. You need the zone key from the dashboard, the same key the Web SDK uses. The Android SDK sends analytics events too (see Analytics below), so the zone's Analytics tab counts the app along with the web.

Before you start

  • Android 7.0 (API 24) or later, and Kotlin. The SDK brings its own Compose UI; a Compose app needs nothing more.
  • For the XML view: a host with a lifecycle, such as a ComponentActivity, an AppCompatActivity or a Fragment. A plain Activity shows nothing and logs a warning.
  • A zone with a display, and its key (tak_…), from the zone's Install tab in the dashboard.

Steps

  1. Add the dependency

    Add android-sdk to your app module. It comes from Maven Central, group site.lempert.taleart, and brings mobile-sdk (the shared core) with it. Gradle Kotlin DSL:

    dependencies {
        implementation("site.lempert.taleart:android-sdk:0.1.1")
    }

    Groovy (build.gradle):

    dependencies {
        implementation 'site.lempert.taleart:android-sdk:0.1.1'
    }

    Your app also needs the internet permission, which the SDK does not add for you. In AndroidManifest.xml:

    <uses-permission android:name="android.permission.INTERNET" />
    Diagram: your app module depends on android-sdk 0.1.1, which pulls in mobile-sdk 0.1.1. Both come from Maven Central.
    Diagram of the dependency graph (not a screenshot).
  2. Get the zone key

    On the zone's Install tab in the dashboard, copy the key (tak_…). The key is shown once, when it is issued. Put it in your code as a value, never in a URL. It reads that one zone, so treat what the zone shows as public. Rotating the key issues a new one and stops the old one at once.

    Diagram of the dashboard's Install tab for a zone: the zone key starts with tak_, is shown once, and has a Copy button.
    Diagram of the Install tab (not a screenshot).
  3. Place the zone: XML or Compose

    In XML, add a TaleartView with the key in app:taleartZoneKey. Its width fills the parent and its height is wrap_content: the bar or banner sets the height.

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <site.lempert.taleart.sdk.android.TaleartView
            android:id="@+id/taleart_top"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:taleartZoneKey="tak_YOUR_ZONE_KEY" />
    
    </LinearLayout>

    In Compose, call TaleartZone. It takes the API origin and the key first; an empty API origin uses the one set in step 4.

    import androidx.compose.foundation.layout.Column
    import androidx.compose.runtime.Composable
    import androidx.compose.ui.Modifier
    import androidx.compose.foundation.layout.fillMaxWidth
    import site.lempert.taleart.sdk.ui.TaleartZone
    
    @Composable
    fun HomeScreen() {
        Column {
            TaleartZone(
                apiUrl = "",                       // empty: the API URL set in TaleartSdk.init
                zoneKey = "tak_YOUR_ZONE_KEY",
                modifier = Modifier.fillMaxWidth(),
            )
        }
    }
    Screenshot of the TaleArt sample app on Android showing only the zone: a bar of round covers in gradient rings with the group names Summer, New arrivals and Winter under them, and a fourth cover cut off at the screen edge, on a plain light page.
    The bar in the TaleArt sample app (taller) on Android, a real screenshot taken on an emulator. The sample app shows only the zone here (its taller.zoneOnly debug option), so the picture holds nothing but the SDK's own output. The row scrolls sideways, so the fourth cover is cut off at the screen edge.
  4. Initialise once in Application

    Set the SDK's configuration once, in Application.onCreate, before any view shows. Every view reads it; a later TaleartSdk.init replaces it, and the views read it again. Set apiUrl: a native app has no page to take the origin from, and with an empty one a zone shows nothing. Register the class in the manifest:

    import android.app.Application
    import site.lempert.taleart.sdk.TaleartConfig
    import site.lempert.taleart.sdk.TaleartSdk
    
    class TaleartApp : Application() {
        override fun onCreate() {
            super.onCreate()
            TaleartSdk.init(
                TaleartConfig(
                    apiUrl = "https://api.example.com",
                ),
            )
        }
    }
    <application
        android:name=".TaleartApp"
        ... >
    Diagram: Application.onCreate calls TaleartSdk.init once. The configuration holds apiUrl, locale, strings and listener, and every zone reads it.
    Diagram of the start-up order (not a screenshot).
  5. Pass viewer properties

    Pass what the app knows about the viewer as properties: a map from a name to a String, a number, a Boolean or a list of Strings. A tale group can carry an audience rule, set in the dashboard under its targeting, and the SDK shows the group only when the rule passes these values. The rule can test a value with eq, in, gt, contains, exists and the other operators listed on the Targeting page.

    TaleartSdk.init(
        TaleartConfig(
            apiUrl = "https://api.example.com",
            properties = mapOf(
                "plan" to "pro",                            // a String
                "age" to 34,                                // a number
                "newsletter" to true,                       // a Boolean
                "segment" to listOf("beta", "newsletter"),  // a list of Strings
            ),
        ),
    )

    A name starts with a letter and uses letters, digits, _, . or -, up to 64 characters, and is case-sensitive. A map has at most 50 properties, a string at most 256 characters and a list at most 50 items. An invalid entry is ignored. Dates go as epoch milliseconds.

    Leave the properties out (null, the default) when the app passes none: every group's audience rule then passes, as it does with no tags. An empty map means the app knows nothing about the viewer: a group whose rule needs a value is then left out, while the negative conditions (neq, notIn, notExists) pass.

    A group shows only when its tags and its audience rule both pass. The properties stay on the device: the SDK sends them in no request header, no URL and no analytics event. The rule itself is public, as the tag names are, so keep private content out of rule values.

    A view can use its own properties in code (there is no XML attribute for them). They replace the SDK's for that view only:

    // One view only: its properties replace the SDK's, and its zone is read again at once.
    view.properties = mapOf("plan" to "free")   // Java: view.setProperties(map)
    
    // Back to the SDK's properties:
    view.properties = null

    A later TaleartSdk.init applies its properties on the next read of a zone: a zone already on screen keeps the groups it shows until it reads again, as it does for tags. Setting a view's properties reads that view's zone at once. The Compose TaleartZone has no properties of its own (see Not available natively yet).

  6. Listen to hooks and override a link

    A listener gets the zone's events: onLoaded, onLoadFailed, onPresented, onActionClicked and the rest. Set it in TaleartConfig.listener for every view, or on one view with its listener; the view's runs first. Each method has a default that does nothing, so override only what you use.

    onActionClicked is the one hook with an answer. Return true when your app routed the link, and the SDK does not open the URL. Return false and the system browser opens it.

    import site.lempert.taleart.sdk.TaleartEvent
    import site.lempert.taleart.sdk.TaleartListener
    
    class AppHooks(private val router: AppRouter) : TaleartListener {
        override fun onLoaded(event: TaleartEvent) {
            // The zone with the key prefix event.zoneKeyPrefix is on screen.
        }
    
        override fun onActionClicked(event: TaleartEvent): Boolean {
            val url = event.url ?: return false
            if (!url.startsWith("https://shop.example.com/")) return false
            router.open(url)   // your own navigation
            return true        // handled: the SDK does not open the browser
        }
    }
    
    // In Application.onCreate, next to apiUrl:
    //     TaleartConfig(apiUrl = "https://api.example.com", listener = AppHooks(router))

    The methods, their payloads and the events are in the Android SDK reference. onGroupImpression fires on native too, once per group per load. onComponentEvent reports a countdown's end and a promo code's copy. onUserInteracted reports each answer the server accepts to a poll, a quiz, a rating or a reaction.

    Diagram: a zone event goes to the view's listener, then to the global listener. If onActionClicked returns true the app routes the link; if it returns false the system browser opens it.
    Diagram of the hook order and the link decision (not a screenshot).
  7. Localise the texts

    The SDK's texts (the viewer's buttons, the banner's controls, the names it announces) are English by default. Set locale (BCP 47, such as he-IL) and the texts per locale. A text is looked up under the full locale, then its language, then stringsForEveryLocale, then English. A missing key shows English, never the key. With no locale set, the device's locale is used.

    TaleartSdk.init(
        TaleartConfig(
            apiUrl = "https://api.example.com",
            locale = "he-IL",
            strings = mapOf(
                "he" to mapOf(
                    "viewer.close" to "סגור",
                    "bar.seen" to "{name}, נצפה",
                ),
            ),
        ),
    )
    
    // One view only:
    // TaleartZone(apiUrl = "", zoneKey = "tak_…", locale = "he-IL", strings = mapOf("he" to mapOf("viewer.close" to "סגור")))

    The key names are in the Android SDK reference. Each string key has its English default there.

    Screenshot of the TaleArt sample app's viewer on Android with the Hebrew locale and right-to-left layout, open on a Hebrew group: a pale sun over a sea gradient, the group name in a chip on the right, the close and pause buttons on the left, and two progress bars.
    A Hebrew group in the sample app's viewer (taller), a real screenshot taken on an emulator. The group name and the controls sit by right-to-left rules. The viewer's buttons are icons in this shot, so it does not show their localised labels.
  8. Verify

    Run the app on a device or an emulator, then check, in order:

    • The bar (or banner) appears where you placed the view.
    • A tap on a circle opens the full-screen viewer, and Close (in your locale) returns to the bar.
    • Logcat, filtered on TaleArt, shows the warnings the SDK logs. A wrong key shows no zone and logs one failure; onLoadFailed gets the message.

    When the API runs on your own computer, newer Android versions can ask once to allow access to nearby devices or the local network; allow it, then reload.

    Screenshot of the TaleArt sample app's viewer on Android, open on the group Summer: a pale sun over a gradient of sand and sea, the group name in a dark chip at the top left, the pause and close buttons at the top right and two progress bars along the top.
    A tale open in the viewer, a real screenshot taken on an emulator. The viewer is running (the Pause button shows, not Play), and the first of the group's two progress bars is filling.
    Screenshot of the TaleArt sample app's debug settings screen on Android, not an SDK screen: an API URL field, a zone key field that shows only its first eight characters, tak_demo, followed by dots, and Load and Cancel buttons.
    The settings screen of the TaleArt sample app (taller), not an SDK screen. It is where the sample app takes its API URL and zone key. The key is masked after its first eight characters in this picture.

Show a message when a zone fails

By default a view that cannot read its zone shows nothing. Set fallback = FallbackMode.Message in TaleartConfig to show the translated "Stories could not be loaded." text with a "Try again" button, which reads the zone again. A view can show its own placeholder instead with fallbackView, and that wins over the message.

TaleartSdk.init(
    TaleartConfig(
        apiUrl = "https://api.example.com",
        fallback = FallbackMode.Message,    // the message and a retry button, for every view
    ),
)

// One view only: your own placeholder, which wins over the message.
view.fallbackView = myPlaceholder           // Java: view.setFallbackView(myPlaceholder)
Screenshot of the TaleArt sample app on Android when its zone cannot be read: on a plain page, the centred message 'Stories could not be loaded.' with a Try again button under it.
The fallback message in the sample app (taller) on Android, a real screenshot taken on an emulator. The sample app asks for a key the stand-in API refuses, and shows only the zone (its taller.zoneOnly debug option), so the message and the button are all there is.

Cap how often a viewer sees a group

frequency hides a tale group after the viewer has seen enough of it. A view is one opening of the viewer on the group. The count stays on this device and is never sent to TaleArt. Groups over the cap leave the display, and when none are left nothing shows. The default is no cap.

TaleartSdk.init(
    TaleartConfig(
        apiUrl = "https://api.example.com",
        frequency = FrequencyRule.Once,     // hide a group after the viewer has opened it once
        // frequency = FrequencyRule.Daily,              // at most one view per 24 hours
        // frequency = FrequencyRule.Weekly,             // at most one view per 7 days
        // frequency = FrequencyRule.Count(views = 3),   // hide a group after 3 views (1 to 50)
    ),
)
Screenshot of the TaleArt sample app on Android with a frequency cap of once, before any view: the zone bar shows three round covers in gradient rings, labelled Morning, Afternoon and Evening.
The zone bar before the viewer has opened a group, with the cap set to once (taller.frequency in the sample app), a real screenshot taken on an emulator.
Screenshot of the same TaleArt sample app on Android after the group Morning was viewed once and the app opened again: the zone bar shows only two covers, Afternoon and Evening, and the first group is gone.
The same zone after the viewer had opened the first group, Morning, once: the group has left the bar and the other two stay (a real screenshot taken on an emulator).

Bar layouts

A bar zone draws its groups in one of four layouts, set by the bar's layout setting in the dashboard. Ring is the default. The other bar settings (shape, size, titles, alignment and gap) apply where the table says so, and the rest are ignored. While the zone is read, the bar shows a loading skeleton in the footprint it had last, so the page does not move when the groups arrive. The skeleton is announced as busy, and it stands still under reduced motion.

The bar layouts and the settings each one ignores
LayoutUseIgnored settings
ringThe default. Each cover sits in a gradient ring with its title under it; the ring turns while a group is unseen and stays grey once it is seen.None.
chipA pill per group: a round cover, its title, and a dot while the group is unseen (the seen pill is muted and has no dot). The pills wrap onto more rows.shape. The size sets the cover (28, 32 or 40 dp).
posterA portrait card per group: the cover fills it, with the title and the tale count under a dark scrim, and a segment for each tale along the top.shape and size. The card is 112 by 188 dp and has a fixed corner radius.
stackThe groups overlap in one collapsed pill: up to four round covers and a +N bubble for the rest, with the count of unseen groups beside them. The pill opens the first unseen group.shape, showTitles, alignment and gap. The size sets the covers (40, 48 or 56 dp).
Screenshot of the TaleArt sample app on Android with the bar in the ring layout: round covers in gradient rings with the group names Summer, New arrivals and Winter under them, and a fourth cover cut off at the edge.
The ring layout, the default. The sample app (taller) sets the layout with its taller.barLayout debug option and shows only the zone; a real screenshot taken on an emulator. Its zone has six groups, and the row scrolls sideways.
Screenshot of the TaleArt sample app on Android with the bar in the chip layout: six pills, each with a round cover, the group name (Summer, New arrivals, Winter, Trips, Food, Music) and a small ring for a group not yet seen, two pills to a row on this narrow screen.
The chip layout, a real screenshot taken on an emulator. On a narrow screen the pills wrap onto several rows.
Screenshot of the TaleArt sample app on Android with the bar in the poster layout: tall cards, each with its group's picture, a white title and a line such as '2 stories' over a dark scrim, a bar for each tale along the top, and the third card cut off at the edge because the row scrolls sideways.
The poster layout, a real screenshot taken on an emulator. Each group's card shows a bar for each of its tales.
Screenshot of the TaleArt sample app on Android with the bar in the stack layout: one rounded pill holding four overlapping round covers and a +2 bubble for the other groups, with the line '6 new stories' beside them.
The stack layout, a real screenshot taken on an emulator. The zone has six groups, so two of them are in the +2 bubble.
Screenshot of the TaleArt sample app on Android while its zone is still being read: round grey placeholders with a short grey bar under each, in the footprint of the ring layout.
The loading skeleton while the zone is read, a real screenshot taken on an emulator before the zone arrived (the stand-in API held the answer back). It uses the footprint of the ring layout on the first read.
The ring bar for a moment, then a tap on the second group opens it, the progress runs, a swipe turns to the next group and Close returns to the bar, now with the groups seen gray. A recording of the sample app (taller) on an Android emulator, set up with its taller.zoneOnly and taller.autoOpenItem debug options. It is under 8 seconds.

A banner zone shows its groups one at a time, or in one of five other variants: the frame's variant setting in the dashboard picks it. A missing or unknown value is the carousel. Each variant ignores the settings the table names, and the rest apply. The carousel has been restyled: each slide shows the group's name and a Watch stories button on a scrim, and its previous and next buttons are 48 dp, so they are easy to touch. Right to left, the line of slides, the turn of the coverflow and the fan of the deck, and the marquee run the other way, and the buttons sit on the other edge.

The banner variants and the settings each one ignores
VariantUseIgnored settings
carouselThe default. One slide at a time, with the group’s name and a Watch stories button on a scrim, previous and next buttons, dots and autoplay.None.
coverflowThe current slide in front, its neighbours turned away at the sides, the far ones smaller and fainter. Swipe, or tap a neighbour.None.
peekThe current slide in the middle with the neighbours showing at the edges, like a pager with padding.None.
deckThe slides fanned out like a hand of cards. The front card opens its group; a tap on a card behind it brings that card forward.None.
stickyA full-width bar on the card colour, pinned to the bottom of the space its host gives it and clear of the system’s bottom bar: previous and next buttons, the current slide’s round cover and name, and a dismiss button (all 44 dp, 48 dp to touch). Dismissed, it stays hidden until the app restarts.aspectRatio, showDots and loop. It always loops.
stripA continuous marquee of the groups’ covers and names, scrolling at a fixed speed (about 18 seconds a loop) toward the start of the line. A press pauses it; under reduced motion it stops and can be scrolled sideways.aspectRatio, showDots, loop, autoplay and intervalSeconds.
Screenshot of the TaleArt sample app on Android with a banner in the carousel variant: the group name Summer and a Watch stories button over the cover, previous and next arrows at the edges, and a row of dots below.
The carousel: the group name and a Watch stories button on a dark scrim over the shown cover, the previous and next buttons on the edges and the dots under it; a real screenshot taken on an emulator.
Screenshot of the TaleArt sample app on Android with a banner in the coverflow variant: the front card with the name Summer and a Watch stories button, and the covers of the neighbouring groups behind it at both sides.
Coverflow: the shown group in front, its neighbours at both sides; a real screenshot taken on an emulator.
Screenshot of the TaleArt sample app on Android with a banner in the peek variant: the middle card with the name Summer and a Watch stories button, and the neighbours cut off at the left and right edges.
Peek: the shown group in the middle, the neighbours cut off at the edges; a real screenshot taken on an emulator.
Screenshot of the TaleArt sample app on Android with a banner in the deck variant: the front card with the name Summer and a Watch stories button, and a fan of cards behind it.
Deck: the groups fanned out behind the front card; a real screenshot taken on an emulator.
Screenshot of the TaleArt sample app on Android with a banner in the sticky variant: a full-width white bar with a previous and a next button, a round cover, the group name Summer and a dismiss cross.
Sticky: a full-width bar for the shown group, with the previous and next buttons, a round cover, the name and a dismiss button; a real screenshot taken on an emulator. At the foot of an edge-to-edge screen the bar pads itself by the navigation bar, inside its own background.
Screenshot of the TaleArt sample app on Android with a banner in the strip variant: group names and round covers (Winter, Trips, Food) running across a soft grey strip, the first cut off at the left edge.
Strip: the groups scrolling as a marquee, covers and names moving toward the start of the line; a real screenshot taken on an emulator.
What you see: the coverflow advances by itself to the second group, a tap on the next arrow shows the third and stops the slide show, and the dots follow each change. A recording of the sample app (taller) on an Android emulator, under 8 seconds. It stays still under reduced motion.

Analytics events

The SDK sends impression, open, view and click events for each zone, in batches and in the background, so the app never waits on them. Turn them off for every view with analytics = false, or for one view with app:taleartAnalytics="false"; a view's setting wins. On native, an impression is a group's bar item first drawn, or a banner slide first shown: the SDK does not measure whether it is on screen.

TaleartSdk.init(
    TaleartConfig(
        apiUrl = "https://api.example.com",
        analytics = true,                   // false turns the SDK's events off for every view
    ),
)
<site.lempert.taleart.sdk.android.TaleartView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:taleartZoneKey="tak_YOUR_ZONE_KEY"
    app:taleartAnalytics="false" />

Countdowns and promo codes

A tale can hold a countdown: the days, hours, minutes and seconds left to a moment in UTC, then its ended text. A tale can also hold a promo code: the code on a ticket, with a Copy stub beside it. Add either in the dashboard, as you add any element of a tale; the SDK draws it as the web SDK does. The countdown counts on the device's clock, so a visitor with a wrong clock sees a wrong time. A promo code is visible to every visitor, so it is not a secret.

Countdown

Set the end as an instant in UTC, such as 2026-12-31T21:00:00Z. The units read Days, Hours, Min and Sec, and from the end on the ended text shows (Ended, unless you set your own). The end reaches your listener once, as onComponentEvent with event.action "ended" and the countdown as event.component.

Promo code

Set the code: 1 to 32 letters, digits, _ or -. Copy puts it on the clipboard and a toast over the card's top corner says Copied for four seconds; when the clipboard refuses, it says Could not copy. A copy reaches your listener as onComponentEvent with event.action "copied" and the code as event.component.value.

import site.lempert.taleart.sdk.TaleartEvent
import site.lempert.taleart.sdk.TaleartListener

// A countdown's end and a promo code's copy reach the listener as componentEvent.
class ComponentHooks : TaleartListener {
    override fun onComponentEvent(event: TaleartEvent) {
        when (event.action) {
            // A countdown reached its end, once; event.component.type is "countdown".
            "ended" -> println("Countdown ${event.component?.id} ended")
            // A promo code was copied; the code is the component's value.
            "copied" -> println("Copied ${event.component?.value}")
        }
    }
}
Screenshot of a TaleArt sample countdown on Android over a sunset picture: a white card reading 'Sale ends in' with 02 Days, 05 Hours, 39 Min and a seconds figure in large digits, each in a grey tile with its unit below.
A countdown in the sample app's viewer (taller) on Android, a real screenshot taken on an emulator. The figures flip as they tick on the device's clock.
What you see: the viewer opens on the countdown and its seconds tile flips each second for about four seconds. A recording of the sample app (taller) with its taller.autoOpenItem debug option.
Screenshot of a TaleArt sample promo code on Android over a teal picture of a gift: a white card titled 'Your welcome gift' with a ticket that holds the code SUMMER25 and a Copy stub, and the note '20% off sandals, until Sunday' under it, the ticket centred in the card.
A promo code in the sample app's viewer (taller) on Android, before a copy, a real screenshot taken on an emulator.
The same promo code card on Android just after Copy was tapped: a dark 'Copied' toast sits over the card's top corner.
The same promo code after Copy, a real screenshot taken on an emulator. The toast shows for four seconds. The code is on the clipboard; Android's own clipboard preview, which it adds at the foot of the screen, was swiped away before the picture was taken.
A close-up of the promo code card on Android just after Copy: the card's top, the ticket with the code SUMMER25 and the Copy stub, and a dark Copied toast over the card's top corner.
The Copied toast in close-up, a real screenshot cropped above the foot of the screen (where Android shows its own clipboard preview), with the promo card a little higher in the zone than in the picture above so that the preview stays clear of it. It is also the poster of the clip below.
What you see: the Copy stub is pressed and a Copied toast shows over the card's corner for about four seconds. A recording of the sample app (taller) with its taller.autoCopy debug option, cropped above the foot of the screen where Android shows its own clipboard preview.

Polls, quizzes, ratings and reactions

A tale can hold one poll, one quiz, one rating and one reaction, each added in the dashboard as any element of a tale. A visitor answers each once: the answer is kept on the device, so a tale opened again shows the result at once. The server counts the answers and the SDK draws the counts it returns. In a preview nothing is sent or kept.

Poll

Two options as buttons. A vote shows each option's share, with the vote selected, and the result is read to screen readers.

Screenshot of a TaleArt sample poll on Android over a sunrise picture: the title 'Quick poll', the question 'Tea or coffee?' and two answer buttons, Tea and Coffee, before any vote.
A poll before the visitor votes, in the sample app's viewer (taller) on Android, a real screenshot taken on an emulator.
The same poll on Android after a vote for Tea: Tea is selected and shows 61 percent, Coffee shows 39 percent, each with a bar filled to its share.
After a vote each option shows its share as a filled bar and the vote is selected. The result is also said to screen readers, as text that is never drawn (a real screenshot taken on an emulator).

Poll views

A poll is drawn in one of five views, chosen by the poll's view. The sticker is the default, and a poll without a view, or with one the SDK does not know, is a sticker. The view changes how the poll looks, not what it records: a vote is counted the same way in every view. Two options can carry a colour (options[].color, #rrggbb); only this-or-that and grid use it, and without one the theme's colours are used.

ViewUseOption colour
sticker (default)A small card over the tale, for a quick question.Not used: the theme's colours.
embedA full-width card with its own results header: the vote total once voted, else the line that results appear after a vote.Not used: the theme's colours.
this-or-thatTwo halves that fill the card. Once voted they split at the vote's share and a VS mark sits on the seam; mirrored in right to left.Each half takes its option's colour, else the theme's; the text on it is white or black, whichever reads better.
gridTwo large tiles side by side, each with its label and, once voted, its share.Each tile takes its option's colour, else the theme's; the text on it is white or black, whichever reads better.
inlineCompact rows, for a poll in the flow of a page.Not used: the theme's colours.

The embed, this-or-that, grid and inline views are shown before a vote and after one. The voted images are real votes in the sample app: a vote for Midnight in each view.

Screenshot of the embed poll view on Android: a card with the title Poll, the question 'Which colourway next?', the line 'Results appear after you vote' and two option rows, Midnight and Coral, before a vote.
The embed view before a vote, in the sample app's viewer (taller), a real screenshot taken on an emulator.
The embed view on Android after a vote for Midnight: the header reads '101 votes', the rows show Midnight at 58 percent, selected, and Coral at 42 percent, each with a bar filled to its share.
The embed view after a vote: its header counts the votes and each row shows its share (a real screenshot taken on an emulator).
Screenshot of the this-or-that poll view on Android: the question 'Which colourway next?' over two equal halves, a blue half for Midnight and a coral half for Coral, with a VS mark on the seam, before a vote.
The this-or-that view before a vote: two equal halves, each in its option's colour (a real screenshot taken on an emulator).
The this-or-that view on Android after a vote for Midnight: the blue half takes 60 percent of the card and the coral half 40 percent, the VS mark sits on the seam.
The this-or-that view after a vote: the seam moves to the vote's share (a real screenshot taken on an emulator).
Screenshot of the grid poll view on Android: two large tiles side by side under the question, Midnight in blue and Coral in coral, before a vote.
The grid view before a vote: two large tiles (a real screenshot taken on an emulator).
The grid view on Android after a vote for Midnight: each tile shows its share, Midnight at 55 percent and Coral at 45 percent.
The grid view after a vote: each tile shows its share (a real screenshot taken on an emulator).
Screenshot of the inline poll view on Android: the question 'Which colourway next?' and two compact pills, Midnight and Coral, in a short card near the foot of the tale, before a vote.
The inline view before a vote: compact pills in a row (a real screenshot taken on an emulator). The card grows to fit the pills' 48 dp touch targets when its box in the dashboard is shorter.
The inline view on Android after a vote for Midnight: the pills give way to a stacked bar split at 52 and 48 percent, with the legend 'Midnight 52% Coral 48%' under it.
The inline view after a vote: a stacked bar of the shares with a legend, the vote in bold (a real screenshot taken on an emulator).
What you see: the tale opens on the this-or-that poll, a vote for Midnight follows, and the halves move to the vote's share, which then holds for three seconds. A recording of the sample app (taller), driven by its taller.autoOpenItem and taller.autoVote debug options.

Quiz

Two to four answers. Once answered, the quiz says whether the answer was right: the correct answer is marked with a tick and a wrong one with a cross, as words too. Without a correct answer the quiz works as a survey and shows only the shares. The correct answer comes with the tale, so a quiz is not a secure test.

Screenshot of a TaleArt sample quiz on Android over a sky picture with clouds: the title Quiz, the question 'What is the capital of France?' and three answer buttons, Paris, Lyon and Nice, before an answer.
A quiz before the visitor answers, in the sample app's viewer (taller) on Android, a real screenshot taken on an emulator.
The same quiz on Android after the visitor picked Lyon, a wrong answer: Paris is outlined in green with a tick and 73 percent, Lyon is outlined in red with a cross and 19 percent, Nice shows 8 percent, .
A wrong answer is marked with a cross and the right one with a tick, each with its share. The marks are read to screen readers as words too (a real screenshot taken on an emulator).
What you see: the quiz opens, a wrong answer (Lyon) is picked, that answer shakes, the wrong and the right ones are marked, and the result holds for three seconds. A recording of the sample app (taller) with its taller.autoVote debug option.

Rating

A slider from 0 to 100 whose thumb is an emoji you choose. The visitor drags it or taps the track, and the rating is sent when they let go. Once rated, the count and the average show.

Screenshot of a TaleArt sample rating on Android over a night sky with a moon: a dark card titled 'Rate us' with the question 'How was your visit?' and a slider whose thumb is a star, set to the middle, before a rating.
A rating before the visitor sends one, in the sample app's viewer (taller) on Android, a real screenshot taken on an emulator.
The same rating on Android after the visitor tapped near 90: the star thumb sits near 90 percent of the track, a small mark shows the average at 78 percent, and the line under the track reads 'Average 78 from 213 ratings'.
The rating once sent: the thumb is the visitor's own rating, and the line under the track shows the average of all ratings (a real screenshot taken on an emulator).
What you see: the star is dragged along the track and let go, and the average of all ratings shows. A recording of the sample app (taller); the drag is a swipe sent from the computer.

Reaction

Two to six different emoji. A tap sends that emoji; every emoji then shows its count, and the tapped one is selected.

Screenshot of a TaleArt sample reaction on Android over fireworks on a night sky: a white card titled 'Your reaction' with the question 'How do you feel?' and three emoji buttons, a heart, a laughing face and a surprised face, before a tap.
A reaction row before a tap, in the sample app's viewer (taller) on Android, a real screenshot taken on an emulator.
The same reaction row on Android after a tap on the heart: the heart button is outlined as selected and shows a count of 129, and the laughing and surprised faces show 41 and 17.
After a tap the tapped emoji is selected and every emoji shows its count (a real screenshot taken on an emulator).
What you see: the heart is tapped, rises from its button, and every emoji then shows its count. A recording of the sample app (taller) with its taller.autoPress debug option.

Each accepted answer reaches your listener as onUserInteracted, with the element in event.component and the answer as its value, and as onComponentEvent with event.action "selected". An answer kept from an earlier visit is not reported again. When an answer fails, the SDK says so on screen and the visitor can answer again.

Right to left

The SDK's views lay out in the device's direction: Hebrew and Arabic are right to left, so the bar starts on the right, a viewer's progress fills from the right, Previous is the right third of the tale, and the poll and rating layouts follow. Numbers, promo codes and a countdown's digits stay left to right. To set the direction yourself, pass layoutDirection = "rtl" or "ltr"; any other value, or none, follows the device.

TaleartSdk.init(
    TaleartConfig(
        apiUrl = "https://api.example.com",
        locale = "he-IL",
        layoutDirection = "rtl",            // "ltr" or "rtl"; null (the default) follows the device
    ),
)
Screenshot of the TaleArt sample app's bar on Android with a Hebrew locale and right-to-left layout: round covers in gradient rings with Hebrew group names under them, the first group on the right and the row running to the left.
The bar with the Hebrew locale and layoutDirection set to rtl, a real screenshot taken on an emulator. The first group is on the right.
Screenshot of the TaleArt sample app's viewer on Android in right-to-left layout, on the second tale of a Hebrew group: the group name chip on the right, the pause and close buttons on the left, and the progress bars, the first full and the second filling, starting from the right.
The viewer in the same setup, a real screenshot taken on an emulator: the progress fills from the right, and the group name sits at the start of the line.

Backups and the visitor id

The SDK makes a visitor id once and keeps it in its own store on the device: the taleart-sdk SharedPreferences. It is random, not derived from the person or the device, and tied to no account. It is still a stable identifier of an app install, so cover it in your privacy notice and your consent setup. Clearing the app's data makes a new one. A reinstall does too, unless a device backup restores the store, and Android Auto Backup does restore it. To start fresh after a reinstall, exclude sharedpref/taleart-sdk.xml in the app's backup rules: android:fullBackupContent for Android 11 and lower, and android:dataExtractionRules from Android 12.

<full-backup-content>
    <exclude domain="sharedpref" path="taleart-sdk.xml" />
</full-backup-content>
<data-extraction-rules>
    <cloud-backup>
        <exclude domain="sharedpref" path="taleart-sdk.xml" />
    </cloud-backup>
</data-extraction-rules>

Not available natively yet

  • onGroupImpression counts a group when it is first shown in a render, not when it is on screen: the native SDK does not measure visibility yet.
  • The Compose TaleartZone has no properties parameter: a composable uses the SDK’s viewer properties from TaleartConfig, and cannot set its own for one zone.

The Install a zone page covers the Web SDK, which draws the same elements.