Repository: corona-warn-app/cwa-app-android
File: Corona-Warn-App/src/main/java/de/rki/coronawarnapp/familytest/core/repository/FamilyTestRepository.kt (Kotlin)

Code before:
    private val storage: FamilyTestStorage,
    private val timeStamper: TimeStamper,
    private val familyTestNotificationService: FamilyTestNotificationService
) {

    val familyTests: Flow<Set<FamilyCoronaTest>> = storage.familyTestMap.map {
        it.values.toSet()
    }

    val familyTestsInRecycleBin: Flow<Set<FamilyCoronaTest>> = storage.familyTestRecycleBinMap.map {
        it.values.toSet()
    }

    suspend fun registerTest(
        qrCode: CoronaTestQRCode,
        personName: String
    ): FamilyCoronaTest {
        return FamilyCoronaTest(
            personName = personName,
            coronaTest = processor.register(qrCode)
        ).also { test ->
            storage.save(test)
        }
    }

    suspend fun refresh(): Set<Error> = coroutineScope {
        val pollResults = familyTests.first().filterNot {
            it.coronaTest.isPollingStopped()
        }.map { originalTest ->
            async { processor.pollServer(originalTest) }
        }.awaitAll()

        pollResults.filterIsInstance<Success>()
            .filter { it.hasUpdate }
            .map { it.updated }
            .also { updates ->
                storage.updateAll(updates)
                notifyIfNeeded()
            }

        pollResults.filterIsInstance<Error>().toSet()
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    internal suspend fun notifyIfNeeded() = familyTests.first().filter {
        it.hasResultChangeBadge && !it.isResultAvailableNotificationSent
    }.map {
        it.identifier
    }.toSet().ifNotEmptyDo { identifiers ->
        Timber.tag(TAG).d("Notifying about [%s] family test result changes", identifiers.size)
        familyTestNotificationService.showTestResultNotification()

Review comment (performance):
This is dangerous because we are losing thread-safety. While the server is polling the tests, the test could be changed and updated in the database by other operations. Then when the polling is finished, the tests still have the old, cached state and will override the state in the database..

Code after:
    private val timeStamper: TimeStamper,
    private val familyTestNotificationService: FamilyTestNotificationService
) {

    val familyTests: Flow<Set<FamilyCoronaTest>> = storage.familyTestMap.map {
        it.values.toSet()
    }

    val familyTestsInRecycleBin: Flow<Set<FamilyCoronaTest>> = storage.familyTestRecycleBinMap.map {
        it.values.toSet()
    }

    val familyTestsToRefresh: Flow<Set<FamilyCoronaTest>> = familyTests.map {
        it.filterNot {
            it.coronaTest.isPollingStopped()
        }.toSet()
    }

    suspend fun registerTest(
        qrCode: CoronaTestQRCode,
        personName: String
    ): FamilyCoronaTest {
        return FamilyCoronaTest(
            personName = personName,
            coronaTest = processor.register(qrCode)
        ).also { test ->
            storage.save(test)
        }
    }

    suspend fun refresh(): Map<TestIdentifier, Exception> {
        val exceptions = mutableMapOf<TestIdentifier, Exception>()
        val updates = mutableListOf<Pair<TestIdentifier, (FamilyCoronaTest) -> FamilyCoronaTest>>()
        familyTestsToRefresh.first().forEach { originalTest ->
            when (val updateResult = processor.pollServer(originalTest.coronaTest)) {
                is CoronaTestResultUpdate ->
                    updates.add(
                        Pair(
                            originalTest.identifier
                        ) { test ->
                            test.updateTestResult(
                                updateResult.coronaTestResult
                            ).let { updated ->
                                updateResult.labId?.let { labId ->
                                    updated.updateLabId(labId)
                                } ?: updated
                            }.let { updated ->
                                updateResult.sampleCollectedAt?.let { collectedAt ->
                                    updated.updateSampleCollectedAt(collectedAt)
                                } ?: updated
                            }