Skip to content

Repository files navigation

⚡ Flare

JitPackBuild & VerifyKotlinMin SDKLicense

Production-grade, highly customizable alerts and toasts for modern Android applications.

Android's built-in Toast API has strict OS-level restrictions, cannot be custom-styled, lacks support for action buttons, and does not provide queue sequencing. Flare is a lightweight, zero-XML notification library engineered to provide beautiful, responsive banners across both Jetpack Compose and traditional Android Views, sharing a single thread-safe Kotlin core.


📖 Contents


🌟 Features

  • 🎯 Unified Platform Core — Core configurations, state machine, and scheduling models reside in a pure-Kotlin module (flare-core), ready for Kotlin Multiplatform (KMP) targets.
  • 🔄 Synchronized Thread-Safe Queueing — Safely sequence alerts using FlareQueueMode.ENQUEUE (FIFO execution) or instantly replace active alerts using FlareQueueMode.REPLACE.
  • 🖐️ Physics-Based Swipe Gestures — Real-time touch velocity tracking. Fling to dismiss or trigger spring-back snap transitions automatically if the threshold is not cleared.
  • 🌓 Dynamic Theme Alignment — Follows dark/light system presets automatically (FlareTheme.AUTO) or pins explicit LIGHT/DARK UI configurations.
  • 📳 Haptic Feedback Wrappers — Modern tactile vibration feedback using VibrationEffect with defensive fallbacks for older devices.
  • ⏱️ Countdown Indicator Bar — Horizontal progress bar draining dynamically and smoothly relative to the remaining display duration.
  • 📐 Notch & Cutout Protection — Adjusts vertical offsets dynamically using WindowInsetsCompat for status bars, navigation overlays, and physical notches.
  • 📦 Zero-XML Footprint — Entirely written using programmatic Canvas drawing and vector paths, maintaining a compiled library size of under 2 KB to avoid binary bloat.

🏛️ Architecture & Flow of Execution

Flare separates scheduling mechanics from presentation. Banners do not manipulate the UI layer directly; instead, they flow through a synchronized scheduling pipeline.

Component Map

 ┌────────────────────────────────────────────────────────┐
│ Your App Code │
└───────────┬────────────────────────────────┬───────────┘
│ │
│ (Compose UI) │ (View System UI)
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ flare-compose │ │ flare-android │
│ (FlareHost, state) │ │ (DecorView overlay) │
└───────────┬───────────┘ └───────────┬───────────┘
│ │
│ Registers UI Listeners │
└───────────────┬────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ flare-core │
│ (Thread-Safe Queue, Configs, Message Models) │
└────────────────────────────────────────────────────────┘

Color-Coded Execution Pipeline

Below is the execution flow from trigger to dismissal. Colors represent the transition boundaries between the App context, Core Queue, and the Android OS Window layers:

graph TD
%% Node Styling Definitions %%
classDef appNode fill:#FFF3E0,stroke:#FF9800,stroke-width:2px,color:#000;
classDef coreNode fill:#E8EAF6,stroke:#3F51B5,stroke-width:2px,color:#000;
classDef uiNode fill:#E1F5FE,stroke:#03A9F4,stroke-width:2px,color:#000;
classDef osNode fill:#E8F5E9,stroke:#4CAF50,stroke-width:2px,color:#000;
%% Workflow Connections %%
App[1. App triggers show / Builder]:::appNode -->|Submit Message| Lock[2. Acquire Queue stateLock]:::coreNode
Lock -->|Validate Mode| ActiveCheck{3. Is Message Active?}:::coreNode
ActiveCheck -->|Yes & ENQUEUE| Queue[4. Push to Concurrent Queue]:::coreNode
ActiveCheck -->|Yes & REPLACE| DismissPrev[5. Push current to dismiss list & Clear pending]:::coreNode
ActiveCheck -->|No| SetActive[6. Set currentMessage = message]:::coreNode
SetActive -->|Release stateLock| Notify[7. Broadcast show message outside lock]:::coreNode
DismissPrev -->|Release stateLock| Notify
Notify -->|Listener Callback| Window[8. Attach custom FlareView to DecorView / Composable Host]:::uiNode
Window -->|Measure Screen Insets| Insets[9. Apply padding for Status notch / Nav bars]:::osNode
Insets -->|Animation Engine| Spring[10. Run entry SpringAnimation]:::uiNode
Spring -->|Completion / Timeout / Swipe| Clean[11. Run exit animation]:::uiNode
Clean -->|onMessageDismissed| Release[12. Update Queue stateLock & notify next item]:::coreNode
%% Styling Application %%
style ActiveCheck fill:#FCF3CF,stroke:#F1C40F,stroke-width:2px,color:#000
Loading

Note

Why Notify Outside the Lock? Standard synchronized locks are re-entrant on the same thread, meaning double locks don't block. However, keeping the listener notification outside the synchronized block prevents blocking thread resources when running custom user logic (e.g. running action button callbacks) and avoids potential cross-lock deadlocks.


📦 Installation

Add the JitPack maven path to your root settings.gradle.kts configuration:

dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}

Include the native module libraries inside your app module's build.gradle.kts:

dependencies {
// flare-core is pulled in transitively — no need to declare it directly// Traditional XML / View System Integration
implementation("com.github.RoxyBasicNeedBot.Flare:flare-android:v1.0.7")
// Jetpack Compose Integration
implementation("com.github.RoxyBasicNeedBot.Flare:flare-compose:v1.0.7")
}

🚀 Quick Start Guide

1. Global Initialization

Configure your default configurations inside your Application class or main launcher entrypoint:

importandroid.app.Applicationimportcom.roxy.flare.android.Flareimportcom.roxy.flare.FlarePositionimportcom.roxy.flare.FlareDurationimportcom.roxy.flare.FlareThemeclassMainApplication : Application() {
overridefunonCreate() {
super.onCreate()
// Globally initialize default configurationsFlare.configure {
defaultPosition =FlarePosition.BOTTOM
defaultDuration =FlareDuration.SHORT
theme =FlareTheme.AUTO// Real-time dark/light mode switching
hapticEnabled =true
cornerRadiusDp =16f
}
}
}

2. Android View System (Builder Pattern)

Launch customized overlay banners programmatically using the fluent builder API:

importcom.roxy.flare.android.Flareimportcom.roxy.flare.FlareTypeimportcom.roxy.flare.FlarePositionimportcom.roxy.flare.FlareDuration// Standard Warning AlertFlare.with(activity)
.type(FlareType.WARNING)
.message("Unable to sync database logs.")
.show()
// Advanced Custom BuilderFlare.with(activity)
.type(FlareType.ERROR)
.message("Transaction failed. Please try again.")
.position(FlarePosition.TOP) // Drops from the top, notches accounted
.duration(FlareDuration.LONG)
.showProgressBar(true) // Visual draining bar
.action("Retry") {
executePaymentRetry()
}
.show()

3. Jetpack Compose UI (State-Driven)

Wrap your screen contents inside FlareHost and show alerts using the suspend-based API:

importandroidx.compose.foundation.layout.paddingimportandroidx.compose.material3.Buttonimportandroidx.compose.material3.Scaffoldimportandroidx.compose.material3.Textimportandroidx.compose.runtime.Composableimportandroidx.compose.runtime.rememberCoroutineScopeimportandroidx.compose.ui.Modifierimportcom.roxy.flare.FlareTypeimportcom.roxy.flare.compose.FlareHostimportcom.roxy.flare.compose.rememberFlareHostStateimportkotlinx.coroutines.launch
@Composable
funMainComposeScreen() {
val flareState = rememberFlareHostState()
val scope = rememberCoroutineScope()
// Host handles gesture swipes, spring animations, and queue listeningFlareHost(state = flareState) {
Scaffold { paddingValues ->Button(
modifier =Modifier.padding(paddingValues),
onClick = {
scope.launch {
// Suspend function awaits result (Dismissed / ActionPerformed)val result = flareState.show {
type =FlareType.SUCCESS
message ="Data exported successfully!"
action("Undo") {
performUndo()
}
}
}
}
) {
Text("Export File")
}
}
}
}

Tip

Single Host State Rule: Place a single rememberFlareHostState() at the root of your Composable tree (e.g., above your NavHost) instead of declaring one per screen. This ensures a single queue handles transitions gracefully during screen navigation.


🛠️ Configuration Reference Matrix

Configure your parameters in both XML/View and Compose builders:

PropertyTypeDefaultDescription / Notes
typeFlareTypeINFOAlert styles: SUCCESS (✓), ERROR (✗), WARNING (⚠), INFO (ℹ), LOADING (spinner), or CUSTOM
messageString""Title / body text. Auto-truncated with ellipsis after 4 lines.
positionFlarePositionBOTTOMViewport alignment: TOP (status bar padded), BOTTOM (navbar padded), CENTER.
durationFlareDurationSHORTLifecycle: SHORT (2000ms), LONG (3500ms), INDEFINITE, or CUSTOM(ms).
showProgressBarBooleanfalseVisual draining indicator synced to remaining display duration.
hapticBooleantrueDevice tactile vibration trigger upon display of banner.
iconFlareIconTypeDefaultIcon presets: Default, None, or override with Custom(icon) (Bitmaps/Painters).
animationTypeFlareAnimationTypeSLIDEEntry transition physics: SLIDE (spring displacement), FADE, BOUNCE.
cornerRadiusDpFloat?12fCard layout corner rounding.
customColorLong?nullHex ARGB override color (e.g. 0xFF6A1B9A) for the background card.
queueModeFlareQueueModeENQUEUEQueue dispatch mode: ENQUEUE (FIFO order) or REPLACE (override queue).

🔬 Technical Specifications & Under-the-Hood Mechanics

1. Thread-Safe Queue Engine

The queuing system in flare-core coordinates scheduling using an thread-safe singleton state machine:

object FlareQueue {
privateval stateLock =Any()
privateval queue =ConcurrentLinkedQueue<FlareMessage>()
privateval listeners = mutableListOf<FlareQueueListener>()
@Volatile
privatevar currentMessage:FlareMessage?=null
}
  • Lock Contention Management: State changes (like enqueue, onMessageDismissed, and clear) are guarded by stateLock.
  • Lock-Free Notifications: To prevent listener actions from holding the lock or causing deadlocks in re-entrant callbacks, notifications (notifyShow and notifyDismiss) are executed only after releasing the synchronized block.
  • Leak-Proof Queue Drainage: When clear() or enqueue(REPLACE) is invoked, all pending queued items are gathered and explicitly dismissed via notifyDismiss. This triggers their registered listeners to clean up and unregister, eliminating memory leaks of dropped messages.

2. Edge-to-Edge Window Insets Overlay

Traditional Android toasts are heavily restricted. flare-android bypasses this by overlaying views directly onto the Host Activity's decorView:

val decorView = activity.window?.decorView as?ViewGroup
decorView.addView(flareView)
  • Insets Adaptation: The overlay applies padding dynamically using ViewCompat.setOnApplyWindowInsetsListener to wrap around notches, cutouts, status bars, and navigation buttons.
  • Main Thread Confinement: View updates (attaching, dismissing, or post-delay timeouts) check the calling thread and hop to the Main UI Thread using Handler(Looper.getMainLooper()) to prevent crashes when triggered from background contexts.
  • Lifecycle Cleanup: The library hooks into Application.ActivityLifecycleCallbacks to automatically clear overlays and active progress animators when the activity is destroyed.

3. Compose Pointer Gesture Mechanics

In Jetpack Compose, gestures are handled inside the alert Composable:

  • Physics Spring Tracking: Touch gestures are tracked via Modifier.pointerInput detecting drag offsets.
  • Damping & Snap: The drag displacement determines alert layout offset and alpha. If a drag exceeds 50% of screen width or has high velocity, it dismisses the alert. Otherwise, a spring animation snaps the card back to its home boundaries.
  • DisposableEffect Integration: When the FlareHostState leaves the composition tree, DisposableEffect invokes state.release(), resolving all active suspend continuations and unregistering the listener.

📝 License & Authorship

This library is licensed under the BSD 3-Clause License - see the LICENSE file for details.


Developed with precision and care by Roxy ⚡️
Copyright © 2026, 𝕽𝕺𝕏𝕐•𝔹𝕒𝕤𝕚𝕔ℕ𝕖𝕖𝕕𝔹𝕠𝕥. All rights reserved.

About

🔥 Flare — Beautiful, expressive alerts & toasts for Android. Supports Jetpack Compose + View system, queue management, swipe-to-dismiss, action buttons & more.

Topics

Resources

Stars

17 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages