Repository: corona-warn-app/cwa-app-android
File: Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/expiration/DccExpirationChangeObserver.kt (Kotlin)

Code before:
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class DccExpirationChangeObserver @Inject constructor(
    @AppScope private val appScope: CoroutineScope,
    private val certificateProvider: CertificateProvider,
    private val dccExpirationNotificationService: DccExpirationNotificationService
) {

    fun setup() {
        Timber.tag(TAG).d("setup()")

        certificateProvider.certificateContainer
            .onStart { Timber.tag(TAG).d("Started monitoring certs for state changes") }
            .map { certificateContainer ->
                certificateContainer.allCwaCertificates
                    .filterNot { it.getState() is CwaCovidCertificate.State.Valid }
                    .associate { it.uniqueCertificateIdentifier to it.getState() }
            }
            .distinctUntilChanged()
            .drop(1) // Drop initial emission to avoid unnecessary check after app start
            .filter { it.isNotEmpty() }
            .onEach {
                Timber.tag(TAG).d("Expiration changed: %s", it)
                dccExpirationNotificationService.showNotificationIfStateChanged(ignoreLastCheck = true)
            }
            .catch { Timber.tag(TAG).e("Failed to observe certs for state changes") }
            .launchIn(scope = appScope)
    }
}

private val TAG = tag<DccExpirationChangeObserver>()


Review comment (suggestion):
we should actually do it when app comes to foreground see `DccStateCheckScheduler`, maybe we should have it in one place. I suggest we remove it from `DccStateCheckScheduler` and let `DccExpirationChangeObserver` handle state notifications. BUT this observer can run in the background also which is fine as the specs states that notifications should be triggered from background too see https://github.com/corona-warn-app/cwa-app-tech-spec/blob/4fa9158c9c6215630b5b1563a45a55c37122f068/docs/spec/dgc-overview-client.md#creating-local-notifications-for-validity-changes-of-digital-green-certificates

Code after:
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class DccExpirationChangeObserver @Inject constructor(
    @AppScope private val appScope: CoroutineScope,
    private val certificateProvider: CertificateProvider,
    private val dccExpirationNotificationService: DccExpirationNotificationService
) {

    fun setup() {
        Timber.tag(TAG).d("setup()")

        certificateProvider.certificateContainer
            .onStart { Timber.tag(TAG).d("Started monitoring certs for state changes") }
            .mapLatest { certificateContainer ->
                certificateContainer.allCwaCertificates
                    .filterNot { it.getState() is CwaCovidCertificate.State.Valid }
                    .associate { it.uniqueCertificateIdentifier to it.getState() }
            }
            .distinctUntilChanged()
            .filter { it.isNotEmpty() }
            .onEach {
                Timber.tag(TAG).d("Expiration changed: %s", it)
                dccExpirationNotificationService.showNotificationIfStateChanged(ignoreLastCheck = true)
            }
            .catch { Timber.tag(TAG).e("Failed to observe certs for state changes") }
            .launchIn(scope = appScope)
    }
}

private val TAG = tag<DccExpirationChangeObserver>()