Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7dc8f0b
MOBILE-97: delete ios tests
sergeysozinov Apr 3, 2026
02eec86
Merge pull request #704 from mindbox-cloud/master
itmindbox Apr 6, 2026
35d2bf5
MOBILE-95: Fix dialogShown for push permission request from js
enotniy Apr 7, 2026
4933833
Merge pull request #705 from mindbox-cloud/feature/MOBILE-95
enotniy Apr 8, 2026
5e90616
MOBILE-114: Add public API unregisterInAppCallback
enotniy Apr 16, 2026
43e8e1c
MOBILE-114: Add tests for unregisterInAppCallback
enotniy Apr 16, 2026
107b9b4
MOBILE-114: Change inAppCallback to inAppCallbackProvider
enotniy Apr 17, 2026
3fc81fd
Merge pull request #706 from mindbox-cloud/feature/MOBILE-114
enotniy Apr 17, 2026
70d1ee6
MOBILEWEBVIEW-117: Update rustore version
enotniy Mar 12, 2026
eb22c96
Merge pull request #692 from mindbox-cloud/feature/MOBILEWEBVIEW-117
enotniy Apr 21, 2026
3be75ea
MOBILE-129: Add operationsDomain
enotniy Apr 27, 2026
08884cc
MOBILE-129: Add test for operationsDomain
enotniy Apr 27, 2026
d797449
MOBILE-129: Follow code review
enotniy Apr 27, 2026
a0811cb
MOBILE-129: Add tests for domain validations
enotniy Apr 28, 2026
c3f0659
Merge pull request #707 from mindbox-cloud/feature/MOBILE-129
enotniy Apr 28, 2026
c53f506
MOBILE-129: Add critical error for invalid operationDomain
enotniy Apr 28, 2026
467f78a
Merge pull request #708 from mindbox-cloud/feature/MOBILE-129_2
enotniy Apr 28, 2026
7c5b3df
MOBILE-173: Add anntations SerializedName for WebviewBridge
enotniy May 8, 2026
8134b01
Add kover
enotniy May 8, 2026
052aea3
Merge pull request #710 from mindbox-cloud/kitselyuk/add-kover
enotniy May 8, 2026
d4e5b6c
MOBILE-173: Add test for gson serialization
enotniy May 8, 2026
19d3886
Merge pull request #711 from mindbox-cloud/feature/MOBILE-173
enotniy May 8, 2026
1394622
Merge pull request #713 from mindbox-cloud/master
itmindbox May 14, 2026
c02e049
Bump SDK version to 2.15.2
github-actions[bot] May 19, 2026
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
28 changes: 26 additions & 2 deletions .github/workflows/lint_unitTests_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ on:
- reopened
- synchronize

permissions:
contents: read

jobs:
lint:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -64,13 +67,34 @@ jobs:
- name: Setup Android SDK
uses: android-actions/setup-android@v2

- name: unit tests
run: ./gradlew --no-daemon --stacktrace testDebugUnitTest
- name: unit tests with coverage
run: ./gradlew --no-daemon --stacktrace testDebugUnitTest koverHtmlReport

- name: test report
uses: asadmansr/android-test-report-action@v1.2.0
if: ${{ always() }}

- name: upload coverage report
uses: actions/upload-pages-artifact@v3
if: github.ref == 'refs/heads/develop'
with:
path: build/reports/kover/html

deploy-coverage:
runs-on: ubuntu-latest
needs: unit
if: github.ref == 'refs/heads/develop'
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deploy.outputs.page_url }}
steps:
- name: deploy to GitHub Pages
id: deploy
uses: actions/deploy-pages@v4

build:
runs-on: ubuntu-latest
steps:
Expand Down
13 changes: 13 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ allprojects {
}
}

apply plugin: 'org.jetbrains.kotlinx.kover'

dependencies {
kover(project(':sdk'))
kover(project(':mindbox-firebase'))
kover(project(':mindbox-huawei'))
kover(project(':mindbox-rustore'))
kover(project(':mindbox-firebase-starter'))
kover(project(':mindbox-huawei-starter'))
kover(project(':mindbox-rustore-starter'))
kover(project(':mindbox-sdk-starter-core'))
}

tasks.register('clean', Delete) {
delete rootProject.getLayout().getBuildDirectory()
}
4 changes: 2 additions & 2 deletions example/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,12 @@ dependencies {
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation 'com.google.firebase:firebase-messaging-ktx'
implementation 'com.huawei.hms:push:6.11.0.300'
implementation 'ru.rustore.sdk:pushclient:6.10.0'
implementation 'ru.rustore.sdk:pushclient:7.2.0'

implementation 'com.google.code.gson:gson:2.11.0'

//Mindbox
implementation 'cloud.mindbox:mobile-sdk:2.15.1'
implementation 'cloud.mindbox:mobile-sdk:2.15.2'
implementation 'cloud.mindbox:mindbox-firebase'
implementation 'cloud.mindbox:mindbox-huawei'
implementation 'cloud.mindbox:mindbox-rustore'
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# SDK version property
SDK_VERSION_NAME=2.15.1
SDK_VERSION_NAME=2.15.2
USE_LOCAL_MINDBOX_COMMON=true
android.nonTransitiveRClass=false
kotlin.mpp.androidGradlePluginCompatibility.nowarn=true
Expand Down
7 changes: 5 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ agcp = "1.9.1.300"
ktlint-plugin = "12.1.1"
ksp = "1.9.22-1.0.17"
maven_publish = "0.32.0"
kover = "0.8.3"

pushclient = "6.10.0"
pushclient = "7.2.0"

[bundles]
buildscript-plugins = [
Expand All @@ -51,6 +52,7 @@ buildscript-plugins = [
"ktlint_gradle_plugin",
"ksp_gradle_plugin",
"maven_publish_plugin",
"kover_gradle_plugin",
]

test = [
Expand Down Expand Up @@ -116,4 +118,5 @@ google_services = { module = "com.google.gms:google-services", version.ref = "go
agcp = { module = "com.huawei.agconnect:agcp", version.ref = "agcp" }
ktlint_gradle_plugin = { module = "org.jlleitschuh.gradle:ktlint-gradle", version.ref = "ktlint-plugin" }
ksp_gradle_plugin = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" }
maven_publish_plugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "maven_publish" }
maven_publish_plugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "maven_publish" }
kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" }
1 change: 1 addition & 0 deletions modulesCommon.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ apply plugin: 'kotlin-android'
apply plugin: 'signing'
apply plugin: 'org.jlleitschuh.gradle.ktlint'
apply plugin: 'com.vanniktech.maven.publish'
apply plugin: 'org.jetbrains.kotlinx.kover'

group = 'com.github.mindbox-cloud'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ class InputParametersUnitTest {

private val wrongDomainParameter = arrayListOf(
"",
"https://api.mindbox.ru",
"api.mindbox.ru/",
"https://api.mindbox.ru/",
"hgkkjhhv",
"4854-t789"
)

private val normalizedDomainParameter = arrayListOf(
"https://api.mindbox.ru",
"api.mindbox.ru/",
"https://api.mindbox.ru/"
)

private val wrongUuidParameters = arrayListOf(
"ларалтка ыфдво",
"7659d 79",
Expand Down Expand Up @@ -115,45 +118,22 @@ class InputParametersUnitTest {
}

@Test
fun domain_startsWithHttps() {
val errors = SdkValidation.validateConfiguration(
domain = wrongDomainParameter[1],
endpointId = rightEndpointParameter,
previousDeviceUUID = rightUuidParameter,
previousInstallationId = rightUuidParameter
)
assertEquals(1, errors.size)
assertEquals(SdkValidation.Error.INVALID_FORMAT_DOMAIN, errors[0])
}

@Test
fun domain_endsWithSlash() {
val errors = SdkValidation.validateConfiguration(
domain = wrongDomainParameter[2],
endpointId = rightEndpointParameter,
previousDeviceUUID = rightUuidParameter,
previousInstallationId = rightUuidParameter
)
assertEquals(1, errors.size)
assertEquals(SdkValidation.Error.INVALID_FORMAT_DOMAIN, errors[0])
}

@Test
fun domain_startsWithHttpsAndEndsWithSlash() {
val errors = SdkValidation.validateConfiguration(
domain = wrongDomainParameter[3],
endpointId = rightEndpointParameter,
previousDeviceUUID = rightUuidParameter,
previousInstallationId = rightUuidParameter
)
assertEquals(1, errors.size)
assertEquals(SdkValidation.Error.INVALID_FORMAT_DOMAIN, errors[0])
fun domain_withSchemeOrTrailingSlash_isNormalized() {
normalizedDomainParameter.forEach { input ->
val errors = SdkValidation.validateConfiguration(
domain = input,
endpointId = rightEndpointParameter,
previousDeviceUUID = rightUuidParameter,
previousInstallationId = rightUuidParameter
)
assertEquals("Expected 0 errors for '$input'", 0, errors.size)
}
}

@Test
fun domain_InvalidFormat() {
val errors4 = SdkValidation.validateConfiguration(
domain = wrongDomainParameter[4],
domain = wrongDomainParameter[1],
endpointId = rightEndpointParameter,
previousDeviceUUID = rightUuidParameter,
previousInstallationId = rightUuidParameter
Expand All @@ -162,7 +142,7 @@ class InputParametersUnitTest {
assertEquals(SdkValidation.Error.INVALID_DOMAIN, errors4[0])

val errors5 = SdkValidation.validateConfiguration(
domain = wrongDomainParameter[5],
domain = wrongDomainParameter[2],
endpointId = rightEndpointParameter,
previousDeviceUUID = rightUuidParameter,
previousInstallationId = rightUuidParameter
Expand Down
93 changes: 66 additions & 27 deletions sdk/src/main/java/cloud/mindbox/mobile_sdk/Mindbox.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ import androidx.lifecycle.Lifecycle.State.RESUMED
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.WorkerFactory
import cloud.mindbox.common.MindboxCommon
import cloud.mindbox.mobile_sdk.Mindbox.disposeDeviceUuidSubscription
import cloud.mindbox.mobile_sdk.Mindbox.disposePushTokenSubscription
import cloud.mindbox.mobile_sdk.Mindbox.handleRemoteMessage
import cloud.mindbox.mobile_sdk.di.MindboxDI
import cloud.mindbox.mobile_sdk.di.mindboxInject
import cloud.mindbox.mobile_sdk.inapp.data.managers.SessionStorageManager
Expand Down Expand Up @@ -735,18 +732,72 @@ public object Mindbox : MindboxLog {
}

/**
* Method to register callback for InApp Message
* Registers a callback for InApp messages.
*
* Call this method after you call [Mindbox.init]
* Call this method after [Mindbox.init]. The SDK holds a **strong reference** to
* [inAppCallback], so the callback persists until explicitly replaced or removed via
* [unregisterInAppCallback].
*
* @param inAppCallback used to provide required callback implementation
* Calling this method again replaces the previously registered callback.
*
* **Application-level callback (recommended):**
* Register once in `Application.onCreate` with a callback that does not reference any
* Activity. No cleanup needed.
* ```kotlin
* class MyApp : Application() {
* override fun onCreate() {
* super.onCreate()
* Mindbox.init(...)
* Mindbox.registerInAppCallback(MyGlobalInAppCallback())
* }
* }
* ```
*
* **Per-screen callback:**
* If different screens require different callback behavior and the callback captures an
* Activity reference, use `onResume`/`onPause` — **not** `onCreate`/`onDestroy`.
* Android guarantees that `onPause` of the current Activity is called before `onResume`
* of the next, so callbacks never overlap and the Activity reference is always cleared
* before the Activity can be garbage-collected.
* ```kotlin
* override fun onResume() {
* super.onResume()
* Mindbox.registerInAppCallback(myScreenCallback)
* }
* override fun onPause() {
* super.onPause()
* Mindbox.unregisterInAppCallback()
* }
* ```
*
* @param inAppCallback the callback implementation to register
**/

public fun registerInAppCallback(inAppCallback: InAppCallback) {
MindboxLoggerImpl.d(this, "registerInAppCallback")
mindboxLogI("InApp callback registered: ${inAppCallback::class.simpleName}")
inAppMessageManager.registerInAppCallback(inAppCallback)
}

/**
* Unregisters the current InApp message callback and restores the default SDK behavior.
*
* The default behavior handles URL redirects, deep links, payload copying, and logging
* automatically — the same actions performed when no custom callback is registered.
*
* **When to call:**
* Only needed for per-screen callbacks registered in `onResume`. Call in the corresponding
* `onPause` to release the Activity reference and restore default behavior while another
* screen is in the foreground.
*
* Not needed if the callback was registered at the Application level and does not
* reference any Activity.
*
* @see registerInAppCallback
**/
public fun unregisterInAppCallback() {
mindboxLogI("InApp callback unregistered, default behavior restored")
inAppMessageManager.unregisterInAppCallback()
}

/**
* Method to initialise push services
*
Expand Down Expand Up @@ -1386,6 +1437,7 @@ public object Mindbox : MindboxLog {
endpointId = configuration.endpointId,
previousDeviceUUID = configuration.previousDeviceUUID,
previousInstallationId = configuration.previousInstallationId,
operationsDomain = configuration.operationsDomain,
)

return if (validationErrors.isEmpty()) {
Expand All @@ -1395,27 +1447,14 @@ public object Mindbox : MindboxLog {
throw InitializeMindboxException(validationErrors.toString())
}
MindboxLoggerImpl.e(this, "Invalid configuration parameters found: $validationErrors")
val isDeviceIdError = validationErrors.contains(
SdkValidation.Error.INVALID_DEVICE_ID,
)
val isInstallationIdError = validationErrors.contains(
SdkValidation.Error.INVALID_INSTALLATION_ID,
)

val previousDeviceUUID = if (isDeviceIdError) {
""
} else {
configuration.previousDeviceUUID
}
val previousInstallationId = if (isInstallationIdError) {
""
} else {
configuration.previousInstallationId
}
val isDeviceIdError = validationErrors.contains(SdkValidation.Error.INVALID_DEVICE_ID)
val isInstallationIdError = validationErrors.contains(SdkValidation.Error.INVALID_INSTALLATION_ID)
val isOperationsDomainError = validationErrors.contains(SdkValidation.Error.INVALID_OPERATIONS_DOMAIN)

configuration.copy(
previousDeviceUUID = previousDeviceUUID,
previousInstallationId = previousInstallationId,
previousDeviceUUID = if (isDeviceIdError) "" else configuration.previousDeviceUUID,
previousInstallationId = if (isInstallationIdError) "" else configuration.previousInstallationId,
operationsDomain = if (isOperationsDomainError) null else configuration.operationsDomain,
)
}
}
Expand Down
Loading
Loading