Skip to content

Commit

Permalink
Merge pull request EventFahrplan#638 from EventFahrplan/about-screen-…
Browse files Browse the repository at this point in the history
…unidirectional-data-flow

Let AppRepository be the single source of truth for "About" screen.
  • Loading branch information
johnjohndoe authored Apr 25, 2024
2 parents 303c841 + abbe289 commit 04b65c5
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ class AboutViewModel(

init {
launch {
val meta = repository.readMeta()
mutableAboutParameter.value = aboutParameterFactory.createAboutParameter(meta)
repository.meta.collect { meta ->
mutableAboutParameter.value = aboutParameterFactory.createAboutParameter(meta)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package nerd.tuxmobil.fahrplan.congress.repositories

import android.content.Context
import android.net.Uri
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import info.metadude.android.eventfahrplan.commons.extensions.onFailure
import info.metadude.android.eventfahrplan.commons.logging.Logging
Expand All @@ -23,7 +24,6 @@ import info.metadude.android.eventfahrplan.engelsystem.EngelsystemNetworkReposit
import info.metadude.android.eventfahrplan.engelsystem.RealEngelsystemNetworkRepository
import info.metadude.android.eventfahrplan.engelsystem.models.ShiftsResult
import info.metadude.android.eventfahrplan.network.models.HttpHeader
import info.metadude.android.eventfahrplan.network.models.Meta
import info.metadude.android.eventfahrplan.network.repositories.RealScheduleNetworkRepository
import info.metadude.android.eventfahrplan.network.repositories.ScheduleNetworkRepository
import info.metadude.kotlin.library.engelsystem.models.Shift
Expand Down Expand Up @@ -81,6 +81,8 @@ import nerd.tuxmobil.fahrplan.congress.serialization.ScheduleChanges.Companion.c
import nerd.tuxmobil.fahrplan.congress.utils.AlarmToneConversion
import nerd.tuxmobil.fahrplan.congress.validation.MetaValidation.validate
import okhttp3.OkHttpClient
import info.metadude.android.eventfahrplan.network.models.Meta as MetaNetworkModel
import nerd.tuxmobil.fahrplan.congress.models.Meta as MetaAppModel

object AppRepository {

Expand Down Expand Up @@ -123,6 +125,27 @@ object AppRepository {
*/
val loadScheduleState: Flow<LoadScheduleState> = mutableLoadScheduleState

private val refreshMetaSignal = MutableSharedFlow<Unit>()

private fun refreshMeta() {
logging.d(LOG_TAG, "Refreshing meta ...")
val requestIdentifier = "refreshMeta"
parentJobs[requestIdentifier] = databaseScope.launchNamed(requestIdentifier) {
refreshMetaSignal.emit(Unit)
}
}

/**
* Emits meta from the database.
*/
@OptIn(ExperimentalCoroutinesApi::class)
val meta: Flow<MetaAppModel> by lazy {
refreshMetaSignal
.onStart { emit(Unit) }
.mapLatest { readMeta() }
.flowOn(executionContext.database)
}

private val refreshStarredSessionsSignal = MutableSharedFlow<Unit>()

private fun refreshStarredSessions() {
Expand Down Expand Up @@ -376,7 +399,7 @@ object AppRepository {

private fun parseSchedule(scheduleXml: String,
httpHeader: HttpHeader,
oldMeta: Meta,
oldMeta: MetaNetworkModel,
onParsingDone: (parseScheduleResult: ParseResult) -> Unit,
onLoadingShiftsDone: (loadShiftsResult: LoadShiftsResult) -> Unit) {
scheduleNetworkRepository.parseSchedule(scheduleXml, httpHeader,
Expand Down Expand Up @@ -795,17 +818,19 @@ object AppRepository {
metaDatabaseRepository.query().toMetaAppModel()

/**
* Updates the [Meta] information in the database.
* Updates the `Meta` information in the database.
*
* The [Meta.httpHeader] properties should only be written if a
* The [MetaNetworkModel.httpHeader] properties should only be written if a
* network response is received with a status code of HTTP 200 (OK).
*
* See also: [HttpStatus.HTTP_OK]
*/
private fun updateMeta(meta: Meta) {
@VisibleForTesting
fun updateMeta(meta: MetaNetworkModel) {
val metaDatabaseModel = meta.toMetaDatabaseModel()
val values = metaDatabaseModel.toContentValues()
metaDatabaseRepository.insert(values)
refreshMeta()
}

fun readScheduleRefreshIntervalDefaultValue() =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import info.metadude.android.eventfahrplan.commons.testing.MainDispatcherTestExtension
import info.metadude.android.eventfahrplan.commons.testing.verifyInvokedOnce
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import nerd.tuxmobil.fahrplan.congress.TestExecutionContext
import nerd.tuxmobil.fahrplan.congress.about.AboutViewEvent.OnPostalAddressClick
Expand Down Expand Up @@ -49,8 +50,9 @@ class AboutViewModelTest {
aboutParameterFactory = aboutParameterFactory,
)

private fun createRepository() = mock<AppRepository> {
on { readMeta() } doReturn Meta()
private fun createRepository(metaValue: Meta = Meta()) = mock<AppRepository> {
on { meta } doReturn flowOf(metaValue)
on { readMeta() } doReturn metaValue
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package nerd.tuxmobil.fahrplan.congress.repositories

import android.content.ContentValues
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import info.metadude.android.eventfahrplan.commons.testing.MainDispatcherTestExtension
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.MetasTable.Columns.ETAG
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.MetasTable.Columns.NUM_DAYS
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.MetasTable.Columns.SCHEDULE_LAST_MODIFIED
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.MetasTable.Columns.SUBTITLE
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.MetasTable.Columns.TIME_ZONE_NAME
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.MetasTable.Columns.TITLE
import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.MetasTable.Columns.VERSION
import info.metadude.android.eventfahrplan.database.repositories.MetaDatabaseRepository
import kotlinx.coroutines.test.runTest
import nerd.tuxmobil.fahrplan.congress.TestExecutionContext
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.kotlin.mock
import org.threeten.bp.ZoneId
import info.metadude.android.eventfahrplan.database.models.HttpHeader as HttpHeaderDatabaseModel
import info.metadude.android.eventfahrplan.database.models.Meta as MetaDatabaseModel
import info.metadude.android.eventfahrplan.network.models.HttpHeader as HttpHeaderNetworkModel
import info.metadude.android.eventfahrplan.network.models.Meta as MetaNetworkModel
import nerd.tuxmobil.fahrplan.congress.models.HttpHeader as HttpHeaderAppModel
import nerd.tuxmobil.fahrplan.congress.models.Meta as MetaAppModel

/**
* Covers [AppRepository.meta] and [AppRepository.updateMeta].
*/
@ExtendWith(MainDispatcherTestExtension::class)
class AppRepositoryMetaTest {

private val metaDatabaseRepository = InMemoryMetaDatabaseRepository()

private val testableAppRepository: AppRepository
get() = with(AppRepository) {
initialize(
context = mock(),
logging = mock(),
executionContext = TestExecutionContext,
databaseScope = mock(),
networkScope = mock(),
okHttpClient = mock(),
alarmsDatabaseRepository = mock(),
highlightsDatabaseRepository = mock(),
sessionsDatabaseRepository = mock(),
metaDatabaseRepository = metaDatabaseRepository,
scheduleNetworkRepository = mock(),
engelsystemNetworkRepository = mock(),
sharedPreferencesRepository = mock(),
sessionsTransformer = mock()
)
return this
}

@Test
fun `meta emits default Meta model`() = runTest {
val expected = MetaAppModel(version = "0.0.0")
testableAppRepository.meta.test {
val actual = awaitItem()
assertThat(actual).isEqualTo(expected)
}
}

@Test
fun `meta emits empty Meta model`() = runTest {
testableAppRepository.updateMeta(MetaNetworkModel())
val expected = MetaAppModel()
testableAppRepository.meta.test {
val actual = awaitItem()
assertThat(actual).isEqualTo(expected)
}
}

@Test
fun `meta emits custom Meta model`() = runTest {
testableAppRepository.updateMeta(MetaNetworkModel(
numDays = 4,
version = "1.2.3",
timeZoneName = "Europe/Berlin",
title = "37C3",
subtitle = "Unlocked",
httpHeader = HttpHeaderNetworkModel(eTag = "abc", lastModified = "9000"),
))
val expected = MetaAppModel(
numDays = 4,
version = "1.2.3",
timeZoneId = ZoneId.of("Europe/Berlin"),
title = "37C3",
subtitle = "Unlocked",
httpHeader = HttpHeaderAppModel(eTag = "abc", lastModified = "9000")
)
testableAppRepository.meta.test {
val actual = awaitItem()
assertThat(actual).isEqualTo(expected)
}
}

}

private class InMemoryMetaDatabaseRepository : MetaDatabaseRepository {

private var meta = MetaDatabaseModel(version = "0.0.0")

override fun insert(values: ContentValues): Long {
meta = values.toMeta()
return 0
}

override fun query(): MetaDatabaseModel {
return meta
}

}

private fun ContentValues.toMeta() = MetaDatabaseModel(
numDays = get(NUM_DAYS) as Int,
version = get(VERSION) as String,
timeZoneName = get(TIME_ZONE_NAME) as String?,
title = get(TITLE) as String,
subtitle = get(SUBTITLE) as String,
httpHeader = HttpHeaderDatabaseModel(
eTag = get(ETAG) as String,
lastModified = get(SCHEDULE_LAST_MODIFIED) as String,
),
)

0 comments on commit 04b65c5

Please sign in to comment.