Add AppForegroundManager (#599)

This commit is contained in:
Brian Yencho 2024-01-13 11:15:06 -06:00 committed by Álison Fernandes
parent 70308c84be
commit 3d75867a15
8 changed files with 168 additions and 0 deletions

View file

@ -77,6 +77,11 @@ The following is a list of all third-party dependencies included as part of the
- Purpose: Display and capture images for barcode scanning.
- License: Apache 2.0
- **AndroidX Lifecycle**
- https://developer.android.com/jetpack/androidx/releases/lifecycle
- Purpose: Lifecycle aware components and tooling.
- License: Apache 2.0
- **AndroidX Security**
- https://developer.android.com/jetpack/androidx/releases/security
- Purpose: Safely manage keys and encrypt files and sharedpreferences.

View file

@ -162,6 +162,7 @@ dependencies {
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.lifecycle.process)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.navigation.compose)

View file

@ -0,0 +1,15 @@
package com.x8bit.bitwarden.data.platform.manager
import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState
import kotlinx.coroutines.flow.StateFlow
/**
* A manager for tracking app foreground state changes.
*/
interface AppForegroundManager {
/**
* Emits whenever there are changes to the app foreground state.
*/
val appForegroundStateFlow: StateFlow<AppForegroundState>
}

View file

@ -0,0 +1,36 @@
package com.x8bit.bitwarden.data.platform.manager
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/**
* Primary implementation of [AppForegroundManager].
*/
class AppForegroundManagerImpl(
processLifecycleOwner: LifecycleOwner = ProcessLifecycleOwner.get(),
) : AppForegroundManager {
private val mutableAppForegroundStateFlow =
MutableStateFlow(AppForegroundState.BACKGROUNDED)
override val appForegroundStateFlow: StateFlow<AppForegroundState>
get() = mutableAppForegroundStateFlow.asStateFlow()
init {
processLifecycleOwner.lifecycle.addObserver(
object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
mutableAppForegroundStateFlow.value = AppForegroundState.FOREGROUNDED
}
override fun onStop(owner: LifecycleOwner) {
mutableAppForegroundStateFlow.value = AppForegroundState.BACKGROUNDED
}
},
)
}
}

View file

@ -5,6 +5,8 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.platform.datasource.network.authenticator.RefreshAuthenticator
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptors
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManagerImpl
import com.x8bit.bitwarden.data.platform.manager.NetworkConfigManager
import com.x8bit.bitwarden.data.platform.manager.NetworkConfigManagerImpl
import com.x8bit.bitwarden.data.platform.manager.SdkClientManager
@ -29,6 +31,11 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
object PlatformManagerModule {
@Provides
@Singleton
fun provideAppForegroundManager(): AppForegroundManager =
AppForegroundManagerImpl()
@Provides
@Singleton
fun provideClock(): Clock = Clock.systemDefaultZone()

View file

@ -0,0 +1,16 @@
package com.x8bit.bitwarden.data.platform.manager.model
/**
* Represents the foreground state of the app.
*/
enum class AppForegroundState {
/**
* Denotes that the app is backgrounded.
*/
BACKGROUNDED,
/**
* Denotes that the app is foregrounded.
*/
FOREGROUNDED,
}

View file

@ -0,0 +1,87 @@
package com.x8bit.bitwarden.data.platform.manager
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import app.cash.turbine.test
import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class AppForegroundManagerTest {
private val fakeLifecycleOwner = FakeLifecycleOwner()
private val appForegroundManager = AppForegroundManagerImpl(
processLifecycleOwner = fakeLifecycleOwner,
)
@Suppress("MaxLineLength")
@Test
fun `appForegroundStateFlow should emit whenever the underlying ProcessLifecycleOwner receives start and stop events`() =
runTest {
appForegroundManager.appForegroundStateFlow.test {
// Initial state is BACKGROUNDED
assertEquals(
AppForegroundState.BACKGROUNDED,
awaitItem(),
)
fakeLifecycleOwner.lifecycle.dispatchOnStart()
assertEquals(
AppForegroundState.FOREGROUNDED,
awaitItem(),
)
fakeLifecycleOwner.lifecycle.dispatchOnStop()
assertEquals(
AppForegroundState.BACKGROUNDED,
awaitItem(),
)
}
}
}
private class FakeLifecycle(
private val lifecycleOwner: LifecycleOwner,
) : Lifecycle() {
private val observers = mutableSetOf<DefaultLifecycleObserver>()
override var currentState: State = State.INITIALIZED
override fun addObserver(observer: LifecycleObserver) {
observers += (observer as DefaultLifecycleObserver)
}
override fun removeObserver(observer: LifecycleObserver) {
observers -= (observer as DefaultLifecycleObserver)
}
/**
* Triggers [DefaultLifecycleObserver.onStart] calls for each registered observer.
*/
fun dispatchOnStart() {
currentState = State.STARTED
observers.forEach { observer ->
observer.onStart(lifecycleOwner)
}
}
/**
* Triggers [DefaultLifecycleObserver.onStop] calls for each registered observer.
*/
fun dispatchOnStop() {
currentState = State.CREATED
observers.forEach { observer ->
observer.onStop(lifecycleOwner)
}
}
}
private class FakeLifecycleOwner : LifecycleOwner {
override val lifecycle: FakeLifecycle = FakeLifecycle(this)
}

View file

@ -71,6 +71,7 @@ androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "androidxHiltNavigationCompose" }
androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "androidxLifecycle" }
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidxLifecycle" }
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidxLifecycle" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidxNavigation" }