A Compose-first Android library for polished “What’s New” release notes. It matches releases by semantic version, remembers dismissed versions, supports runtime localization and themes, and works in both Compose and View/Fragment applications.
Inspired by SvenTiigi/WhatsNewKit. This is an independent Android implementation of the public concepts and behavior of the MIT-licensed original project.
Theme and locale switching · Bottom sheet · Dialog · Full screen · Fragment API · Automatic presentation reset · MP4
Runtime locale switching · Light and dark themes · Arabic RTL layout
- Automatic or manual presentation.
- Material 3 bottom sheet, dialog, and full-screen styles.
- Exact semantic-version matching with patch fallback (
1.2.7→1.2.0). - DataStore, SharedPreferences, in-memory, or custom version stores.
- Android string resources, format arguments, and localized Markdown.
- Runtime per-app locale switching with AppCompat.
- System, light, and dark themes; Android 12+ dynamic color; custom
ColorSchemesupport. - Drawable,
ImageVector, or custom composable feature images. - Primary, URL, suspend custom, and custom-presentation actions.
- Haptic feedback and localized accessibility descriptions.
- Compose API plus
WhatsNewDialogFragmentfor View/Fragment apps. - Configurable spacing, sizing, dismissal behavior, and scroll indicators.
- Android minSdk 21 and compileSdk 35
- JDK 17
- Kotlin 2.0+
- Jetpack Compose with Material 3
dependencies {
implementation(project(":whatsnewkit"))
}./gradlew :whatsnewkit:publishToMavenLocal// settings.gradle.kts in the consuming project
dependencyResolutionManagement {
repositories {
mavenLocal()
google()
mavenCentral()
}
}dependencies {
implementation("io.github.whatsnewkit:whatsnewkit:0.1.1")
}The artifact has not yet been published to Maven Central or another remote registry. Do not use a remote dependency URL until a release channel is configured.
<!-- app/src/main/res/values/strings.xml -->
<resources>
<string name="release_title">What’s **New**</string>
<string name="feature_title">Faster search</string>
<string name="feature_body">Results now appear instantly.</string>
<string name="search_icon_description">Search</string>
<string name="continue_action">Continue</string>
<string name="learn_more_action">Learn more</string>
</resources>val releases = whatsNewCollection {
release("1.2.0") {
title = WhatsNewText.markdownResource(R.string.release_title)
feature(
titleResourceId = R.string.feature_title,
subtitleResourceId = R.string.feature_body,
image = WhatsNewFeatureImage.Vector(
imageVector = Icons.Rounded.Search,
contentDescription = WhatsNewText.resource(
R.string.search_icon_description,
),
),
)
primaryAction(R.string.continue_action)
secondaryOpenUrl(
R.string.learn_more_action,
"https://example.com/release-notes",
)
}
}@Composable
fun App() {
val context = LocalContext.current
val environment = remember(context) {
WhatsNewEnvironment.create(
context = context,
whatsNewCollection = releases,
)
}
MaterialTheme {
AppNavigation()
WhatsNewHost(
environment = environment,
style = WhatsNewPresentationStyle.BottomSheet,
)
}
}WhatsNewEnvironment.create reads the installed app’s versionName. The matching release is shown
only if it has not already been dismissed. Dismissing records the version; merely requesting a
presentation does not.
BottomSheet, Dialog, and FullScreen are available. Manual presentation:
var visible by remember { mutableStateOf(false) }
WhatsNewPresentation(
whatsNew = releases.last(),
visible = visible,
onDismissRequest = { visible = false },
style = WhatsNewPresentationStyle.Dialog,
versionStore = store, // Checks before display and records after dismissal.
)When versionStore is set, manual presentation waits for the store check and skips versions already
dismissed without briefly flashing the UI. Set skipIfPresented = false for a forced preview, or
omit the store to show without checking or recording.
Use the stateless WhatsNewView to embed content in an existing navigation destination.
- Exact matches are preferred.
- Version suffixes such as
2.1.0-beta03are accepted. - If
2.1.7has no exact release, the environment tries2.1.0. - Override
WhatsNewEnvironment.whatsNew()for rollout flags, remote configuration, audience targeting, or another matching policy.
val testEnvironment = WhatsNewEnvironment(
currentVersion = WhatsNewVersion(1, 2, 0),
versionStore = InMemoryWhatsNewVersionStore(),
whatsNewCollection = releases,
)Add translations with standard Android directories such as values-zh-rCN, values-ko, and
values-ar. Resource-backed text is resolved against the active locale when composed. The built-in
default title/action and sample cover en, zh-CN, zh-TW, ko, ja, es, pt-BR, de, fr,
ru, and RTL ar:
WhatsNewText.resource(R.string.feature_body)
WhatsNewText.resource(R.string.welcome_message, userName)
WhatsNewText.markdownResource(R.string.release_title)The DSL provides resource overloads for title, feature, primaryAction, and
secondaryOpenUrl. Image descriptions also accept WhatsNewText, so screen-reader labels can be
localized. Pass null only for a purely decorative image.
Runtime language switching:
AppCompatDelegate.setApplicationLocales(
LocaleListCompat.forLanguageTags("ko"),
)
// Follow the system locale again.
AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList())For Arabic and other RTL locales, enable android:supportsRtl="true" on the consuming app. To expose
the supported locales in Android 13+ system app-language settings, also provide an Android
localeConfig; see the sample manifest and res/xml/locales_config.xml.
For locale persistence on Android 12 and lower, add this service inside <application>:
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data android:name="autoStoreLocales" android:value="true" />
</service>var mode by rememberSaveable { mutableStateOf(WhatsNewThemeMode.System) }
val themeConfig = WhatsNewThemeConfig(mode = mode, useDynamicColor = true)
WhatsNewHost(environment = environment, themeConfig = themeConfig)WhatsNewThemeMode supports System, Light, and Dark. Android 12+ dynamic color is enabled by
default. Omit themeConfig to inherit the host Material theme.
val brandedTheme = WhatsNewThemeConfig(
mode = WhatsNewThemeMode.System,
useDynamicColor = false,
lightColorScheme = lightColorScheme(primary = BrandBlue),
darkColorScheme = darkColorScheme(primary = BrandBlueLight),
)val store = DataStoreWhatsNewVersionStore(context)
store.save(WhatsNewVersion(1, 2, 0))
store.hasPresented(WhatsNewVersion(1, 2, 0))
store.remove(WhatsNewVersion(1, 2, 0)) // Reset one version.
store.clear()Also available: SharedPreferencesWhatsNewVersionStore, InMemoryWhatsNewVersionStore, or a custom
WhatsNewVersionStore for Room, Firebase, account sync, or another backend.
WhatsNewSecondaryAction.OpenUrl(WhatsNewText("Read more"), releaseNotesUrl)
WhatsNewSecondaryAction.Custom(WhatsNewText("Enable feature")) {
repository.enableFeature()
dismiss()
}
WhatsNewSecondaryAction.Present(WhatsNewText("Details")) { dismiss ->
DetailsScreen(onClose = dismiss)
}WhatsNewDialogFragment.show(
fragmentManager = supportFragmentManager,
whatsNew = release,
versionStore = DataStoreWhatsNewVersionStore(this),
asBottomSheet = true,
)Use the suspend WhatsNewDialogFragment.showIfNeeded(...) from a lifecycle coroutine to skip a
version already recorded by the store. The existing show(...) API remains the forced-display
entry point.
Models may contain callbacks and composables, so Fragment payloads are retained in-process across configuration changes but cannot be reconstructed after process death. Drive automatic presentation again from the Activity after process recreation.
val compactLayout = WhatsNewLayout.Default.copy(
contentPadding = PaddingValues(20.dp),
featureListSpacing = 16.dp,
featureImageSize = 44.dp,
showsScrollIndicator = false,
dismissOnClickOutside = true,
)Colors use Material 3 semantic tokens by default. Releases and actions can override their colors.
- Open the repository in Android Studio and select JDK 17.
- Wait for Gradle sync, then select the
samplerun configuration. - Start an API 21+ emulator or connect a device.
- Run the app and try language, theme, bottom-sheet, dialog, full-screen, and Fragment modes.
./gradlew :sample:assembleDebug
adb install -r sample/build/outputs/apk/debug/sample-debug.apk./gradlew \
:whatsnewkit:testDebugUnitTest \
:whatsnewkit:lintDebug \
:sample:lintDebug \
:whatsnewkit:assembleRelease \
:sample:assembleDebugRelease AAR: whatsnewkit/build/outputs/aar/whatsnewkit-release.aar
- Update the version in
whatsnewkit/build.gradle.kts. - Update documentation and release notes.
- Run the complete build and test command above.
- Create an annotated tag such as
v0.1.1. - Push the branch and tag to GitHub.
- Create a GitHub Release and attach
whatsnewkit-release.aarfor direct downloads if desired. - Document compatibility, features, fixes, and upgrade steps in the release description.
git tag -a v0.1.1 -m "WhatsNewKit Android 0.1.1"
git push origin main
git push origin v0.1.1whatsnewkit/
├── whatsnewkit/ Android library
├── sample/ Runnable sample app
├── README.md English documentation
├── README.zh-CN.md
├── README.ko.md
└── README.ja.md
Released under the MIT License.



