Skip to content

A non-intrusive and fully customizable suite of popup messages for Android.

License

Notifications You must be signed in to change notification settings

Digidemic/whisper

Repository files navigation

Whisper

A non-intrusive and fully customizable suite of popup messages for Android.

Download Template | Example App | Config Examples


Endless fun possibilities!

Download Template

Download Template

Download Template


The most subtle changes can make the biggest difference.

All whisper.yaml template samples can be found in /yaml-samples/.


Transform Whisper and make it your own!

  • Easy to use with simple and short one liners.
  • Ready to use out of the box with no required setup. Just call Whisper with a profile and it handles the rest!
  • Tons of configurations including custom sounds, vibration, fonts, shadow/layers, gradients, and so many design options! See configuration.md for all configurable fields with documentation, examples, and screenshots.
  • Make config changes at runtime in code or import the whisper.yaml template with your config changes (applies automatically. See the collection of samples for inspiration).
  • Comprehensive documentation throughout the entire codebase, configuration yaml, README, and configurations.md.
  • Non-modal to not interfere with user interaction, background processes, and foreground loading.
  • Display as many or as little Whispers on screen at once.
  • Dynamic durations based on message length auto-dismiss Whispers without having to consider what the best time out is for each Whisper (developer defined durations or none at all are also options).
  • Whisper can double as a debugging tool! Use Whisper.Trace or Whisper.Debug for Whispers that only will display for debug builds (no need to comment out or delete these for release!).

Check out the example app for Whisper to test out Whisper and the many configuration options!


Table of Contents


Usage / Examples

Ex 1: Creating the most simple Whisper possible

The following is the same as using Whisper.default(). This example does not contain a set onClick action or time out duration. The Whisper will auto-dismiss based on message length and Whisper.GlobalConfig.timeoutLengthPerCharacter.
In this example, this is the Activity.

Whisper(this, "I am a Whisper using the Default profile. I will auto-dismiss if not clicked.")

Ex 2: Creating multiple Whispers with different auto-dismissing durations

The following demonstrates displaying multiple Whispers at once, different forms of auto-dismissing, and how they are queued for auto-dismissing (skipping in order any that are not set for auto-dismissal).
In the example, this is the Activity.

Whisper.info(this, "Auto-dismiss set for 2.5 seconds", 2500)
Whisper.warn(this, "Never auto-dismiss", 0)
Whisper.error(this, "Auto-dismiss time out driven by message length")

Ex 3: Creating a Whisper with an onClick action

The following demonstrates dismissing a Whisper by tapping it and performing an action when tapped (opening another Whisper in this case). Alternatives to closing a Whisper includes letting it auto-dismiss, calling Whisper.remove(), or calling Whisper.clear(). Whisper.remove() and Whisper.clear() are demonstrated in Example 4.
NOTE: A defined onClick is not required for dismissing a Whisper by tapping it. Leaving the onClick parameter undefined or null will still allow a user to tap a Whisper to dismiss it (as long as Whisper.GlobalConfig.tapToDismiss is true).

Whisper.critical(
    activity = this, 
    message = "Tap to dismiss and perform an additional action",
    duration = 0
) { activity, whisperId ->
    Whisper.info(activity, "You closed the previous Whisper!")
}

Ex 4: Dismiss using Whisper.remove() and Whisper.clear()

The following demonstrates the use of Whisper.remove() and Whisper.clear(). Whispers can also be dismissed by letting them auto-dismiss or by tapping (as long as Whisper.GlobalConfig.tapToDismiss is true). See Example 2 and Example 3 for demonstrations of Whispers closing in other ways.

val whisperId = Whisper.trace(
    activity = this,
    message = "Whisper 1: Use my returned Whisper ID when calling Whisper.remove()",
    duration = 0
)

Whisper.fatal(
    activity = this,
    message = "Whisper 2: Dismiss me with Whisper.clear()",
    duration = 0
)

Whisper.fatal(
    activity = this,
    message = "Whisper 3: Dismiss me with Whisper.clear()",
    duration = 0
)

// ...

// Button onClick action for "Pass Whisper 1's Whisper ID to Whisper.remove()":
Whisper.remove(this, whisperId)

// Button onClick action for "Call Whisper.clear()":
Whisper.clear(this)

YAML Setup / Configuration

Sample whisper.yaml's can be copied or downloaded from /yaml-samples/.

While not required to use Whisper, it is highly recommended to add a whisper.yaml configuration file with your desired changes to your project. The YAML configuration file is an optional file that automatically sets Whisper configurations (Whisper searches for the file and applies it during the first Whisper call. Default values are used if whisper.yaml is not found).

Importing the YAML configuration file

  1. Any whisper.yaml from /yaml-samples/ can be used. Or if preferred, the default whisper.yaml file can be downloaded/copied here.
  2. Add whisper.yaml to your module's assets resource folder (See screenshot below)
    Example: YourProject\app\src\main\assets\whisper.yaml
  3. YAML file name must be whisper.yaml. However, this can be changed to a different file name and extension by updating Whisper.GlobalConfig.yamlFileName. This change MUST be made before any other calls are made to Whisper. Because of the confusion this may cause, it is not recommended to change the whisper.yaml file name.
  4. Update the YAML with your desired changes. Any option you do not wish to change from the default value can be left untouched or removed from the YAML file.
  5. Done! The first Whisper created in the lifecycle of your app will apply the whisper.yaml settings. The first time a Whisper is opened, the YAML is applied. If a yaml file is not found or present, all configuration options are set to their default values.
    For all YAML configurable fields, examples, and documentation, please see configuration.md.

Additional YAML config functions (these should almost never be used)

The following YAML functions are unnecessary to call for most use cases. These functions will either apply whisper.yaml if it has not been applied yet or force it to apply again. However, whisper.yaml automatically applies when calling Whisper for the first time in your app's lifecycle (thus meaning these functions should not be used in standard use cases).

  • Whisper.GlobalConfig.applyYamlIfNeverRan()

    • If a whisper.yaml config is present in your project, this function applies it to your Whisper settings only if whisper.yaml has not already been applied prior in the lifecycle of the app. whisper.yaml automatically applies to your settings when creating a Whisper for the first time in your app's lifecycle. The only time this function may need to be called is if there was dynamic code value changes made to Whisper.GlobalConfig or the profiles prior to creating your first whisper. This so whisper.yaml does not overwrite those programmatic changes.
      Note: It is not recommended to ever make Whisper.GlobalConfig or profile changes programmatically. Recommended is to only use whisper.yaml for configuration changes.
  • Whisper.GlobalConfig.applyYaml()

    • If a whisper.yaml config is present in your project, this function will overwrite any config changes made. This includes if prior changes were made programmatically to Whisper.GlobalConfig, the profiles, or if whisper.yaml was already applied prior in the life of your app. whisper.yaml automatically applies to your settings when creating a Whisper for the first time in your app's lifecycle. The only time this function may be needed is if dynamic code value changes were made to Whisper.GlobalConfig or the profiles that now need to be reverted. This so whisper.yaml overwrites all those programmatic changes no longer needed.
      Note: It is not recommended to ever make Whisper.GlobalConfig or profile changes programmatically. Recommended is to only use whisper.yaml for configuration changes.

Syntax / Setup

Main Whisper Syntax

Main usage and examples of Whisper can be found in Usage / Examples.

All Whisper profiles have 4 function parameters to open a Whisper. Two are mandatory and two are optional:

  • activity - Activity / Required
    • Active Activity. Needed for displaying the Whisper and determining if the app is running in debug.
  • message - String / Required
    • Message text to display within the Whisper.
  • duration - Long / Optional
  • onClick - (Activity, String) -> Unit / Optional
    • Will execute the passed block of code when tapped.
    • Will also close the individual Whisper when tapped regardless if onClick is defined (as long as Whisper.GlobalConfig.tapToDismiss remains true).
    • Available within onClick will be the Activity and its unique Whisper ID.
    • NOTE: If a Whisper does not have a time out (duration passed was 0) and Whisper.GlobalConfig.tapToDismiss is set to false, the Whisper can still be dismissed in the onClick by passing the unique Whisper ID to Whisper.remove() or calling Whisper.clear().

Clean up!

Activities that may use Whisper should add Whsiper.finish() to their onDestroy. Without it, the following error may display in the logs: android.view.WindowLeaked

override fun onDestroy() {
    super.onDestroy()
    Whisper.finish(this)
}

Whisper Profiles

Calling Whisper with a profile is the main use of Whisper. There are 8 profile options to allow for different colors and styles for better customizing Whisper. Two profiles, trace and debug, are reserved as debugging Whispers that do not show in release builds of the app.

Default

Creates a new Whisper using the Default profile. Can be called with Whisper(), Whisper.invoke(), or Whisper.default().

Whisper(activity, "Default Whisper profile")
// or
Whisper.invoke(activity, "Default Whisper profile")
// or
Whisper.default(activity, "Default Whisper profile")

Info

Creates a new Whisper using the Info profile.

Whisper.info(activity, "Info Whisper profile")

Warn

Creates a new Whisper using the Warn profile.

Whisper.warn(activity, "Warn Whisper profile")

Error

Creates a new Whisper using the Error profile.

Whisper.error(activity, "Error Whisper profile")

Fatal

Creates a new Whisper using the Fatal profile.

Whisper.fatal(activity, "Fatal Whisper profile")

Critical

Creates a new Whisper using the Critical profile.

Whisper.critical(activity, "Critical Whisper profile")

Trace

Creates a new Whisper using the Trace profile. Whisper.debug() and Whisper.trace() will only be created if the app running in a debug build.

Whisper.trace(activity, "Trace Whisper profile")

Debug

Creates a new Whisper using the Debug profile. Whisper.debug() and Whisper.trace() will only be created if the app running in a debug build.

Whisper.debug(activity, "Debug Whisper profile")


Configuration Options

How to Configure

There are two ways to update from the default configurations:

  1. Updating and importing the whisper.yaml into your project (recommended)
  2. Programmatically in code (not recommended in most cases)

Please see the configuration.md documentation for more information, examples, and screenshots of each configuration option.

Configuration options and examples

See the configuration markdown for an example of each configuration option. Each config options (both GlobalConfig and Profile Specific) can be navigated to directly with the links below


Other Functions and Considerations

Removing Whispers

Whispers can be dismissed in multiple ways. This includes an auto-dismiss time out, a set-dismiss time out, clicking the Whisper (so long as Whisper.GlobalConfig.tapToDismiss is true), or using Whisper.remove() or Whisper.clear().

  • Removing a specific Whisper via code

    • Anytime a Whisper is created, a unique whisperId (UUID) will be returned. This can be ignored if there is no intention of calling Whisper.remove() for that specific Whisper. Otherwise, this whisperId can be stored and used anytime to dismiss the active Whisper (nothing will happen if the Whisper is already dismissed). For a demonstration, see example 4.
      Whisper.remove(activity, whisperId)
  • Remove all Whispers at once

    • All active Whispers (visibly being displayed and invisible Whispers queued) can all be removed by calling Whisper.clear()
      Whisper.clear(activity)

Get Whisper Count

To get a real-time count of all active Whispers (visibly being displayed and invisible Whispers queued), use the following:

Whisper.activeWhisperCount()

Vibration

A custom vibration can be applied per Whisper profile. More information and configuration details can be found in configurations.md.
If using Vibrate, make sure your module's AndroidManifest.xml includes the following, otherwise vibration will not work:

<uses-permission android:name="android.permission.VIBRATE"/>

Sound

The device's notification or a custom sound file can be played per Whisper profile. If using a custom sound file, the file must be in module's res/raw directory. The set value must be the sound file name without the extension.
Example: src/main/res/raw/horn.mp3 -> "horn"

Apply Custom Fonts

Custom fonts can be applied per profile by setting the font file name to Whisper.PROFILE.design.text.font.fontFamily. Custom fonts need to be in the module's /assets/ directory. Fonts should be either .ttf or .otf. The set value should be the font name with the extension (with the same casing as the file name).
Example: src/main/assets/Miracode.ttf -> "Miracode.ttf"

Using Whisper with coroutines

Whispers do not need to be placed in coroutines. However, if Whispers need to be placed in a coroutine, using Dispatchers.Main is recommended to ensure proper behavior. This may look like the following:

CoroutineScope(Dispatchers.Main).launch {
  // Whispers here
}

Installation

Install with JitPack

  1. Add JitPack to your project's root build.gradle at the end of repositories:
  • dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            mavenCentral()
            maven { url 'https://jitpack.io' }
      }
    }
  1. In the build.gradle of the module(s) you wish to use Whisper with, add the following to dependencies:
  • dependencies {
        // Required: Installs the .aar without any documentation.
        implementation 'com.github.digidemic:whisper:1.0.0'
        
        // Optional: Displays documentation while writing coding. 
        implementation 'com.github.digidemic:whisper:1.0.0:javadoc'
    
        // Optional: Displays documentation (more comprehensive than javadoc in some cases) and uncompiled code when stepping into library.
        implementation 'com.github.digidemic:whisper:1.0.0:sources'
    }
  1. Sync gradle successfully.
  2. Done! Your Android project is now ready to use Whisper. Go to Examples or Syntax for Whisper usage!

Versioning

  • SemVer is used for versioning.
  • Given a version number MAJOR . MINOR . PATCH
    1. MAJOR version - Incompatible API changes.
    2. MINOR version - Functionality added in a backwards-compatible manner.
    3. PATCH version - Backwards-compatible bug fixes.

License

Whisper created by Adam Steinberg of DIGIDEMIC, LLC

Copyright 2024 DIGIDEMIC, LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

The Whisper Example App uses fonts: Acrylic.otf, Miracode.ttf, and Tachyo.otf. All are under SIL OPEN FONT LICENSE Version 1.1. A copy of each license can be found in in example app's assets directory.

Whisper consumes another DIGIDEMIC library, Kyaml, to read and apply the whisper.yaml config file.