commit
7ca17f68ed
@ -0,0 +1,3 @@
|
|||||||
|
# :app-nia-catalog module
|
||||||
|
|
||||||
|
![Dependency graph](../docs/images/graphs/dep_graph_app_nia_catalog.png)
|
@ -0,0 +1,3 @@
|
|||||||
|
# :app module
|
||||||
|
|
||||||
|
![Dependency graph](../docs/images/graphs/dep_graph_app.png)
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.android.build.api.variant.LibraryAndroidComponentsExtension
|
||||||
|
import com.google.samples.apps.nowinandroid.configureJacoco
|
||||||
|
import org.gradle.api.Plugin
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.artifacts.VersionCatalogsExtension
|
||||||
|
import org.gradle.kotlin.dsl.dependencies
|
||||||
|
import org.gradle.kotlin.dsl.getByType
|
||||||
|
import org.gradle.kotlin.dsl.kotlin
|
||||||
|
|
||||||
|
class AndroidHiltConventionPlugin : Plugin<Project> {
|
||||||
|
override fun apply(target: Project) {
|
||||||
|
with(target) {
|
||||||
|
with(pluginManager) {
|
||||||
|
apply("org.jetbrains.kotlin.kapt")
|
||||||
|
apply("dagger.hilt.android.plugin")
|
||||||
|
}
|
||||||
|
|
||||||
|
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||||
|
dependencies {
|
||||||
|
"implementation"(libs.findLibrary("hilt.android").get())
|
||||||
|
"kapt"(libs.findLibrary("hilt.compiler").get())
|
||||||
|
"kaptAndroidTest"(libs.findLibrary("hilt.compiler").get())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import com.diffplug.gradle.spotless.SpotlessExtension
|
|
||||||
import org.gradle.api.Plugin
|
|
||||||
import org.gradle.api.Project
|
|
||||||
import org.gradle.api.artifacts.VersionCatalogsExtension
|
|
||||||
import org.gradle.kotlin.dsl.configure
|
|
||||||
import org.gradle.kotlin.dsl.getByType
|
|
||||||
|
|
||||||
class SpotlessConventionPlugin : Plugin<Project> {
|
|
||||||
override fun apply(target: Project) {
|
|
||||||
with(target) {
|
|
||||||
pluginManager.apply("com.diffplug.spotless")
|
|
||||||
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
|
|
||||||
|
|
||||||
extensions.configure<SpotlessExtension> {
|
|
||||||
kotlin {
|
|
||||||
target("**/*.kt")
|
|
||||||
targetExclude("**/build/**/*.kt")
|
|
||||||
ktlint(libs.findVersion("ktlint").get().toString()).userData(mapOf("android" to "true"))
|
|
||||||
licenseHeaderFile(rootProject.file("spotless/copyright.kt"))
|
|
||||||
}
|
|
||||||
format("kts") {
|
|
||||||
target("**/*.kts")
|
|
||||||
targetExclude("**/build/**/*.kts")
|
|
||||||
// Look for the first line that doesn't have a block comment (assumed to be the license)
|
|
||||||
licenseHeaderFile(rootProject.file("spotless/copyright.kts"), "(^(?![\\/ ]\\*).*$)")
|
|
||||||
}
|
|
||||||
format("xml") {
|
|
||||||
target("**/*.xml")
|
|
||||||
targetExclude("**/build/**/*.xml")
|
|
||||||
// Look for the first XML tag that isn't a comment (<!--) or the xml declaration (<?xml)
|
|
||||||
licenseHeaderFile(rootProject.file("spotless/copyright.xml"), "(<[^!?])")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.samples.apps.nowinandroid
|
||||||
|
|
||||||
|
import com.android.build.api.artifact.SingleArtifact
|
||||||
|
import com.android.build.api.variant.AndroidComponentsExtension
|
||||||
|
import com.android.build.api.variant.BuiltArtifactsLoader
|
||||||
|
import com.android.build.api.variant.HasAndroidTest
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.file.Directory
|
||||||
|
import org.gradle.api.file.DirectoryProperty
|
||||||
|
import org.gradle.api.provider.ListProperty
|
||||||
|
import org.gradle.api.provider.Property
|
||||||
|
import org.gradle.api.tasks.Input
|
||||||
|
import org.gradle.api.tasks.InputDirectory
|
||||||
|
import org.gradle.api.tasks.InputFiles
|
||||||
|
import org.gradle.api.tasks.Internal
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
internal fun Project.configurePrintApksTask(extension: AndroidComponentsExtension<*, *, *>) {
|
||||||
|
extension.onVariants { variant ->
|
||||||
|
if (variant is HasAndroidTest) {
|
||||||
|
val loader = variant.artifacts.getBuiltArtifactsLoader()
|
||||||
|
val artifact = variant.androidTest?.artifacts?.get(SingleArtifact.APK)
|
||||||
|
val javaSources = variant.androidTest?.sources?.java?.all
|
||||||
|
val kotlinSources = variant.androidTest?.sources?.kotlin?.all
|
||||||
|
|
||||||
|
val testSources = if (javaSources != null && kotlinSources != null) {
|
||||||
|
javaSources.zip(kotlinSources) { javaDirs, kotlinDirs ->
|
||||||
|
javaDirs + kotlinDirs
|
||||||
|
}
|
||||||
|
} else javaSources ?: kotlinSources
|
||||||
|
|
||||||
|
if (artifact != null && testSources != null) {
|
||||||
|
tasks.register(
|
||||||
|
"${variant.name}PrintTestApk",
|
||||||
|
PrintApkLocationTask::class.java
|
||||||
|
) {
|
||||||
|
apkFolder.set(artifact)
|
||||||
|
builtArtifactsLoader.set(loader)
|
||||||
|
variantName.set(variant.name)
|
||||||
|
sources.set(testSources)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal abstract class PrintApkLocationTask : DefaultTask() {
|
||||||
|
@get:InputDirectory
|
||||||
|
abstract val apkFolder: DirectoryProperty
|
||||||
|
|
||||||
|
@get:InputFiles
|
||||||
|
abstract val sources: ListProperty<Directory>
|
||||||
|
|
||||||
|
@get:Internal
|
||||||
|
abstract val builtArtifactsLoader: Property<BuiltArtifactsLoader>
|
||||||
|
|
||||||
|
@get:Input
|
||||||
|
abstract val variantName: Property<String>
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
fun taskAction() {
|
||||||
|
val hasFiles = sources.orNull?.any { directory ->
|
||||||
|
directory.asFileTree.files.any {
|
||||||
|
it.isFile && it.parentFile.path.contains("build${File.separator}generated").not()
|
||||||
|
}
|
||||||
|
} ?: throw RuntimeException("Cannot check androidTest sources")
|
||||||
|
|
||||||
|
// Don't print APK location if there are no androidTest source files
|
||||||
|
if (!hasFiles) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val builtArtifacts = builtArtifactsLoader.get().load(apkFolder.get())
|
||||||
|
?: throw RuntimeException("Cannot load APKs")
|
||||||
|
if (builtArtifacts.elements.size != 1)
|
||||||
|
throw RuntimeException("Expected one APK !")
|
||||||
|
val apk = File(builtArtifacts.elements.single().outputFile).toPath()
|
||||||
|
println(apk)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
# :core:common module
|
||||||
|
|
||||||
|
![Dependency graph](../../docs/images/graphs/dep_graph_core_common.png)
|
@ -0,0 +1,3 @@
|
|||||||
|
# :core:data-test module
|
||||||
|
|
||||||
|
![Dependency graph](../../docs/images/graphs/dep_graph_core_data_test.png)
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.samples.apps.nowinandroid.core.data.test
|
||||||
|
|
||||||
|
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
|
||||||
|
class AlwaysOnlineNetworkMonitor @Inject constructor() : NetworkMonitor {
|
||||||
|
override val isOnline: Flow<Boolean> = flowOf(true)
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
# :core:data module
|
||||||
|
|
||||||
|
![Dependency graph](../../docs/images/graphs/dep_graph_core_data.png)
|
@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.samples.apps.nowinandroid.core.data.model
|
|
||||||
|
|
||||||
import com.google.samples.apps.nowinandroid.core.database.model.EpisodeEntity
|
|
||||||
import com.google.samples.apps.nowinandroid.core.network.model.NetworkEpisode
|
|
||||||
import com.google.samples.apps.nowinandroid.core.network.model.NetworkEpisodeExpanded
|
|
||||||
|
|
||||||
fun NetworkEpisode.asEntity() = EpisodeEntity(
|
|
||||||
id = id,
|
|
||||||
name = name,
|
|
||||||
publishDate = publishDate,
|
|
||||||
alternateVideo = alternateVideo,
|
|
||||||
alternateAudio = alternateAudio,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun NetworkEpisodeExpanded.asEntity() = EpisodeEntity(
|
|
||||||
id = id,
|
|
||||||
name = name,
|
|
||||||
publishDate = publishDate,
|
|
||||||
alternateVideo = alternateVideo,
|
|
||||||
alternateAudio = alternateAudio,
|
|
||||||
)
|
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.samples.apps.nowinandroid.core.data.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.ConnectivityManager.NetworkCallback
|
||||||
|
import android.net.Network
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
import android.net.NetworkRequest.Builder
|
||||||
|
import android.os.Build.VERSION
|
||||||
|
import android.os.Build.VERSION_CODES
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.flow.conflate
|
||||||
|
|
||||||
|
class ConnectivityManagerNetworkMonitor @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context
|
||||||
|
) : NetworkMonitor {
|
||||||
|
override val isOnline: Flow<Boolean> = callbackFlow<Boolean> {
|
||||||
|
val callback = object : NetworkCallback() {
|
||||||
|
override fun onAvailable(network: Network) {
|
||||||
|
channel.trySend(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLost(network: Network) {
|
||||||
|
channel.trySend(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val connectivityManager = context.getSystemService<ConnectivityManager>()
|
||||||
|
|
||||||
|
connectivityManager?.registerNetworkCallback(
|
||||||
|
Builder()
|
||||||
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||||
|
.build(),
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
|
||||||
|
channel.trySend(connectivityManager.isCurrentlyConnected())
|
||||||
|
|
||||||
|
awaitClose {
|
||||||
|
connectivityManager?.unregisterNetworkCallback(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.conflate()
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
private fun ConnectivityManager?.isCurrentlyConnected() = when (this) {
|
||||||
|
null -> false
|
||||||
|
else -> when {
|
||||||
|
VERSION.SDK_INT >= VERSION_CODES.M ->
|
||||||
|
activeNetwork
|
||||||
|
?.let(::getNetworkCapabilities)
|
||||||
|
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||||
|
?: false
|
||||||
|
else -> activeNetworkInfo?.isConnected ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.samples.apps.nowinandroid.core.data.util
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reports on if synchronization is in progress
|
||||||
|
*/
|
||||||
|
interface SyncStatusMonitor {
|
||||||
|
val isSyncing: Flow<Boolean>
|
||||||
|
}
|
@ -1,72 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.samples.apps.nowinandroid.core.data.testdoubles
|
|
||||||
|
|
||||||
import com.google.samples.apps.nowinandroid.core.database.dao.EpisodeDao
|
|
||||||
import com.google.samples.apps.nowinandroid.core.database.model.EpisodeEntity
|
|
||||||
import com.google.samples.apps.nowinandroid.core.database.model.PopulatedEpisode
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test double for [EpisodeDao]
|
|
||||||
*/
|
|
||||||
class TestEpisodeDao : EpisodeDao {
|
|
||||||
|
|
||||||
private var entitiesStateFlow = MutableStateFlow(
|
|
||||||
listOf(
|
|
||||||
EpisodeEntity(
|
|
||||||
id = "1",
|
|
||||||
name = "Episode",
|
|
||||||
publishDate = Instant.fromEpochMilliseconds(0),
|
|
||||||
alternateVideo = null,
|
|
||||||
alternateAudio = null,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun getEpisodesStream(): Flow<List<PopulatedEpisode>> =
|
|
||||||
entitiesStateFlow.map {
|
|
||||||
it.map(EpisodeEntity::asPopulatedEpisode)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun insertOrIgnoreEpisodes(episodeEntities: List<EpisodeEntity>): List<Long> {
|
|
||||||
entitiesStateFlow.value = episodeEntities
|
|
||||||
// Assume no conflicts on insert
|
|
||||||
return episodeEntities.map { it.id.toLong() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun updateEpisodes(entities: List<EpisodeEntity>) {
|
|
||||||
throw NotImplementedError("Unused in tests")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun deleteEpisodes(ids: List<String>) {
|
|
||||||
val idSet = ids.toSet()
|
|
||||||
entitiesStateFlow.update { entities ->
|
|
||||||
entities.filterNot { idSet.contains(it.id) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun EpisodeEntity.asPopulatedEpisode() = PopulatedEpisode(
|
|
||||||
entity = this,
|
|
||||||
newsResources = emptyList(),
|
|
||||||
authors = emptyList(),
|
|
||||||
)
|
|
@ -1,98 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.samples.apps.nowinandroid.core.database.model
|
|
||||||
|
|
||||||
import com.google.samples.apps.nowinandroid.core.model.data.Author
|
|
||||||
import com.google.samples.apps.nowinandroid.core.model.data.Episode
|
|
||||||
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
|
|
||||||
import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Video
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class PopulatedEpisodeKtTest {
|
|
||||||
@Test
|
|
||||||
fun populated_episode_can_be_mapped_to_episode() {
|
|
||||||
val populatedEpisode = PopulatedEpisode(
|
|
||||||
entity = EpisodeEntity(
|
|
||||||
id = "0",
|
|
||||||
name = "Test",
|
|
||||||
publishDate = Instant.fromEpochMilliseconds(1),
|
|
||||||
alternateAudio = "audio",
|
|
||||||
alternateVideo = "video"
|
|
||||||
),
|
|
||||||
newsResources = listOf(
|
|
||||||
NewsResourceEntity(
|
|
||||||
id = "1",
|
|
||||||
episodeId = "0",
|
|
||||||
title = "news",
|
|
||||||
content = "Hilt",
|
|
||||||
url = "url",
|
|
||||||
headerImageUrl = "headerImageUrl",
|
|
||||||
type = Video,
|
|
||||||
publishDate = Instant.fromEpochMilliseconds(1),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
authors = listOf(
|
|
||||||
AuthorEntity(
|
|
||||||
id = "2",
|
|
||||||
name = "name",
|
|
||||||
imageUrl = "imageUrl",
|
|
||||||
twitter = "twitter",
|
|
||||||
mediumPage = "mediumPage",
|
|
||||||
bio = "bio",
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
val episode = populatedEpisode.asExternalModel()
|
|
||||||
|
|
||||||
assertEquals(
|
|
||||||
Episode(
|
|
||||||
id = "0",
|
|
||||||
name = "Test",
|
|
||||||
publishDate = Instant.fromEpochMilliseconds(1),
|
|
||||||
alternateAudio = "audio",
|
|
||||||
alternateVideo = "video",
|
|
||||||
newsResources = listOf(
|
|
||||||
NewsResource(
|
|
||||||
id = "1",
|
|
||||||
episodeId = "0",
|
|
||||||
title = "news",
|
|
||||||
content = "Hilt",
|
|
||||||
url = "url",
|
|
||||||
headerImageUrl = "headerImageUrl",
|
|
||||||
type = Video,
|
|
||||||
publishDate = Instant.fromEpochMilliseconds(1),
|
|
||||||
authors = listOf(),
|
|
||||||
topics = listOf()
|
|
||||||
)
|
|
||||||
),
|
|
||||||
authors = listOf(
|
|
||||||
Author(
|
|
||||||
id = "2",
|
|
||||||
name = "name",
|
|
||||||
imageUrl = "imageUrl",
|
|
||||||
twitter = "twitter",
|
|
||||||
mediumPage = "mediumPage",
|
|
||||||
bio = "bio",
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
episode
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,3 @@
|
|||||||
|
# :core:database module
|
||||||
|
|
||||||
|
![Dependency graph](../../docs/images/graphs/dep_graph_core_database.png)
|
@ -0,0 +1,314 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 11,
|
||||||
|
"identityHash": "2f83f889f6d8a96243f4ce387adbc604",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "authors",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `image_url` TEXT NOT NULL, `twitter` TEXT NOT NULL DEFAULT '', `medium_page` TEXT NOT NULL DEFAULT '', `bio` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "imageUrl",
|
||||||
|
"columnName": "image_url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "twitter",
|
||||||
|
"columnName": "twitter",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mediumPage",
|
||||||
|
"columnName": "medium_page",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bio",
|
||||||
|
"columnName": "bio",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "news_resources_authors",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`news_resource_id` TEXT NOT NULL, `author_id` TEXT NOT NULL, PRIMARY KEY(`news_resource_id`, `author_id`), FOREIGN KEY(`news_resource_id`) REFERENCES `news_resources`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`author_id`) REFERENCES `authors`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "newsResourceId",
|
||||||
|
"columnName": "news_resource_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "authorId",
|
||||||
|
"columnName": "author_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"news_resource_id",
|
||||||
|
"author_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_news_resources_authors_news_resource_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"news_resource_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_news_resources_authors_news_resource_id` ON `${TABLE_NAME}` (`news_resource_id`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_news_resources_authors_author_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"author_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_news_resources_authors_author_id` ON `${TABLE_NAME}` (`author_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "news_resources",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"news_resource_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "authors",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"author_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "news_resources",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `url` TEXT NOT NULL, `header_image_url` TEXT, `publish_date` INTEGER NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "content",
|
||||||
|
"columnName": "content",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "url",
|
||||||
|
"columnName": "url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "headerImageUrl",
|
||||||
|
"columnName": "header_image_url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "publishDate",
|
||||||
|
"columnName": "publish_date",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "news_resources_topics",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`news_resource_id` TEXT NOT NULL, `topic_id` TEXT NOT NULL, PRIMARY KEY(`news_resource_id`, `topic_id`), FOREIGN KEY(`news_resource_id`) REFERENCES `news_resources`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`topic_id`) REFERENCES `topics`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "newsResourceId",
|
||||||
|
"columnName": "news_resource_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "topicId",
|
||||||
|
"columnName": "topic_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"news_resource_id",
|
||||||
|
"topic_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_news_resources_topics_news_resource_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"news_resource_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_news_resources_topics_news_resource_id` ON `${TABLE_NAME}` (`news_resource_id`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_news_resources_topics_topic_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"topic_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_news_resources_topics_topic_id` ON `${TABLE_NAME}` (`topic_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "news_resources",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"news_resource_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "topics",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"topic_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "topics",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `shortDescription` TEXT NOT NULL, `longDescription` TEXT NOT NULL DEFAULT '', `url` TEXT NOT NULL DEFAULT '', `imageUrl` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "shortDescription",
|
||||||
|
"columnName": "shortDescription",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "longDescription",
|
||||||
|
"columnName": "longDescription",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "url",
|
||||||
|
"columnName": "url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "imageUrl",
|
||||||
|
"columnName": "imageUrl",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2f83f889f6d8a96243f4ce387adbc604')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.samples.apps.nowinandroid.core.database.dao
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.OnConflictStrategy
|
|
||||||
import androidx.room.Query
|
|
||||||
import androidx.room.Transaction
|
|
||||||
import androidx.room.Update
|
|
||||||
import com.google.samples.apps.nowinandroid.core.database.model.EpisodeEntity
|
|
||||||
import com.google.samples.apps.nowinandroid.core.database.model.PopulatedEpisode
|
|
||||||
import com.google.samples.apps.nowinandroid.core.model.data.Episode
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DAO for [EpisodeEntity] and [Episode] access
|
|
||||||
*/
|
|
||||||
@Dao
|
|
||||||
interface EpisodeDao {
|
|
||||||
@Transaction
|
|
||||||
@Query(value = "SELECT * FROM episodes")
|
|
||||||
fun getEpisodesStream(): Flow<List<PopulatedEpisode>>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts [episodeEntities] into the db if they don't exist, and ignores those that do
|
|
||||||
*/
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
suspend fun insertOrIgnoreEpisodes(episodeEntities: List<EpisodeEntity>): List<Long>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates [entities] in the db that match the primary key, and no-ops if they don't
|
|
||||||
*/
|
|
||||||
@Update
|
|
||||||
suspend fun updateEpisodes(entities: List<EpisodeEntity>)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts or updates [entities] in the db under the specified primary keys
|
|
||||||
*/
|
|
||||||
@Transaction
|
|
||||||
suspend fun upsertEpisodes(entities: List<EpisodeEntity>) = upsert(
|
|
||||||
items = entities,
|
|
||||||
insertMany = ::insertOrIgnoreEpisodes,
|
|
||||||
updateMany = ::updateEpisodes
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes rows in the db matching the specified [ids]
|
|
||||||
*/
|
|
||||||
@Query(
|
|
||||||
value = """
|
|
||||||
DELETE FROM episodes
|
|
||||||
WHERE id in (:ids)
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
suspend fun deleteEpisodes(ids: List<String>)
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.samples.apps.nowinandroid.core.database.dao
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs an upsert by first attempting to insert [items] using [insertMany] with the the result
|
|
||||||
* of the inserts returned.
|
|
||||||
*
|
|
||||||
* Items that were not inserted due to conflicts are then updated using [updateMany]
|
|
||||||
*/
|
|
||||||
suspend fun <T> upsert(
|
|
||||||
items: List<T>,
|
|
||||||
insertMany: suspend (List<T>) -> List<Long>,
|
|
||||||
updateMany: suspend (List<T>) -> Unit,
|
|
||||||
) {
|
|
||||||
val insertResults = insertMany(items)
|
|
||||||
|
|
||||||
val updateList = items.zip(insertResults)
|
|
||||||
.mapNotNull { (item, insertResult) ->
|
|
||||||
if (insertResult == -1L) item else null
|
|
||||||
}
|
|
||||||
if (updateList.isNotEmpty()) updateMany(updateList)
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.samples.apps.nowinandroid.core.database.model
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.Index
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cross reference for many to many relationship between [EpisodeEntity] and [AuthorEntity]
|
|
||||||
*/
|
|
||||||
@Entity(
|
|
||||||
tableName = "episodes_authors",
|
|
||||||
primaryKeys = ["episode_id", "author_id"],
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = EpisodeEntity::class,
|
|
||||||
parentColumns = ["id"],
|
|
||||||
childColumns = ["episode_id"],
|
|
||||||
onDelete = ForeignKey.CASCADE
|
|
||||||
),
|
|
||||||
ForeignKey(
|
|
||||||
entity = AuthorEntity::class,
|
|
||||||
parentColumns = ["id"],
|
|
||||||
childColumns = ["author_id"],
|
|
||||||
onDelete = ForeignKey.CASCADE
|
|
||||||
),
|
|
||||||
],
|
|
||||||
indices = [
|
|
||||||
Index(value = ["episode_id"]),
|
|
||||||
Index(value = ["author_id"]),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
data class EpisodeAuthorCrossRef(
|
|
||||||
@ColumnInfo(name = "episode_id")
|
|
||||||
val episodeId: String,
|
|
||||||
@ColumnInfo(name = "author_id")
|
|
||||||
val authorId: String,
|
|
||||||
)
|
|
@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.samples.apps.nowinandroid.core.database.model
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines an NiA episode.
|
|
||||||
* It is a parent in a 1 to many relationship with [NewsResourceEntity]
|
|
||||||
*/
|
|
||||||
@Entity(
|
|
||||||
tableName = "episodes",
|
|
||||||
)
|
|
||||||
data class EpisodeEntity(
|
|
||||||
@PrimaryKey
|
|
||||||
val id: String,
|
|
||||||
val name: String,
|
|
||||||
@ColumnInfo(name = "publish_date")
|
|
||||||
val publishDate: Instant,
|
|
||||||
@ColumnInfo(name = "alternate_video")
|
|
||||||
val alternateVideo: String?,
|
|
||||||
@ColumnInfo(name = "alternate_audio")
|
|
||||||
val alternateAudio: String?,
|
|
||||||
)
|
|
@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.samples.apps.nowinandroid.core.database.model
|
|
||||||
|
|
||||||
import androidx.room.Embedded
|
|
||||||
import androidx.room.Junction
|
|
||||||
import androidx.room.Relation
|
|
||||||
import com.google.samples.apps.nowinandroid.core.model.data.Episode
|
|
||||||
|
|
||||||
/**
|
|
||||||
* External data layer representation of an NiA episode
|
|
||||||
*/
|
|
||||||
data class PopulatedEpisode(
|
|
||||||
@Embedded
|
|
||||||
val entity: EpisodeEntity,
|
|
||||||
@Relation(
|
|
||||||
parentColumn = "id",
|
|
||||||
entityColumn = "episode_id"
|
|
||||||
)
|
|
||||||
val newsResources: List<NewsResourceEntity>,
|
|
||||||
@Relation(
|
|
||||||
parentColumn = "id",
|
|
||||||
entityColumn = "id",
|
|
||||||
associateBy = Junction(
|
|
||||||
value = EpisodeAuthorCrossRef::class,
|
|
||||||
parentColumn = "episode_id",
|
|
||||||
entityColumn = "author_id",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val authors: List<AuthorEntity>
|
|
||||||
)
|
|
||||||
|
|
||||||
fun PopulatedEpisode.asExternalModel() = Episode(
|
|
||||||
id = entity.id,
|
|
||||||
name = entity.name,
|
|
||||||
publishDate = entity.publishDate,
|
|
||||||
alternateVideo = entity.alternateVideo,
|
|
||||||
alternateAudio = entity.alternateAudio,
|
|
||||||
newsResources = newsResources.map(NewsResourceEntity::asExternalModel),
|
|
||||||
authors = authors.map(AuthorEntity::asExternalModel)
|
|
||||||
)
|
|
@ -0,0 +1,3 @@
|
|||||||
|
# :core:datastore-test module
|
||||||
|
|
||||||
|
![Dependency graph](../../docs/images/graphs/dep_graph_core_datastore_test.png)
|
@ -0,0 +1,3 @@
|
|||||||
|
# :core:datastore module
|
||||||
|
|
||||||
|
![Dependency graph](../../docs/images/graphs/dep_graph_core_datastore.png)
|
@ -0,0 +1,3 @@
|
|||||||
|
# :core:designsystem module
|
||||||
|
|
||||||
|
![Dependency graph](../../docs/images/graphs/dep_graph_core_designsystem.png)
|
@ -0,0 +1,3 @@
|
|||||||
|
# :core:model module
|
||||||
|
|
||||||
|
![Dependency graph](../../docs/images/graphs/dep_graph_core_model.png)
|
@ -0,0 +1,3 @@
|
|||||||
|
# :core:navigation module
|
||||||
|
|
||||||
|
![Dependency graph](../../docs/images/graphs/dep_graph_core_navigation.png)
|
@ -0,0 +1,3 @@
|
|||||||
|
# :core:network module
|
||||||
|
|
||||||
|
![Dependency graph](../../docs/images/graphs/dep_graph_core_network.png)
|
File diff suppressed because it is too large
Load Diff
@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.samples.apps.nowinandroid.core.network.model
|
|
||||||
|
|
||||||
import com.google.samples.apps.nowinandroid.core.model.data.Episode
|
|
||||||
import com.google.samples.apps.nowinandroid.core.network.model.util.InstantSerializer
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Network representation of [Episode] when fetched from /episodes
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
data class NetworkEpisode(
|
|
||||||
val id: String,
|
|
||||||
val name: String,
|
|
||||||
@Serializable(InstantSerializer::class)
|
|
||||||
val publishDate: Instant,
|
|
||||||
val alternateVideo: String?,
|
|
||||||
val alternateAudio: String?,
|
|
||||||
val newsResources: List<String> = listOf(),
|
|
||||||
val authors: List<String> = listOf(),
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Network representation of [Episode] when fetched from /episodes/{id}
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
data class NetworkEpisodeExpanded(
|
|
||||||
val id: String,
|
|
||||||
val name: String,
|
|
||||||
@Serializable(InstantSerializer::class)
|
|
||||||
val publishDate: Instant,
|
|
||||||
val alternateVideo: String,
|
|
||||||
val alternateAudio: String,
|
|
||||||
val newsResources: List<NetworkNewsResource> = listOf(),
|
|
||||||
val authors: List<NetworkAuthor> = listOf(),
|
|
||||||
)
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue