Android v26.10.0
Compatibility
| Name | Version | Implementation |
|---|---|---|
| Compile SDK | 36 | compileSdk = q2libs.versions.compileSdk.get() |
| Target SDK | 36 | targetSdk = q2libs.versions.targetSdk.get() |
| Min SDK | 29 | minSdk = q2libs.versions.minSdk.get() |
| Java / JVM | 21 | javaVersion = q2libs.versions.javaVersion |
| Gradle | 9.4.1 | gradle-wrapper.properties |
| Android Gradle Plugin | 9.1.0 | id(q2libs.plugins.android.application) |
| Kotlin Gradle Plugin | 2.3.20 | (built into AGP 9; no explicit plugin alias) |
| KSP | 2.3.6 | id(q2libs.plugins.ksp) |
| Google Services Plugin | 4.4.2 | id(q2libs.plugins.google.services) |
| Kotlin Serialization | 2.3.20 | id(q2libs.plugins.kotlin.serialization) |
No toolchain bumps in this release. The table above is unchanged from 26.9.1.
Migration from 26.9.1 to 26.10.0
If you're jumping more than 2–3 versions at once, walking each version's migration notes individually can be tedious and error-prone. Consider the Large Version Migrations guide instead. It walks you through replacing your local DevApp with a fresh copy at the target tag and dropping your module back in, which atomically picks up every scaffolding change at once.
Nothing in this release breaks compilation. One change alters runtime behavior
without a compiler error, so read the supportsBiometrics section below even if
your module builds cleanly.
Action required: custom AuthenticationModule must declare supportsBiometrics
AuthenticationModule gains a supportsBiometrics property, defaulted to false. It gates the
biometrics row Q2 publishes into mobile settings. An AuthenticationModule that implements
updateBiometrics and isBiometricsEnabled but leaves supportsBiometrics at the default will
build fine and silently lose its biometrics settings row.
class MyLoginModule(private val sdkUtils: SdkUtils) : AuthenticationModule {
override val supportsBiometrics = true
override suspend fun updateBiometrics(isOn: Boolean, activity: AppCompatActivity): Boolean { … }
override suspend fun isBiometricsEnabled(activity: AppCompatActivity): BiometricsEnabledState { … }
}
It is not suspend, because the settings list is assembled synchronously for the web app. It
answers "is this implemented", not "is biometrics on for this user or available on this device".
Return a constant true, not a device capability check.
Previously the row appeared whenever any AuthenticationModule was registered. For a module that
did no biometrics, that produced a toggle that flipped itself straight back off.
updateBiometrics and isBiometricsEnabled are now defaulted
Both members are no longer abstract. A module that does no biometrics implements neither:
suspend fun updateBiometrics(isOn: Boolean, activity: AppCompatActivity): Boolean = false
suspend fun isBiometricsEnabled(activity: AppCompatActivity): BiometricsEnabledState =
BiometricsEnabledState.N_A
Existing overrides keep working unchanged. If you were implementing these only to satisfy the
compiler, delete them and leave supportsBiometrics at false.
These three members leave AuthenticationModule once a dedicated settings-provider module type
exists. They are documented here as the current contract, not as a long-term home.
SdkUtils.loadPathInUuxViewBeforeLogon / AfterLogon are deprecated
Both are deprecated in favor of openUUXRoute, which replaces the pre- and post-login split with a
single call. They still work in 26.10.0, so this migration is optional for this release.
- sdkUtils.loadPathInUuxViewAfterLogon("activity-center")
+ sdkUtils.openUUXRoute("activity-center")
See openUUXRoute below for the parameter
shape and the behavior differences.
SdkUtils.isSacViaPushAllowed() is deprecated
Secure Account Creation capabilities are now exposed as one object. The old method remains as a default implementation delegating to it, so existing call sites keep compiling and working.
- if (sdkUtils.isSacViaPushAllowed()) { … }
+ if (sdkUtils.sacCapabilities.allowSacViaPush) { … }
New SDK interface capabilities
SdkUtils.openUUXRoute (single-entry UUX navigation)
/**
* Navigates the UUX web view to an Ember route through the router bridge, so the route is
* (re-)entered and refreshed even when it is already the current route.
*
* If called before the user is authenticated, the route is held and replayed automatically
* once they reach the post-login landing page.
*
* @param route Ember route name, e.g. "activity-center".
* @param queryParams query params passed to the route, e.g. mapOf("isMrdcHistory" to true).
*/
abstract fun openUUXRoute(route: String, queryParams: Map<String, Any> = emptyMap())
sdkUtils.openUUXRoute("activity-center")
sdkUtils.openUUXRoute("activity-center", mapOf("isMrdcHistory" to true))
Three behaviors the deprecated loadPathInUuxView* pair did not have:
- No pre-/post-login decision. Called while unauthenticated, the route is held in a
latest-wins slot and replayed when the user reaches the landing page. Called post-login, it
navigates immediately. A
DeepLinkModuleno longer has to guess which variant to call. - Re-enters the current route. Because it goes through the Ember router bridge rather than a path load, calling it for the route already on screen refreshes that route instead of no-opping. This is what makes "open deposit history and show the deposit I just made" work.
- UUX version differences are handled for you. The bridge picks
transitionToortransitionToRoute, and maps renamed legacy route names, based on the UUX version the app is running against. There is no minimum UUX version to check and no per-FI branching to write.
A held route is dropped on login failure, so a route requested before authentication is never replayed into a different user's session on a shared device.
AuthenticationModule.clearCachedCredentials()
A new defaulted no-op hook for dropping whatever your module cached at login: a password held for biometric enrollment, a username, a token.
fun clearCachedCredentials() {}
The host calls it on logout, on a profile switch, and best-effort when a live session returns to
the login screen. Override this rather than logOut: a profile switch is not a logout, so logOut
never fires for one, and a module that clears only there goes on holding one user's password while
a second user is signed in.
Contract requirements:
- Must be idempotent and must not throw. One logout can call it more than once.
- Arrives on an arbitrary thread. A profile switch or session expiry arrives on the WebView's
JavaScript bridge thread, so mark cached fields
@Volatileor synchronize. - Touch no
Viewand no Compose state. Nothing waits on the call.
It deliberately does not fire part-way through a sign-in, since the module is likely still using the credential. Clear your cache at the start of each login attempt as well and nothing accumulates.
AuthenticationModule.logOut (documented contract)
No signature change, but the contract is now explicit: the host will not proceed until
onComplete is called. Call it on every path including failure, and note that a finally is
the safe shape. Otherwise logout never finishes and the user is stranded with no route back to
login.
Override logOut only for work that must finish before the login screen returns, which in practice
means ending a session held off-device. To clear a local credential cache, use
clearCachedCredentials instead: it covers more cases and cannot hang the host.
postQ2LoginResult (hand a completed login back to the host)
A new Fragment extension in com.q2.sdk_interfaces.authentication replaces hand-rolled
setFragmentResult plumbing:
fun Fragment.postQ2LoginResult(response: LoginResponse)
- parentFragmentManager.setFragmentResult(
- AuthenticationModule.ResultKeys.keyFragmentResult,
- Bundle().apply {
- putParcelable(AuthenticationModule.ResultKeys.keyLoginResponse, response)
- }
- )
+ postQ2LoginResult(response)
The host listens on AuthenticationModule.ResultKeys.keyFragmentResult. A wrong key produces no
error, just a login that appears to succeed and goes nowhere. That is why this is now a provided
extension rather than something each module reproduces.
LoginResponse.OAuthTokenResponse.bioEnablement
class OAuthTokenResponse(
val q2Token: String,
val idToken: String?,
val accessToken: String?,
val username: String? = null,
val bioEnablement: Boolean = false
) : LoginResponse()
Whether biometric login is enabled for this user on this device. Supplied by the authenticating
module, which owns that state, and reported in the logonUser authType audit alongside the
authentication method. Defaulted to false, so existing constructor calls are unaffected.
SacCapabilities
data class SacCapabilities(
val allowSacViaPush: Boolean,
val disableSacModification: Boolean,
)
abstract val sacCapabilities: SacCapabilities
disableSacModification is new in this release and has no deprecated predecessor. It reports that
the installation forbids changing an existing Secure Account Creation enrollment, as distinct from
allowSacViaPush, which reports whether SAC via push is available at all.
Other changes affecting modules
SdkUtils.getDeviceID() is backed by a new identifier policy
getDeviceID() previously read Settings.Secure.ANDROID_ID inline. It now delegates to
DeviceIdentifier in the new com.q2.policies library, which returns ANDROID_ID when available
and otherwise mints a random UUID once, persists it encrypted, and reuses it for the life of the
install. Stored values are read first, so an install that has fallen back can never flip back to
ANDROID_ID and change identity mid-life.
For the normal case the returned value is byte-identical to 26.9.1. The change is that
getDeviceID() no longer returns an empty or null-backed value on a device where ANDROID_ID is
unavailable.
Inbound SSO: deviceIdentifierParamKey
A new optional key in the Inbound SSO module's settings.json data block. When present, the device
identifier is sent to the IDP as a sign-in parameter under that name:
"data": {
"buildProperties": {
"issuer": "…",
"clientId": "…",
"identifier_claim": "sub",
"deviceIdentifierParamKey": "Device-ID"
}
}
Omit the key and no device-identifier parameter is sent. See Configuring Inbound SSO.
Native module launches now sync the navigation drawer
When the web app opens a UIModule through the module bridge, Core now highlights the matching
drawer item for the duration of the module and restores the previous selection when the module is
dismissed, including when the module fails to start. Matching is by the route value in the call's
data object, falling back to the module identifier.
No module-side change is required. If your module appears in the navigation drawer, confirm the
drawer item's route matches the identifier or the route you are launched with, or the highlight
will not follow.
ServiceCallsV2.postEmptyLogonUser takes a JsonObject
- fun postEmptyLogonUser(@Body emptyBody: EmptyLoginUserEntity): Call<ResponseBody>
+ fun postEmptyLogonUser(@Body body: JsonObject): Call<ResponseBody>
EmptyLoginUserEntity is deleted. The capabilities call now sends a real body so the logonUser
authType audit fields can ride along. Only relevant if your module called this directly.
Version Updates
No library, plugin, or toolchain versions changed in this release. The version catalog is identical to 26.9.1.
Version Catalog
AndroidX Libraries
| Library | Version | Implementation |
|---|---|---|
| AndroidX Core KTX | 1.12.0 | implementation(q2libs.androidx.core.ktx) |
| AndroidX AppCompat | 1.6.1 | implementation(q2libs.androidx.appcompat) |
| AndroidX Activity KTX | 1.8.1 | implementation(q2libs.androidx.activity.ktx) |
| AndroidX Legacy Support | 1.0.0 | implementation(q2libs.androidx.legacy.support) |
| AndroidX Constraint Layout | 2.1.4 | implementation(q2libs.androidx.constraintlayout) |
| AndroidX CardView | 1.0.0 | implementation(q2libs.androidx.cardview) |
| AndroidX Local Broadcast Manager | 1.1.0 | implementation(q2libs.androidx.localbroadcastmanager) |
| AndroidX Percent Layout | 1.0.0 | implementation(q2libs.androidx.percentlayout) |
| AndroidX Biometric | 1.1.0 | implementation(q2libs.androidx.biometric) |
| AndroidX RecyclerView | 1.3.2 | implementation(q2libs.androidx.recyclerview) |
| AndroidX WebKit | 1.14.0 | implementation(q2libs.androidx.webkit) |
| AndroidX Media | 1.6.0 | implementation(q2libs.androidx.media) |
| AndroidX Browser | 1.8.0 | implementation(q2libs.androidx.browser) |
| AndroidX Grid Layout | 1.0.0 | implementation(q2libs.androidx.gridlayout) |
| AndroidX Preference | 1.2.1 | implementation(q2libs.androidx.preference) |
| AndroidX Security Crypto | 1.1.0-beta01 | implementation(q2libs.androidx.security.crypto) |
| AndroidX ExifInterface | 1.4.2 | implementation(q2libs.androidx.exifinterface) |
| AndroidX Credentials (ref only) | 1.6.0 | (catalog version pin; no library alias yet) |
AndroidX Fragment
| Library | Version | Implementation |
|---|---|---|
| AndroidX Fragment | 1.7.0 | implementation(q2libs.androidx.fragment) |
| AndroidX Fragment KTX | 1.7.0 | implementation(q2libs.androidx.fragment.ktx) |
AndroidX Navigation
| Library | Version | Implementation |
|---|---|---|
| AndroidX Navigation Fragment KTX | 2.7.4 | implementation(q2libs.androidx.navigation.fragment.ktx) |
| AndroidX Navigation UI KTX | 2.7.4 | implementation(q2libs.androidx.navigation.ui.ktx) |
| AndroidX Navigation Compose | 2.8.9 | implementation(q2libs.androidx.navigation.compose) |
AndroidX Lifecycle
| Library | Version | Implementation |
|---|---|---|
| AndroidX Lifecycle Runtime KTX | 2.7.0 | implementation(q2libs.androidx.lifecycle.runtime.ktx) |
| AndroidX Lifecycle ViewModel KTX | 2.7.0 | implementation(q2libs.androidx.lifecycle.viewmodel.ktx) |
| AndroidX Lifecycle Process | 2.7.0 | implementation(q2libs.androidx.lifecycle.process) |
| AndroidX Lifecycle Compiler | 2.7.0 | ksp(q2libs.androidx.lifecycle.compiler) |
| AndroidX Lifecycle Extensions | 2.2.0 (Deprecated) | implementation(q2libs.androidx.lifecycle.extensions) |
| AndroidX Lifecycle ViewModel Compose | 2.7.0 | implementation(q2libs.androidx.lifecycle.viewmodel.compose) |
AndroidX Room
| Library | Version | Implementation |
|---|---|---|
| Room Runtime | 2.8.4 | implementation(q2libs.androidx.room.runtime) |
| Room Compiler | 2.8.4 | ksp(q2libs.androidx.room.compiler) |
| Room KTX | 2.8.4 | implementation(q2libs.androidx.room.ktx) |
AndroidX CameraX
| Library | Version | Implementation |
|---|---|---|
| CameraX Camera2 | 1.5.0 | implementation(q2libs.androidx.camera.camera2) |
| CameraX Lifecycle | 1.5.0 | implementation(q2libs.androidx.camera.lifecycle) |
| Camera View | 1.5.0 | implementation(q2libs.androidx.camera.view) |
AndroidX Compose
| Library | Version | Implementation |
|---|---|---|
| Compose BOM | 2025.12.01 | implementation(platform(q2libs.androidx.compose.bom)) |
| Compose Foundation | - | implementation(q2libs.androidx.compose.foundation) |
| Compose UI | - | implementation(q2libs.androidx.compose.ui) |
| Compose UI Graphics | - | implementation(q2libs.androidx.compose.ui.graphics) |
| Compose UI Tooling | - | implementation(q2libs.androidx.compose.ui.tooling) |
| Compose UI Tooling Preview | - | implementation(q2libs.androidx.compose.ui.tooling.preview) |
| Compose UI Test Manifest | - | implementation(q2libs.androidx.compose.ui.test.manifest) |
| Compose UI Test JUnit4 | - | implementation(q2libs.androidx.compose.ui.test.junit4) |
| Compose Runtime LiveData | - | implementation(q2libs.androidx.compose.runtime.livedata) |
| Material Icons Core | - | implementation(q2libs.material.icons.core) |
| Material3 | - | implementation(q2libs.material3) |
| Material3 Adaptive | - | implementation(q2libs.androidx.compose.material3.adpative) |
| Activity Compose | 1.8.2 | implementation(q2libs.activity.compose) |
Google Libraries
| Library | Version | Implementation |
|---|---|---|
| Material | 1.10.0 | implementation(q2libs.google.material) |
| GSON | 2.10.1 | implementation(q2libs.google.gson) |
| Play Services Vision | 20.1.3 (Deprecated) | implementation(q2libs.google.play.services.vision) |
| ZXing Core | 3.5.1 | implementation(q2libs.google.zxing.core) |
| Age Signals | 0.0.4 | implementation(q2libs.google.age.signals) |
Firebase
| Library | Version | Implementation |
|---|---|---|
| Firebase Messaging | 23.3.0 | implementation(q2libs.google.firebase.messaging) |
| Firebase Messaging KTX | 23.3.0 | implementation(q2libs.google.firebase.messaging.ktx) |
| Firebase Instance ID | 21.1.0 | implementation(q2libs.google.firebase.iid) |
| Firebase Core | 21.1.1 | implementation(q2libs.google.firebase.core) |
| Firebase Crashlytics Build Tools | 2.9.9 | implementation(q2libs.google.firebase.crashlytics.buildtools) |
Kotlin & Jetbrains
| Library | Version | Implementation |
|---|---|---|
| Kotlinx Coroutines | 1.7.3 | implementation(q2libs.jetbrains.kotlinx.coroutines) |
| Kotlinx Serialization JSON | 1.11.0 | implementation(q2libs.kotlinx.serialization) |
| Jetbrains Annotations | 20.1.0 | implementation(q2libs.jetbrains.annotations) |
Networking
| Library | Version | Implementation |
|---|---|---|
| Retrofit2 | 2.9.0 | implementation(q2libs.retrofit2.retrofit) |
| Retrofit2 Converter GSON | 2.9.0 | implementation(q2libs.retrofit2.converter.gson) |
| Retrofit2 RxJava Adapter | 2.1.0 | implementation(q2libs.retrofit2.adapter.rxjava) |
| OkHttp3 | 4.10.0 | implementation(q2libs.okhttp3.okhttp) |
| OkHttp3 Logging Interceptor | 4.10.0 | implementation(q2libs.okhttp3.logging.interceptor) |
| RxAndroid | 1.2.0 | implementation(q2libs.reactivex.rxandroid) |
| Volley | 1.2.1 | implementation(q2libs.volley) |
Dependency Injection
| Library | Version | Implementation |
|---|---|---|
| Dagger | 2.59.2 | implementation(q2libs.dagger) |
| Dagger Compiler | 2.59.2 | ksp(q2libs.dagger.compiler) |
| Hilt | 2.59.2 | implementation(q2libs.hilt) |
| Hilt Compiler | 2.59.2 | ksp(q2libs.hilt.compiler) |
| Hilt Navigation Compose | 1.2.0 | implementation(q2libs.androidx.hilt.navigation.compose) |
| Glassfish Annotation | 10.0-b28 | implementation(q2libs.glassfish.annotation) |
Dependency Injection (Koin)
| Library | Version | Implementation |
|---|---|---|
| Koin Android | 3.4.3 | implementation(q2libs.koin.android) |
| Koin Core | 3.4.3 | implementation(q2libs.koin.core) |
| Koin AndroidX Compose | 3.5.0 | implementation(q2libs.koin.androidx.compose) |
Third-Party Libraries
| Library | Version | Implementation |
|---|---|---|
| EventBus | 3.3.1 | implementation(q2libs.eventbus) |
| EventBus Processor | 3.3.1 | ksp(q2libs.eventbus.processor) |
| Commons IO | 2.6 | implementation(q2libs.commons.io) |
| Picasso | 2.8 | implementation(q2libs.picasso) |
| Coil Compose | 2.7.0 | implementation(q2libs.coil) |
| Coil View | 2.7.0 | implementation(q2libs.coil.view) |
| Timber | 5.0.1 | implementation(q2libs.timber) |
| Apache Commons Imaging | 1.0-alpha2 | implementation(q2libs.apache.commons.imaging) |
Q2 SDK Components
| Library | Coord | Version | Implementation |
|---|---|---|---|
| Q2 SDK Interfaces | com.q2.msdk:sdk_interfaces | q2Version | implementation project(':sdk_interfaces') |
| Q2 UI Components | com.q2:ui-components | 0.1.1 | implementation(q2libs.q2.ui.components) |
| Q2 Routing Service | (local project, unpublished) | q2Version | implementation project(':modules:q2_routing_service') |
Plugin Declarations
| Plugin | ID | Implementation |
|---|---|---|
| Android Application | com.android.application | id(q2libs.plugins.android.application) |
| Android Library | com.android.library | id(q2libs.plugins.android.library) |
| KSP | com.google.devtools.ksp | id(q2libs.plugins.ksp) |
| Compose Compiler | org.jetbrains.kotlin.plugin.compose | id(q2libs.plugins.compose.compiler) |
| Hilt | com.google.dagger.hilt.android | id(q2libs.plugins.hilt) |
| Kotlin Parcelize | org.jetbrains.kotlin.plugin.parcelize | id(q2libs.plugins.kotlin.parcelize) |
| Google Services | com.google.gms.google-services | id(q2libs.plugins.google.services) |
| Artifactory | com.jfrog.artifactory | id(q2libs.plugins.artifactory) |
| Kotlin Serialization | org.jetbrains.kotlin.plugin.serialization | id(q2libs.plugins.kotlin.serialization) |
| Dokka | org.jetbrains.dokka | id(q2libs.plugins.dokka) |
Library Bundles
| Bundle | Libraries | Implementation |
|---|---|---|
| Dagger | dagger, glassfish-annotation | implementation(q2libs.bundles.dagger) |
| Retrofit2 | retrofit2-retrofit, retrofit2-converter-gson | implementation(q2libs.bundles.retrofit2) |
| Koin | koin-android, koin-core, koin-androidx-compose | implementation(q2libs.bundles.koin) |