Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,41 @@
## CHANGE LOG

### Version 4.2.0 *(11 August 2026)*
-------------------------------------------
**What's new**

* **[Android Platform]**
* Supports [CleverTap Android SDK v8.4.1](https://github.com/CleverTap/clevertap-android-sdk/blob/master/docs/CTCORECHANGELOG.md#version-841-august-7-2026).
* Custom-HTML header and footer in-apps now render when the host Activity is not a `FragmentActivity`. Opt in by adding the `CLEVERTAP_INAPP_FRAGMENTLESS_BANNERS` flag to your app's `AndroidManifest.xml`; default off, so existing integrations are unaffected.
* Improves accessibility across in-app notifications and App Inbox with dynamic text scaling, screen-reader announcements and content descriptions, a larger 48dp dismiss-button tap area, and corrected carousel TalkBack navigation.

* **[iOS Platform]**
* Supports [CleverTap iOS SDK v7.8.1](https://github.com/CleverTap/clevertap-ios-sdk/blob/master/CHANGELOG.md#version-781-august-7-2026).
* Improves accessibility across in-app notifications and App Inbox with VoiceOver labels, a larger dismiss-button tap area, and better handling of image content descriptions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@reshab-code add the changelog related to other PRs alse here or after merging, keep a note


* **[Android and iOS Platform]**
* Adds Spin-the-Wheel and Scratch Card gamified templates to Advanced InApp Builder in-app notifications — campaign-driven, no app-side wiring needed.
* Adds per-element click attribution to the in-app `Notification Clicked` event across all in-app templates, plus configurable dismiss gestures (swipe-to-dismiss, and tap-outside on iOS) that are also tracked as `Notification Clicked` events.

**API changes**
* **[Android and iOS Platform]**
* New API: `dismissPipInApp()` - Dismisses the currently visible Picture-in-Picture (PIP) in-app notification. A no-op when no PIP in-app is visible, and other in-app types are never affected. Dismissing frees the in-app display slot, so pair it with `suspendInAppNotifications()` and `resumeInAppNotifications()` to keep a screen free of all in-apps.

**Bug Fixes**
* **[Android Platform]**
* Fixes a critical issue where App Inbox messages were never saved on the device on Android 6.0 through Android 10 (API 23 – 29), leaving the inbox permanently empty. Affects every app using App Inbox on `v4.0.0` and `v4.1.0`; messages reappear automatically on the next inbox fetch after updating, with no code change required.
* Fixes an `AbstractMethodError` crash when a Picture-in-Picture in-app notification played a video on Android 6.0 (API 23). Only affects apps whose `minSdkVersion` is below 24; the fix also covers in-app and App Inbox video playback.

* **[iOS Platform]**
* Fixes an issue where in-apps that were missed while the app was in the background were not shown when the app returned to the foreground.
* Fixes an issue where the `Notification Clicked` event did not include deeplink properties for custom-HTML in-apps.
* Fixes an issue where Advanced InApp Builder full-screen cover in-apps did not respect the device safe area.
* Fixes an issue where client-side in-apps were not persisted when an empty campaign list was received.
* Fixes a potential crash while saving variable diffs, and a potential crash when `syncVariables()` and concurrent variable updates ran at the same time.

* **[Android and iOS Platform]**
* Fixes an issue where the built-in App Inbox did not repaint the message list after a pull-to-refresh — newly fetched messages only appeared after closing and reopening the inbox.

### Version 4.1.0 *(9 June 2026)*
-------------------------------------------
**What's new**
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ To get started, sign up [here](https://clevertap.com/live-product-demo/).

```yaml
dependencies:
clevertap_plugin: 4.1.0
clevertap_plugin: 4.2.0
```

- Run `flutter packages get` to install the SDK
Expand Down
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group = 'com.clevertap.clevertap_plugin'
version = '4.1.0'
version = '4.2.0'

buildscript {
ext.kotlin_version = "1.8.22"
Expand Down Expand Up @@ -62,7 +62,7 @@ android {
dependencies {
implementation 'androidx.core:core-ktx:1.9.0'
testImplementation 'junit:junit:4.13.2'
api 'com.clevertap.android:clevertap-android-sdk:8.3.0'
api 'com.clevertap.android:clevertap-android-sdk:8.4.1'
compileOnly 'androidx.fragment:fragment:1.3.6'
compileOnly 'androidx.core:core:1.9.0'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,10 @@ class DartToNativePlatformCommunicator(
resumeInAppNotifications(result)
}

"dismissPipInApp" -> {
dismissPipInApp(result)
}

"initializeInbox" -> {
initializeInbox(result)
}
Expand Down Expand Up @@ -1536,6 +1540,15 @@ class DartToNativePlatformCommunicator(
}
}

private fun dismissPipInApp(result: MethodChannel.Result) {
if (cleverTapAPI != null) {
cleverTapAPI.dismissPipInApp()
result.success(null)
} else {
result.error(TAG, ERROR_MSG, null)
}
}

private fun initializeInbox(result: MethodChannel.Result) {
if (cleverTapAPI != null) {
cleverTapAPI.initializeInbox()
Expand Down
6 changes: 5 additions & 1 deletion example/android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@ android.useAndroidX=true
#android.enableDexingArtifactTransform.desugaring=false -> this was used before -> check below line
android.useFullClasspathForDexingTransform=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
android.nonFinalResIds=false
# This builtInKotlin flag was added automatically by Flutter migrator
android.builtInKotlin=false
# This newDsl flag was added automatically by Flutter migrator
android.newDsl=false
2 changes: 0 additions & 2 deletions example/ios/Flutter/AppFrameworkInfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,5 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>
32 changes: 32 additions & 0 deletions example/ios/Flutter/ephemeral/flutter_lldb_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#
# Generated file, do not edit.
#

import lldb

def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict):
"""Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages."""
base = frame.register["x0"].GetValueAsAddress()
page_len = frame.register["x1"].GetValueAsUnsigned()

# Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the
# first page to see if handled it correctly. This makes diagnosing
# misconfiguration (e.g. missing breakpoint) easier.
data = bytearray(page_len)
data[0:8] = b'IHELPED!'

error = lldb.SBError()
frame.GetThread().GetProcess().WriteMemory(base, data, error)
if not error.Success():
print(f'Failed to write into {base}[+{page_len}]', error)
return

def __lldb_init_module(debugger: lldb.SBDebugger, _):
target = debugger.GetDummyTarget()
# Caveat: must use BreakpointCreateByRegEx here and not
# BreakpointCreateByName. For some reasons callback function does not
# get carried over from dummy target for the later.
bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$")
bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__))
bp.SetAutoContinue(True)
print("-- LLDB integration loaded --")
5 changes: 5 additions & 0 deletions example/ios/Flutter/ephemeral/flutter_lldbinit
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#
# Generated file, do not edit.
#

command script import --relative-to-command-file flutter_lldb_helper.py
13 changes: 13 additions & 0 deletions example/ios/Flutter/ephemeral/flutter_native_integration.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FLUTTER_ROOT=/Users/runner/hostedtoolcache/flutter/stable-3.44.9-arm64/flutter
FLUTTER_APPLICATION_PATH=/Users/runner/work/clevertap-flutter/clevertap-flutter/wrapper/example
FLUTTER_FRAMEWORK_SWIFT_PACKAGE_PATH=/Users/runner/work/clevertap-flutter/clevertap-flutter/wrapper/example/ios/Flutter/ephemeral/Packages/.packages/FlutterFramework
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_TARGET=lib/main.dart
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=1.0.0
FLUTTER_BUILD_NUMBER=1
DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuNDQuOQ==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049NmIxODJkMmM3NQ==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NWEyYTZhNDJjYw==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMi4y
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=false
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=/Users/runner/work/clevertap-flutter/clevertap-flutter/wrapper/example/.dart_tool/package_config.json
16 changes: 8 additions & 8 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
PODS:
- CleverTap-iOS-SDK (7.7.1):
- CleverTap-iOS-SDK (7.8.1):
- SDWebImage (~> 5.11)
- clevertap_plugin (4.1.0):
- CleverTap-iOS-SDK (= 7.7.1)
- clevertap_plugin (4.2.0):
- CleverTap-iOS-SDK (= 7.8.1)
- Flutter
- Firebase/CoreOnly (10.25.0):
- FirebaseCore (= 10.25.0)
Expand Down Expand Up @@ -71,7 +71,7 @@ PODS:
- nanopb/encode (= 2.30910.0)
- nanopb/decode (2.30910.0)
- nanopb/encode (2.30910.0)
- PromisesObjC (2.4.0)
- PromisesObjC (2.4.1)
- SDWebImage (5.21.7):
- SDWebImage/Core (= 5.21.7)
- SDWebImage/Core (5.21.7)
Expand Down Expand Up @@ -107,8 +107,8 @@ EXTERNAL SOURCES:
:path: Flutter

SPEC CHECKSUMS:
CleverTap-iOS-SDK: c80354ca6bca086bd9ea242c0ad15e859b867ea5
clevertap_plugin: 967312b3841bf78738a922ebe1becbe6085e8029
CleverTap-iOS-SDK: 5cdc6452d832182516340f76444f757962b75db8
clevertap_plugin: 4b1cd9391438bcb537ba4020c3cc7c16271f694b
Firebase: 0312a2352584f782ea56f66d91606891d4607f06
firebase_core: a626d00494efa398e7c54f25f1454a64c8abf197
firebase_messaging: 06391e8f35dc65a00c56580266285263d2861f10
Expand All @@ -120,9 +120,9 @@ SPEC CHECKSUMS:
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
nanopb: 438bc412db1928dac798aa6fd75726007be04262
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesObjC: 752c3227f599e3467650e47ea36f433eeb10c273
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf

PODFILE CHECKSUM: 291c6e37be125d1f160f3b46b95592d2c695ff17

COCOAPODS: 1.15.2
COCOAPODS: 1.16.2
10 changes: 10 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,10 @@ class _MyAppState extends State<MyApp> {
"Resume InApp notifications",
resumeInAppNotifications,
"Resumes display of InApp Notifications."),
_buildListTile(
"Dismiss PIP InApp",
dismissPipInApp,
"Dismisses the currently visible Picture-in-Picture InApp notification, if any."),
]),
if (!kIsWeb)
_buildExpansionTile("Event History", [
Expand Down Expand Up @@ -1793,6 +1797,12 @@ class _MyAppState extends State<MyApp> {
showToast("InApp notification is resumed");
}

void dismissPipInApp() {
CleverTapPlugin.dismissPipInApp();
showToast("PIP InApp notification dismissed (if visible)");
print("InApp -> dismissPipInApp called");
}

void enablePersonalization() {
CleverTapPlugin.enablePersonalization();
showToast("Personalization enabled");
Expand Down
9 changes: 8 additions & 1 deletion ios/Classes/CleverTapPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
[self discardInAppNotifications:call withResult:result];
else if ([@"resumeInAppNotifications" isEqualToString:call.method])
[self resumeInAppNotifications];
else if ([@"dismissPipInApp" isEqualToString:call.method])
[self dismissPipInApp:call withResult:result];
else if ([@"fetchInbox" isEqualToString:call.method])
[self fetchInbox:call withResult:result];
else if ([@"fetchInboxWithCallback" isEqualToString:call.method])
Expand Down Expand Up @@ -671,10 +673,15 @@ - (void)discardInAppNotifications:(FlutterMethodCall *)call withResult:(FlutterR
}

- (void)resumeInAppNotifications {

[[CleverTap sharedInstance] resumeInAppNotifications];
}

- (void)dismissPipInApp:(FlutterMethodCall *)call withResult:(FlutterResult)result {
[[CleverTap sharedInstance] dismissPipInApp];
result(nil);
}

#pragma mark - InApp Controls

- (void)fetchInApps:(FlutterMethodCall *)call withResult:(FlutterResult)result {
Expand Down
4 changes: 2 additions & 2 deletions ios/clevertap_plugin.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#
Pod::Spec.new do |s|
s.name = 'clevertap_plugin'
s.version = '4.1.0'
s.version = '4.2.0'
s.summary = 'CleverTap Flutter plugin.'
s.description = 'The CleverTap iOS SDK for App Analytics and Engagement.'
s.homepage = 'https://github.com/CleverTap/clevertap-ios-sdk'
Expand All @@ -13,7 +13,7 @@ Pod::Spec.new do |s|
s.source_files = 'Classes/**/*'
s.public_header_files = 'Classes/**/*.h'
s.dependency 'Flutter'
s.dependency 'CleverTap-iOS-SDK', '7.7.1'
s.dependency 'CleverTap-iOS-SDK', '7.8.1'
s.ios.deployment_target = '9.0'
end

14 changes: 13 additions & 1 deletion lib/clevertap_plugin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class CleverTapPlugin {
static const libName = 'Flutter';

static const libVersion =
40100; // If the current version is X.X.X then pass as X0X0X
40200; // If the current version is X.X.X then pass as X0X0X

CleverTapPlugin._internal() {
/// Set the CleverTap Flutter library name and the current version for version tracking
Expand Down Expand Up @@ -1043,6 +1043,18 @@ class CleverTapPlugin {
.invokeMethod('resumeInAppNotifications', {});
}

/// Dismisses the currently visible Picture-in-Picture (PIP) InApp Notification, if any.
///
/// This is a no-op when no PIP InApp is visible. Other InApp Notification types are
/// never affected. Safe to call from any thread.
///
/// Note: dismissing releases the InApp display slot, so the next queued InApp (if any)
/// may show immediately. To keep a screen free of all InApps, pair this with
/// suspendInAppNotifications() on screen entry and resumeInAppNotifications() on exit.
static Future<void> dismissPipInApp() async {
return await _dartToNativeMethodChannel.invokeMethod('dismissPipInApp', {});
}

/// Resumes event tracking, overriding any active mute period.
static Future<void> unmute() async {
return await _dartToNativeMethodChannel.invokeMethod('unmute', {});
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: clevertap_plugin
description: The CleverTap Flutter SDK for Mobile Customer Engagement,Analytics and Retention solutions.
version: 4.1.0
version: 4.2.0
homepage: https://github.com/CleverTap/clevertap-flutter

environment:
Expand Down