# HG changeset patch # User Da Risk # Date 1739843912 14400 # Node ID b3dd237df1bdc98e16699779f7d39c2ddce14e29 # Parent 5ad573b6c84dce592f61d9813eab65b66a642c66 geekdroid: add AccountsFlowTest diff -r 5ad573b6c84d -r b3dd237df1bd geekdroid/build.gradle.kts --- a/geekdroid/build.gradle.kts Tue Feb 18 18:08:10 2025 -0400 +++ b/geekdroid/build.gradle.kts Mon Feb 17 21:58:32 2025 -0400 @@ -107,6 +107,9 @@ implementation(libs.androidx.compose.material3) implementation(libs.androidx.activity.compose) + testImplementation(libs.kotlinx.coroutines.test) + testImplementation(libs.turbine) + } diff -r 5ad573b6c84d -r b3dd237df1bd geekdroid/src/main/java/com/geekorum/geekdroid/accounts/AccountsLiveData.kt --- a/geekdroid/src/main/java/com/geekorum/geekdroid/accounts/AccountsLiveData.kt Tue Feb 18 18:08:10 2025 -0400 +++ b/geekdroid/src/main/java/com/geekorum/geekdroid/accounts/AccountsLiveData.kt Mon Feb 17 21:58:32 2025 -0400 @@ -27,10 +27,14 @@ import android.annotation.SuppressLint import android.os.Build import androidx.lifecycle.LiveData +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow /** * Allows to observe the list of [Account] for a specified Account type. */ +@Deprecated("Use AccountManager.accountsFlow()") class AccountsLiveData( private val accountManager: AccountManager, vararg accountType: String? @@ -64,3 +68,43 @@ } } + +/** + * Allows to observe the list of [Account] for a specified Account type. + */ +fun AccountManager.accountsFlow(vararg accountType: String): Flow> = callbackFlow { + val listener = object : OnAccountsUpdateListener { + override fun onAccountsUpdated(accounts: Array?) { + val forTypes = accounts?.filter { it?.type in accountType }?.filterNotNull() ?: emptyList() + trySend(forTypes) + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + addOnAccountsUpdatedListener( + listener, null, true, + accountType + ) + } else { + addOnAccountsUpdatedListener(listener, null, true) + } + + awaitClose { + removeOnAccountsUpdatedListener(listener) + } +} + +/** + * Allows to observe the list of [Account] + */ +fun AccountManager.accountsFlow(): Flow> = callbackFlow { + val listener = object : OnAccountsUpdateListener { + override fun onAccountsUpdated(accounts: Array?) { + trySend(accounts?.filterNotNull() ?: emptyList()) + } + } + addOnAccountsUpdatedListener(listener, null, true) + + awaitClose { + removeOnAccountsUpdatedListener(listener) + } +} \ No newline at end of file diff -r 5ad573b6c84d -r b3dd237df1bd geekdroid/src/test/java/com/geekorum/geekdroid/accounts/AccountsFlowTest.kt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/geekdroid/src/test/java/com/geekorum/geekdroid/accounts/AccountsFlowTest.kt Mon Feb 17 21:58:32 2025 -0400 @@ -0,0 +1,110 @@ +/* + * Geekdroid is a utility library for development on the Android + * Platform. + * + * Copyright (C) 2017-2025 by Frederic-Charles Barthelery. + * + * This file is part of Geekdroid. + * + * Geekdroid is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Geekdroid is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Geekdroid. If not, see . + */ +package com.geekorum.geekdroid.accounts + +import android.accounts.Account +import android.accounts.AccountManager +import android.os.Build +import androidx.test.ext.junit.runners.AndroidJUnit4 +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.runner.RunWith +import org.robolectric.RuntimeEnvironment +import org.robolectric.Shadows +import org.robolectric.annotation.Config +import org.robolectric.shadows.ShadowAccountManager +import kotlin.test.BeforeTest +import kotlin.test.Test + +@RunWith(AndroidJUnit4::class) +@Config(shadows = [com.geekorum.geekdroid.shadows.ShadowAccountManager::class], sdk = [Build.VERSION_CODES.Q]) +class AccountsFlowTest { + + private lateinit var shadowAccountManager: ShadowAccountManager + private lateinit var accountManager: AccountManager + + private val TEST_ACCOUNT_TYPE: String = "test.account" + private val testAccounts = listOf( + Account("test1", TEST_ACCOUNT_TYPE), + Account("test2", TEST_ACCOUNT_TYPE), + Account("test3", TEST_ACCOUNT_TYPE), + ) + private val testAccountMore = Account("test4 more", TEST_ACCOUNT_TYPE) + + + private val OTHER_ACCOUNT_TYPE: String = "other" + private val otherAccount = Account("test1", OTHER_ACCOUNT_TYPE) + private val otherAccount2 = Account("test2", OTHER_ACCOUNT_TYPE) + + @BeforeTest + fun setUp() { + accountManager = AccountManager.get(RuntimeEnvironment.getApplication()) + shadowAccountManager = Shadows.shadowOf(accountManager) + for (account in testAccounts) { + shadowAccountManager.addAccount(account) + } + shadowAccountManager.addAccount(otherAccount) + + } + + @Test + fun testWhenActiveGetTheCorrectData() = runTest { + accountManager.accountsFlow(TEST_ACCOUNT_TYPE) + .test { + assertThat(awaitItem()).isEqualTo(testAccounts) + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun testThatWhenActiveGetCorrectUpdates() = runTest { + accountManager.accountsFlow(TEST_ACCOUNT_TYPE) + .test { + var expected = testAccounts + assertThat(awaitItem()).isEqualTo(expected) + + // add some accounts + shadowAccountManager.addAccount(otherAccount2) + shadowAccountManager.addAccount(testAccountMore) + expected = testAccounts + testAccountMore + + assertThat(awaitItem()).isEqualTo(expected) + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun testThatWhenInactiveGetLaterUpdates() = runTest { + // add some accounts + shadowAccountManager.addAccount(testAccountMore) + shadowAccountManager.addAccount(otherAccount2) + + accountManager.accountsFlow(TEST_ACCOUNT_TYPE) + .test { + var expected = testAccounts + testAccountMore + + assertThat(awaitItem()).isEqualTo(expected) + cancelAndIgnoreRemainingEvents() + } + } +} \ No newline at end of file diff -r 5ad573b6c84d -r b3dd237df1bd gradle/libs.versions.toml --- a/gradle/libs.versions.toml Tue Feb 18 18:08:10 2025 -0400 +++ b/gradle/libs.versions.toml Mon Feb 17 21:58:32 2025 -0400 @@ -22,6 +22,7 @@ recyclerview = "1.4.0" room = "2.6.1" timber = "5.0.1" +turbine = "1.0.0" workmanager = "2.10.0" [plugins] @@ -54,6 +55,7 @@ kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "kotlinx-coroutines-bom" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" } kotlinx-coroutines-play-services = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services" } +kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test" } lifecycle-livedata-core-ktx = { module = "androidx.lifecycle:lifecycle-livedata-core-ktx", version.ref = "lifecycle" } lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycle" } material = { module = "com.google.android.material:material", version.ref = "material" } @@ -64,5 +66,6 @@ recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" } room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } +turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } work-runtime = { module = "androidx.work:work-runtime", version.ref = "workmanager" }