parent
9d8914693a
commit
cbdf6a2d3c
@ -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,
|
||||
)
|
@ -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,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,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)
|
||||
)
|
@ -1,32 +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.model.data
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
/**
|
||||
* External data layer representation of an NiA episode
|
||||
*/
|
||||
data class Episode(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val publishDate: Instant,
|
||||
val alternateVideo: String?,
|
||||
val alternateAudio: String?,
|
||||
val newsResources: List<NewsResource>,
|
||||
val authors: List<Author>
|
||||
)
|
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(),
|
||||
)
|
Loading…
Reference in new issue