[deps]: Update androidxCredentialsProviderEvents to v1.0.0-alpha06 (#6734)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Patrick Honkonen <phonkonen@bitwarden.com>
This commit is contained in:
renovate[bot]
2026-04-07 15:21:44 -04:00
committed by GitHub
parent 89491bbb71
commit 31b3b0304c
14 changed files with 70 additions and 209 deletions

View File

@@ -1,14 +0,0 @@
package com.x8bit.bitwarden.data.platform.manager
import android.content.Context
/**
* F-Droid implementation of [GmsManager]. Always returns `false` since GMS is not available.
*/
@Suppress("UnusedParameter")
class GmsManagerImpl(
context: Context,
) : GmsManager {
override fun isVersionAtLeast(version: Int): Boolean = false
}

View File

@@ -1,18 +0,0 @@
package com.x8bit.bitwarden.data.platform.manager
/**
* The minimum GMS Core version required for Credential Exchange Protocol (CXP) features.
*/
const val MINIMUM_CXP_GMS_VERSION: Int = 261031035
/**
* Manages checks against the installed Google Mobile Services (GMS) Core version.
*/
interface GmsManager {
/**
* Returns `true` if the installed GMS Core version is at least [version], or `false` if
* GMS Core is not installed or does not meet the minimum version.
*/
fun isVersionAtLeast(version: Int): Boolean
}

View File

@@ -47,8 +47,6 @@ import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManagerImpl
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManagerImpl
import com.x8bit.bitwarden.data.platform.manager.GmsManager
import com.x8bit.bitwarden.data.platform.manager.GmsManagerImpl
import com.x8bit.bitwarden.data.platform.manager.LogsManager
import com.x8bit.bitwarden.data.platform.manager.LogsManagerImpl
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
@@ -338,12 +336,6 @@ object PlatformManagerModule {
thirdPartyAutofillEnabledManager = thirdPartyAutofillEnabledManager,
)
@Provides
@Singleton
fun provideGmsManager(
@ApplicationContext context: Context,
): GmsManager = GmsManagerImpl(context = context)
@Provides
@Singleton
fun provideDatabaseSchemeManager(

View File

@@ -1,14 +1,13 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.vault
import androidx.lifecycle.viewModelScope
import com.bitwarden.core.data.manager.BuildInfoManager
import com.bitwarden.core.data.manager.model.FlagKey
import com.bitwarden.network.model.PolicyTypeJson
import com.bitwarden.ui.platform.base.BackgroundEvent
import com.bitwarden.ui.platform.base.BaseViewModel
import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData
import com.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
import com.x8bit.bitwarden.data.platform.manager.GmsManager
import com.x8bit.bitwarden.data.platform.manager.MINIMUM_CXP_GMS_VERSION
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
@@ -28,18 +27,19 @@ import javax.inject.Inject
@HiltViewModel
class VaultSettingsViewModel @Inject constructor(
snackbarRelayManager: SnackbarRelayManager<SnackbarRelay>,
private val buildInfoManager: BuildInfoManager,
private val firstTimeActionManager: FirstTimeActionManager,
private val featureFlagManager: FeatureFlagManager,
private val gmsManager: GmsManager,
private val policyManager: PolicyManager,
) : BaseViewModel<VaultSettingsState, VaultSettingsEvent, VaultSettingsAction>(
initialState = run {
val firstTimeState = firstTimeActionManager.currentOrDefaultUserFirstTimeState
VaultSettingsState(
showImportActionCard = firstTimeState.showImportLoginsCardInSettings,
showImportItemsChevron = featureFlagManager.getFeatureFlag(
key = FlagKey.CredentialExchangeProtocolImport,
) && gmsManager.isVersionAtLeast(MINIMUM_CXP_GMS_VERSION),
showImportItemsChevron = !buildInfoManager.isFdroid &&
featureFlagManager.getFeatureFlag(
key = FlagKey.CredentialExchangeProtocolImport,
),
)
},
) {
@@ -67,8 +67,8 @@ class VaultSettingsViewModel @Inject constructor(
) { isEnabled, policies ->
VaultSettingsAction.Internal.CredentialExchangeAvailabilityChanged(
isEnabled = isEnabled &&
policies.isEmpty() &&
gmsManager.isVersionAtLeast(MINIMUM_CXP_GMS_VERSION),
!buildInfoManager.isFdroid &&
policies.isEmpty(),
)
}
.onEach(::sendAction)
@@ -143,9 +143,9 @@ class VaultSettingsViewModel @Inject constructor(
}
private fun handleImportItemsClicked() {
if (featureFlagManager.getFeatureFlag(FlagKey.CredentialExchangeProtocolImport) &&
policyManager.getActivePolicies(PolicyTypeJson.PERSONAL_OWNERSHIP).isEmpty() &&
gmsManager.isVersionAtLeast(MINIMUM_CXP_GMS_VERSION)
if (!buildInfoManager.isFdroid &&
featureFlagManager.getFeatureFlag(FlagKey.CredentialExchangeProtocolImport) &&
policyManager.getActivePolicies(PolicyTypeJson.PERSONAL_OWNERSHIP).isEmpty()
) {
sendEvent(VaultSettingsEvent.NavigateToImportItems)
} else {

View File

@@ -4,6 +4,7 @@ import android.os.Parcelable
import androidx.annotation.DrawableRes
import androidx.compose.ui.graphics.Color
import androidx.lifecycle.viewModelScope
import com.bitwarden.core.data.manager.BuildInfoManager
import com.bitwarden.core.data.manager.model.FlagKey
import com.bitwarden.core.data.repository.model.DataState
import com.bitwarden.core.util.persistentListOfNotNull
@@ -37,8 +38,6 @@ import com.x8bit.bitwarden.data.billing.manager.PremiumStateManager
import com.x8bit.bitwarden.data.platform.manager.CredentialExchangeRegistryManager
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
import com.x8bit.bitwarden.data.platform.manager.GmsManager
import com.x8bit.bitwarden.data.platform.manager.MINIMUM_CXP_GMS_VERSION
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
import com.x8bit.bitwarden.data.platform.manager.ReviewPromptManager
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
@@ -117,7 +116,7 @@ class VaultViewModel @Inject constructor(
private val networkConnectionManager: NetworkConnectionManager,
private val browserAutofillDialogManager: BrowserAutofillDialogManager,
private val credentialExchangeRegistryManager: CredentialExchangeRegistryManager,
private val gmsManager: GmsManager,
private val buildInfoManager: BuildInfoManager,
featureFlagManager: FeatureFlagManager,
snackbarRelayManager: SnackbarRelayManager<SnackbarRelay>,
) : BaseViewModel<VaultState, VaultEvent, VaultAction>(
@@ -1028,7 +1027,7 @@ class VaultViewModel @Inject constructor(
) {
viewModelScope.launch {
if (action.isCredentialExchangeProtocolExportEnabled &&
gmsManager.isVersionAtLeast(MINIMUM_CXP_GMS_VERSION)
!buildInfoManager.isFdroid
) {
credentialExchangeRegistryManager.register()
} else {

View File

@@ -1,15 +0,0 @@
package com.x8bit.bitwarden.data.platform.manager
import android.content.Context
import com.google.android.gms.common.GoogleApiAvailabilityLight
/**
* Primary implementation of [GmsManager].
*/
class GmsManagerImpl(
private val context: Context,
) : GmsManager {
override fun isVersionAtLeast(version: Int): Boolean =
GoogleApiAvailabilityLight.getInstance().getApkVersion(context) >= version
}

View File

@@ -1,6 +1,8 @@
package com.x8bit.bitwarden.data.platform.manager
import androidx.credentials.providerevents.exception.ClearExportUnknownErrorException
import androidx.credentials.providerevents.exception.RegisterExportUnknownErrorException
import androidx.credentials.providerevents.transfer.ClearExportResponse
import androidx.credentials.providerevents.transfer.CredentialTypes
import androidx.credentials.providerevents.transfer.RegisterExportResponse
import com.bitwarden.core.data.util.asFailure
@@ -28,7 +30,7 @@ class CredentialExchangeRegistryManagerImplTest {
private val credentialExchangeRegistry: CredentialExchangeRegistry = mockk {
coEvery { register(any()) } returns RegisterExportResponse().asSuccess()
coEvery { unregister() } returns RegisterExportResponse().asSuccess()
coEvery { unregister() } returns ClearExportResponse(isDeleted = true).asSuccess()
}
private val settingsDiskSource: SettingsDiskSource = mockk {
every { getAppRegisteredForExport() } returns false
@@ -109,7 +111,7 @@ class CredentialExchangeRegistryManagerImplTest {
fun `unregister should return Failure when unregistration fails`() = runTest {
coEvery {
credentialExchangeRegistry.unregister()
} returns RegisterExportUnknownErrorException().asFailure()
} returns ClearExportUnknownErrorException().asFailure()
val result = registryManager.unregister()

View File

@@ -1,57 +0,0 @@
package com.x8bit.bitwarden.data.platform.manager
import android.content.Context
import com.google.android.gms.common.GoogleApiAvailabilityLight
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class GmsManagerTest {
private val context: Context = mockk()
private val mockGoogleApiAvailabilityLight: GoogleApiAvailabilityLight = mockk()
private val gmsManager = GmsManagerImpl(context = context)
@BeforeEach
fun setUp() {
mockkStatic(GoogleApiAvailabilityLight::class)
every {
GoogleApiAvailabilityLight.getInstance()
} returns mockGoogleApiAvailabilityLight
}
@AfterEach
fun tearDown() {
unmockkStatic(GoogleApiAvailabilityLight::class)
}
@Test
fun `isVersionAtLeast should return true when installed version equals required version`() {
every { mockGoogleApiAvailabilityLight.getApkVersion(context) } returns 261031035
assertTrue(gmsManager.isVersionAtLeast(261031035))
}
@Test
fun `isVersionAtLeast should return true when installed version exceeds required version`() {
every { mockGoogleApiAvailabilityLight.getApkVersion(context) } returns 261031036
assertTrue(gmsManager.isVersionAtLeast(261031035))
}
@Test
fun `isVersionAtLeast should return false when installed version is below required version`() {
every { mockGoogleApiAvailabilityLight.getApkVersion(context) } returns 261031034
assertFalse(gmsManager.isVersionAtLeast(261031035))
}
@Test
fun `isVersionAtLeast should return false when GMS is not installed`() {
every { mockGoogleApiAvailabilityLight.getApkVersion(context) } returns 0
assertFalse(gmsManager.isVersionAtLeast(261031035))
}
}

View File

@@ -1,6 +1,7 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.vault
import app.cash.turbine.test
import com.bitwarden.core.data.manager.BuildInfoManager
import com.bitwarden.core.data.manager.model.FlagKey
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.network.model.PolicyTypeJson
@@ -11,7 +12,6 @@ import com.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
import com.x8bit.bitwarden.data.platform.manager.GmsManager
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
@@ -29,6 +29,9 @@ import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
class VaultSettingsViewModelTest : BaseViewModelTest() {
private val buildInfoManager = mockk<BuildInfoManager> {
every { isFdroid } returns false
}
private val mutableFirstTimeStateFlow = MutableStateFlow(DEFAULT_FIRST_TIME_STATE)
private val firstTimeActionManager = mockk<FirstTimeActionManager> {
every { currentOrDefaultUserFirstTimeState } returns DEFAULT_FIRST_TIME_STATE
@@ -42,9 +45,6 @@ class VaultSettingsViewModelTest : BaseViewModelTest() {
getFeatureFlagFlow(FlagKey.CredentialExchangeProtocolImport)
} returns mutableFeatureFlagFlow
}
private val gmsManager = mockk<GmsManager> {
every { isVersionAtLeast(any()) } returns true
}
private val mutablePoliciesFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Policy>>()
private val policyManager = mockk<PolicyManager> {
every { getActivePolicies(any()) } returns emptyList()
@@ -224,10 +224,12 @@ class VaultSettingsViewModelTest : BaseViewModelTest() {
)
}
@Suppress("MaxLineLength")
@Test
fun `showImportItemsChevron should be false when GMS version is insufficient`() {
every { gmsManager.isVersionAtLeast(any()) } returns false
fun `showImportItemsChevron should be false when feature flag is enabled but policy exists`() {
val viewModel = createViewModel()
mutableFeatureFlagFlow.tryEmit(true)
mutablePoliciesFlow.tryEmit(listOf(mockk()))
assertEquals(
VaultSettingsState(showImportActionCard = true, showImportItemsChevron = false),
viewModel.stateFlow.value,
@@ -235,9 +237,22 @@ class VaultSettingsViewModelTest : BaseViewModelTest() {
}
@Test
fun `ImportItemsClick should emit NavigateToImportVault when GMS version is insufficient`() =
fun `showImportItemsChevron should be false when isFdroid is true`() {
every { buildInfoManager.isFdroid } returns true
val viewModel = createViewModel()
assertEquals(
VaultSettingsState(
showImportActionCard = true,
showImportItemsChevron = false,
),
viewModel.stateFlow.value,
)
}
@Test
fun `ImportItemsClick should emit NavigateToImportVault when isFdroid is true`() =
runTest {
every { gmsManager.isVersionAtLeast(any()) } returns false
every { buildInfoManager.isFdroid } returns true
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(VaultSettingsAction.ImportItemsClick)
@@ -248,23 +263,11 @@ class VaultSettingsViewModelTest : BaseViewModelTest() {
}
}
@Suppress("MaxLineLength")
@Test
fun `showImportItemsChevron should be false when feature flag and GMS sufficient but policy exists`() {
val viewModel = createViewModel()
mutableFeatureFlagFlow.tryEmit(true)
mutablePoliciesFlow.tryEmit(listOf(mockk()))
assertEquals(
VaultSettingsState(showImportActionCard = true, showImportItemsChevron = false),
viewModel.stateFlow.value,
)
}
private fun createViewModel(): VaultSettingsViewModel = VaultSettingsViewModel(
buildInfoManager = buildInfoManager,
firstTimeActionManager = firstTimeActionManager,
snackbarRelayManager = snackbarRelayManager,
featureFlagManager = featureFlagManager,
gmsManager = gmsManager,
policyManager = policyManager,
)
}

View File

@@ -1,6 +1,7 @@
package com.x8bit.bitwarden.ui.vault.feature.vault
import app.cash.turbine.test
import com.bitwarden.core.data.manager.BuildInfoManager
import com.bitwarden.core.data.manager.model.FlagKey
import com.bitwarden.core.data.repository.model.DataState
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
@@ -34,7 +35,6 @@ import com.x8bit.bitwarden.data.billing.manager.PremiumStateManager
import com.x8bit.bitwarden.data.platform.manager.CredentialExchangeRegistryManager
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
import com.x8bit.bitwarden.data.platform.manager.GmsManager
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
import com.x8bit.bitwarden.data.platform.manager.ReviewPromptManager
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
@@ -213,13 +213,14 @@ class VaultViewModelTest : BaseViewModelTest() {
every { delayDialog() } just runs
}
private val buildInfoManager = mockk<BuildInfoManager> {
every { isFdroid } returns false
}
private val credentialExchangeRegistryManager: CredentialExchangeRegistryManager = mockk {
coEvery { register() } returns RegisterExportResult.Success
coEvery { unregister() } returns UnregisterExportResult.Success
}
private val gmsManager: GmsManager = mockk {
every { isVersionAtLeast(any()) } returns true
}
private val mutableCxpExportFeatureFlagFlow = MutableStateFlow(false)
private val mutableArchiveItemsFlagFlow = MutableStateFlow(true)
private val featureFlagManager: FeatureFlagManager = mockk {
@@ -3798,10 +3799,10 @@ class VaultViewModelTest : BaseViewModelTest() {
@Suppress("MaxLineLength")
@Test
fun `CredentialExchangeProtocolExportFlagUpdateReceive should unregister when flag is enabled but GMS version is insufficient`() =
fun `CredentialExchangeProtocolExportFlagUpdateReceive should unregister when flag is enabled but isFdroid is true`() =
runTest {
every { buildInfoManager.isFdroid } returns true
mutableCxpExportFeatureFlagFlow.value = false
every { gmsManager.isVersionAtLeast(any()) } returns false
coEvery { credentialExchangeRegistryManager.unregister() } just awaits
val viewModel = createViewModel()
@@ -3815,26 +3816,8 @@ class VaultViewModelTest : BaseViewModelTest() {
coVerify {
credentialExchangeRegistryManager.unregister()
}
}
@Suppress("MaxLineLength")
@Test
fun `CredentialExchangeProtocolExportFlagUpdateReceive should unregister when flag is disabled and GMS version is sufficient`() =
runTest {
mutableCxpExportFeatureFlagFlow.value = true
every { settingsRepository.isAppRegisteredForExport() } returns true
coEvery { credentialExchangeRegistryManager.unregister() } just awaits
val viewModel = createViewModel()
viewModel.trySendAction(
VaultAction.Internal.CredentialExchangeProtocolExportFlagUpdateReceive(
isCredentialExchangeProtocolExportEnabled = false,
),
)
coVerify {
credentialExchangeRegistryManager.unregister()
coVerify(exactly = 0) {
credentialExchangeRegistryManager.register()
}
}
@@ -3856,7 +3839,7 @@ class VaultViewModelTest : BaseViewModelTest() {
networkConnectionManager = networkConnectionManager,
browserAutofillDialogManager = browserAutofillDialogManager,
credentialExchangeRegistryManager = credentialExchangeRegistryManager,
gmsManager = gmsManager,
buildInfoManager = buildInfoManager,
featureFlagManager = featureFlagManager,
)
}

View File

@@ -1,5 +1,6 @@
package com.bitwarden.cxf.registry
import androidx.credentials.providerevents.transfer.ClearExportResponse
import androidx.credentials.providerevents.transfer.RegisterExportResponse
import com.bitwarden.cxf.registry.model.RegistrationRequest
@@ -15,7 +16,7 @@ interface CredentialExchangeRegistry {
*
* @param registrationRequest The request to register as a credential provider.
*
* @return A [Result] indicating if the application was add to the registry. [Result.isSuccess]
* @return A [Result] indicating if the application was added to the registry. [Result.isSuccess]
* does not indicate if the application was added to the registry. Use the result value to check
* if the application was added or not. [Result.isFailure] only indicates if an error occurred.
*/
@@ -27,7 +28,8 @@ interface CredentialExchangeRegistry {
* By unregistering as a credential provider, the application will no longer be presented as an
* option to users when they initiate the Import process from another credential manager.
*
* @return True if the unregistration was successful, false otherwise.
* @return A [Result] containing a [ClearExportResponse] indicating if the export entries were
* cleared. [Result.isFailure] only indicates if an error occurred during the clear operation.
*/
suspend fun unregister(): Result<RegisterExportResponse>
suspend fun unregister(): Result<ClearExportResponse>
}

View File

@@ -4,14 +4,16 @@ import android.app.Application
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmapOrNull
import androidx.credentials.providerevents.ProviderEventsManager
import androidx.credentials.providerevents.exception.ClearExportException
import androidx.credentials.providerevents.exception.RegisterExportException
import androidx.credentials.providerevents.transfer.ClearExportRequest
import androidx.credentials.providerevents.transfer.ClearExportResponse
import androidx.credentials.providerevents.transfer.ExportEntry
import androidx.credentials.providerevents.transfer.RegisterExportRequest
import androidx.credentials.providerevents.transfer.RegisterExportResponse
import com.bitwarden.annotation.OmitFromCoverage
import com.bitwarden.core.data.util.asFailure
import com.bitwarden.core.data.util.asSuccess
import com.bitwarden.cxf.R
import com.bitwarden.cxf.registry.model.RegistrationRequest
import timber.log.Timber
import java.util.UUID
@@ -26,20 +28,6 @@ internal class CredentialExchangeRegistryImpl(
private val providerEventsManager: ProviderEventsManager =
ProviderEventsManager.create(application)
/**
* This is the default wasm binary provided by Google that runs the logic of deciding whether
* the registered exporter can support the incoming import request.
*
* See https://github.com/danjkim/identity-samples/tree/main/CredentialProvider/credential_exchange_matcher
* for source code and documentation.
*/
private val exportMatcher: ByteArray by lazy {
application
.resources
.openRawResource(R.raw.export_matcher)
.use { it.readBytes() }
}
override suspend fun register(
registrationRequest: RegistrationRequest,
): Result<RegisterExportResponse> {
@@ -52,17 +40,19 @@ internal class CredentialExchangeRegistryImpl(
?: return IllegalArgumentException("Icon drawable must not be null.")
.asFailure()
val request = RegisterExportRequest(
val request = RegisterExportRequest.create(
context = application,
entries = listOf(
ExportEntry(
id = UUID.randomUUID().toString(),
accountDisplayName = null,
userDisplayName = application.getString(registrationRequest.appNameRes),
userDisplayName = application.getString(
registrationRequest.appNameRes,
),
icon = icon,
supportedCredentialTypes = registrationRequest.credentialTypes,
),
),
exportMatcher = exportMatcher,
)
return try {
providerEventsManager
@@ -74,18 +64,12 @@ internal class CredentialExchangeRegistryImpl(
}
}
override suspend fun unregister(): Result<RegisterExportResponse> =
override suspend fun unregister(): Result<ClearExportResponse> =
try {
providerEventsManager.registerExport(
// This is a workaround for unregistering an account since an explicit "unregister"
// API is not currently available.
request = RegisterExportRequest(
entries = emptyList(),
exportMatcher = byteArrayOf(),
),
)
providerEventsManager
.clearExport(request = ClearExportRequest())
.asSuccess()
} catch (e: RegisterExportException) {
} catch (e: ClearExportException) {
Timber.e(e, "Failed to unregister application for export.")
e.asFailure()
}

View File

@@ -22,7 +22,7 @@ androidxCamera = "1.6.0"
androidxComposeBom = "2026.03.01"
androidxCore = "1.18.0"
androidxCredentials = "1.6.0-rc02"
androidxCredentialsProviderEvents = "1.0.0-alpha05"
androidxCredentialsProviderEvents = "1.0.0-alpha06"
androidxHiltNavigationCompose = "1.3.0"
androidxLifecycle = "2.10.0"
androidxNavigation = "2.9.7"