Merge pull request #299 from android/refactor/remove_episodes

Remove episodes related code
pull/307/head
Milosz Moczkowski 2 years ago committed by GitHub
commit f914d6a412
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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,
)

@ -17,7 +17,6 @@
package com.google.samples.apps.nowinandroid.core.data.model
import com.google.samples.apps.nowinandroid.core.database.model.AuthorEntity
import com.google.samples.apps.nowinandroid.core.database.model.EpisodeEntity
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceAuthorCrossRef
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceEntity
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceTopicCrossRef
@ -27,7 +26,6 @@ import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResour
fun NetworkNewsResource.asEntity() = NewsResourceEntity(
id = id,
episodeId = episodeId,
title = title,
content = content,
url = url,
@ -38,7 +36,6 @@ fun NetworkNewsResource.asEntity() = NewsResourceEntity(
fun NetworkNewsResourceExpanded.asEntity() = NewsResourceEntity(
id = id,
episodeId = episodeId,
title = title,
content = content,
url = url,
@ -47,18 +44,6 @@ fun NetworkNewsResourceExpanded.asEntity() = NewsResourceEntity(
type = type,
)
/**
* A shell [EpisodeEntity] to fulfill the foreign key constraint when inserting
* a [NewsResourceEntity] into the DB
*/
fun NetworkNewsResource.episodeEntityShell() = EpisodeEntity(
id = episodeId,
name = "",
publishDate = publishDate,
alternateVideo = null,
alternateAudio = null,
)
/**
* A shell [AuthorEntity] to fulfill the foreign key constraint when inserting
* a [NewsResourceEntity] into the DB

@ -21,15 +21,12 @@ import com.google.samples.apps.nowinandroid.core.data.changeListSync
import com.google.samples.apps.nowinandroid.core.data.model.asEntity
import com.google.samples.apps.nowinandroid.core.data.model.authorCrossReferences
import com.google.samples.apps.nowinandroid.core.data.model.authorEntityShells
import com.google.samples.apps.nowinandroid.core.data.model.episodeEntityShell
import com.google.samples.apps.nowinandroid.core.data.model.topicCrossReferences
import com.google.samples.apps.nowinandroid.core.data.model.topicEntityShells
import com.google.samples.apps.nowinandroid.core.database.dao.AuthorDao
import com.google.samples.apps.nowinandroid.core.database.dao.EpisodeDao
import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceDao
import com.google.samples.apps.nowinandroid.core.database.dao.TopicDao
import com.google.samples.apps.nowinandroid.core.database.model.AuthorEntity
import com.google.samples.apps.nowinandroid.core.database.model.EpisodeEntity
import com.google.samples.apps.nowinandroid.core.database.model.PopulatedNewsResource
import com.google.samples.apps.nowinandroid.core.database.model.TopicEntity
import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel
@ -47,7 +44,6 @@ import kotlinx.coroutines.flow.map
*/
class OfflineFirstNewsRepository @Inject constructor(
private val newsResourceDao: NewsResourceDao,
private val episodeDao: EpisodeDao,
private val authorDao: AuthorDao,
private val topicDao: TopicDao,
private val network: NiaNetworkDataSource,
@ -93,11 +89,6 @@ class OfflineFirstNewsRepository @Inject constructor(
.flatten()
.distinctBy(AuthorEntity::id)
)
episodeDao.insertOrIgnoreEpisodes(
episodeEntities = networkNewsResources
.map(NetworkNewsResource::episodeEntityShell)
.distinctBy(EpisodeEntity::id)
)
newsResourceDao.upsertNewsResources(
newsResourceEntities = networkNewsResources
.map(NetworkNewsResource::asEntity)

@ -18,8 +18,6 @@ package com.google.samples.apps.nowinandroid.core.data.model
import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Article
import com.google.samples.apps.nowinandroid.core.network.model.NetworkAuthor
import com.google.samples.apps.nowinandroid.core.network.model.NetworkEpisode
import com.google.samples.apps.nowinandroid.core.network.model.NetworkEpisodeExpanded
import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource
import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResourceExpanded
import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic
@ -71,7 +69,6 @@ class NetworkEntityKtTest {
val networkModel =
NetworkNewsResource(
id = "0",
episodeId = "2",
title = "title",
content = "content",
url = "url",
@ -82,7 +79,6 @@ class NetworkEntityKtTest {
val entity = networkModel.asEntity()
assertEquals("0", entity.id)
assertEquals("2", entity.episodeId)
assertEquals("title", entity.title)
assertEquals("content", entity.content)
assertEquals("url", entity.url)
@ -93,7 +89,6 @@ class NetworkEntityKtTest {
val expandedNetworkModel =
NetworkNewsResourceExpanded(
id = "0",
episodeId = "2",
title = "title",
content = "content",
url = "url",
@ -105,7 +100,6 @@ class NetworkEntityKtTest {
val entityFromExpanded = expandedNetworkModel.asEntity()
assertEquals("0", entityFromExpanded.id)
assertEquals("2", entityFromExpanded.episodeId)
assertEquals("title", entityFromExpanded.title)
assertEquals("content", entityFromExpanded.content)
assertEquals("url", entityFromExpanded.url)
@ -113,39 +107,4 @@ class NetworkEntityKtTest {
assertEquals(Instant.fromEpochMilliseconds(1), entityFromExpanded.publishDate)
assertEquals(Article, entityFromExpanded.type)
}
@Test
fun network_episode_can_be_mapped_to_episode_entity() {
val networkModel = NetworkEpisode(
id = "0",
name = "name",
publishDate = Instant.fromEpochMilliseconds(1),
alternateVideo = "alternateVideo",
alternateAudio = "alternateAudio",
)
val entity = networkModel.asEntity()
assertEquals("0", entity.id)
assertEquals("name", entity.name)
assertEquals("alternateVideo", entity.alternateVideo)
assertEquals("alternateAudio", entity.alternateAudio)
assertEquals(Instant.fromEpochMilliseconds(1), entity.publishDate)
val expandedNetworkModel =
NetworkEpisodeExpanded(
id = "0",
name = "name",
publishDate = Instant.fromEpochMilliseconds(1),
alternateVideo = "alternateVideo",
alternateAudio = "alternateAudio",
)
val entityFromExpanded = expandedNetworkModel.asEntity()
assertEquals("0", entityFromExpanded.id)
assertEquals("name", entityFromExpanded.name)
assertEquals("alternateVideo", entityFromExpanded.alternateVideo)
assertEquals("alternateAudio", entityFromExpanded.alternateAudio)
assertEquals(Instant.fromEpochMilliseconds(1), entityFromExpanded.publishDate)
}
}

@ -20,21 +20,17 @@ import com.google.samples.apps.nowinandroid.core.data.Synchronizer
import com.google.samples.apps.nowinandroid.core.data.model.asEntity
import com.google.samples.apps.nowinandroid.core.data.model.authorCrossReferences
import com.google.samples.apps.nowinandroid.core.data.model.authorEntityShells
import com.google.samples.apps.nowinandroid.core.data.model.episodeEntityShell
import com.google.samples.apps.nowinandroid.core.data.model.topicCrossReferences
import com.google.samples.apps.nowinandroid.core.data.model.topicEntityShells
import com.google.samples.apps.nowinandroid.core.data.testdoubles.CollectionType
import com.google.samples.apps.nowinandroid.core.data.testdoubles.TestAuthorDao
import com.google.samples.apps.nowinandroid.core.data.testdoubles.TestEpisodeDao
import com.google.samples.apps.nowinandroid.core.data.testdoubles.TestNewsResourceDao
import com.google.samples.apps.nowinandroid.core.data.testdoubles.TestNiaNetworkDataSource
import com.google.samples.apps.nowinandroid.core.data.testdoubles.TestTopicDao
import com.google.samples.apps.nowinandroid.core.data.testdoubles.filteredInterestsIds
import com.google.samples.apps.nowinandroid.core.data.testdoubles.nonPresentInterestsIds
import com.google.samples.apps.nowinandroid.core.database.model.AuthorEntity
import com.google.samples.apps.nowinandroid.core.database.model.EpisodeEntity
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceEntity
import com.google.samples.apps.nowinandroid.core.database.model.PopulatedEpisode
import com.google.samples.apps.nowinandroid.core.database.model.PopulatedNewsResource
import com.google.samples.apps.nowinandroid.core.database.model.TopicEntity
import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel
@ -57,8 +53,6 @@ class OfflineFirstNewsRepositoryTest {
private lateinit var newsResourceDao: TestNewsResourceDao
private lateinit var episodeDao: TestEpisodeDao
private lateinit var authorDao: TestAuthorDao
private lateinit var topicDao: TestTopicDao
@ -73,7 +67,6 @@ class OfflineFirstNewsRepositoryTest {
@Before
fun setup() {
newsResourceDao = TestNewsResourceDao()
episodeDao = TestEpisodeDao()
authorDao = TestAuthorDao()
topicDao = TestTopicDao()
network = TestNiaNetworkDataSource()
@ -85,7 +78,6 @@ class OfflineFirstNewsRepositoryTest {
subject = OfflineFirstNewsRepository(
newsResourceDao = newsResourceDao,
episodeDao = episodeDao,
authorDao = authorDao,
topicDao = topicDao,
network = network,
@ -287,21 +279,6 @@ class OfflineFirstNewsRepositoryTest {
)
}
@Test
fun offlineFirstNewsRepository_sync_saves_shell_episode_entities() =
runTest {
subject.syncWith(synchronizer)
assertEquals(
network.getNewsResources()
.map(NetworkNewsResource::episodeEntityShell)
.distinctBy(EpisodeEntity::id),
episodeDao.getEpisodesStream()
.first()
.map(PopulatedEpisode::entity)
)
}
@Test
fun offlineFirstNewsRepository_sync_saves_topic_cross_references() =
runTest {

@ -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(),
)

@ -18,7 +18,6 @@ package com.google.samples.apps.nowinandroid.core.data.testdoubles
import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceDao
import com.google.samples.apps.nowinandroid.core.database.model.AuthorEntity
import com.google.samples.apps.nowinandroid.core.database.model.EpisodeEntity
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceAuthorCrossRef
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceEntity
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceTopicCrossRef
@ -43,7 +42,6 @@ class TestNewsResourceDao : NewsResourceDao {
listOf(
NewsResourceEntity(
id = "1",
episodeId = "0",
title = "news",
content = "Hilt",
url = "url",
@ -109,16 +107,9 @@ class TestNewsResourceDao : NewsResourceDao {
private fun NewsResourceEntity.asPopulatedNewsResource() = PopulatedNewsResource(
entity = this,
episode = EpisodeEntity(
id = this.episodeId,
name = "episode 4",
publishDate = Instant.fromEpochMilliseconds(2),
alternateAudio = "audio",
alternateVideo = "video",
),
authors = listOf(
AuthorEntity(
id = this.episodeId,
id = "id",
name = "name",
imageUrl = "imageUrl",
twitter = "twitter",

@ -28,7 +28,6 @@ import kotlinx.serialization.json.Json
enum class CollectionType {
Topics,
Authors,
Episodes,
NewsResources
}
@ -53,7 +52,6 @@ class TestNiaNetworkDataSource : NiaNetworkDataSource {
.mapToChangeList(idGetter = NetworkTopic::id),
CollectionType.Authors to allAuthors
.mapToChangeList(idGetter = NetworkAuthor::id),
CollectionType.Episodes to listOf(),
CollectionType.NewsResources to allNewsResources
.mapToChangeList(idGetter = NetworkNewsResource::id),
)

@ -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
)
}
}

@ -30,7 +30,6 @@ class PopulatedNewsResourceKtTest {
val populatedNewsResource = PopulatedNewsResource(
entity = NewsResourceEntity(
id = "1",
episodeId = "0",
title = "news",
content = "Hilt",
url = "url",
@ -38,13 +37,6 @@ class PopulatedNewsResourceKtTest {
type = Video,
publishDate = Instant.fromEpochMilliseconds(1),
),
episode = EpisodeEntity(
id = "4",
name = "episode 4",
publishDate = Instant.fromEpochMilliseconds(2),
alternateAudio = "audio",
alternateVideo = "video",
),
authors = listOf(
AuthorEntity(
id = "2",
@ -71,7 +63,6 @@ class PopulatedNewsResourceKtTest {
assertEquals(
NewsResource(
id = "1",
episodeId = "0",
title = "news",
content = "Hilt",
url = "url",

@ -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')"
]
}
}

@ -21,7 +21,6 @@ import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import com.google.samples.apps.nowinandroid.core.database.NiaDatabase
import com.google.samples.apps.nowinandroid.core.database.model.AuthorEntity
import com.google.samples.apps.nowinandroid.core.database.model.EpisodeEntity
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceAuthorCrossRef
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceEntity
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceTopicCrossRef
@ -38,7 +37,6 @@ import org.junit.Test
class NewsResourceDaoTest {
private lateinit var newsResourceDao: NewsResourceDao
private lateinit var episodeDao: EpisodeDao
private lateinit var topicDao: TopicDao
private lateinit var authorDao: AuthorDao
private lateinit var db: NiaDatabase
@ -51,7 +49,6 @@ class NewsResourceDaoTest {
NiaDatabase::class.java
).build()
newsResourceDao = db.newsResourceDao()
episodeDao = db.episodeDao()
topicDao = db.topicDao()
authorDao = db.authorDao()
}
@ -76,13 +73,6 @@ class NewsResourceDaoTest {
millisSinceEpoch = 2,
),
)
val episodeEntityShells = newsResourceEntities
.map(NewsResourceEntity::episodeEntityShell)
.distinct()
episodeDao.insertOrIgnoreEpisodes(
episodeEntityShells
)
newsResourceDao.upsertNewsResources(
newsResourceEntities
)
@ -128,9 +118,6 @@ class NewsResourceDaoTest {
millisSinceEpoch = 2,
),
)
val episodeEntityShells = newsResourceEntities
.map(NewsResourceEntity::episodeEntityShell)
.distinct()
val newsResourceTopicCrossRefEntities = topicEntities.mapIndexed { index, topicEntity ->
NewsResourceTopicCrossRef(
newsResourceId = index.toString(),
@ -141,9 +128,6 @@ class NewsResourceDaoTest {
topicDao.insertOrIgnoreTopics(
topicEntities = topicEntities
)
episodeDao.insertOrIgnoreEpisodes(
episodeEntities = episodeEntityShells
)
newsResourceDao.upsertNewsResources(
newsResourceEntities
)
@ -193,9 +177,6 @@ class NewsResourceDaoTest {
millisSinceEpoch = 2,
),
)
val episodeEntityShells = newsResourceEntities
.map(NewsResourceEntity::episodeEntityShell)
.distinct()
val newsResourceAuthorCrossRefEntities = authorEntities.mapIndexed { index, authorEntity ->
NewsResourceAuthorCrossRef(
newsResourceId = index.toString(),
@ -204,7 +185,6 @@ class NewsResourceDaoTest {
}
authorDao.upsertAuthors(authorEntities)
episodeDao.upsertEpisodes(episodeEntityShells)
newsResourceDao.upsertNewsResources(newsResourceEntities)
newsResourceDao.insertOrIgnoreAuthorCrossRefEntities(newsResourceAuthorCrossRefEntities)
@ -266,9 +246,7 @@ class NewsResourceDaoTest {
millisSinceEpoch = 10,
),
)
val episodeEntityShells = newsResourceEntities
.map(NewsResourceEntity::episodeEntityShell)
.distinct()
val newsResourceTopicCrossRefEntities = topicEntities.mapIndexed { index, topicEntity ->
NewsResourceTopicCrossRef(
newsResourceId = index.toString(),
@ -286,7 +264,6 @@ class NewsResourceDaoTest {
topicDao.upsertTopics(topicEntities)
authorDao.upsertAuthors(authorEntities)
episodeDao.upsertEpisodes(episodeEntityShells)
newsResourceDao.upsertNewsResources(newsResourceEntities)
newsResourceDao.insertOrIgnoreTopicCrossRefEntities(newsResourceTopicCrossRefEntities)
newsResourceDao.insertOrIgnoreAuthorCrossRefEntities(newsResourceAuthorCrossRefEntities)
@ -327,11 +304,6 @@ class NewsResourceDaoTest {
millisSinceEpoch = 2,
),
)
val episodeEntityShells = newsResourceEntities
.map(NewsResourceEntity::episodeEntityShell)
.distinct()
episodeDao.upsertEpisodes(episodeEntityShells)
newsResourceDao.upsertNewsResources(newsResourceEntities)
val (toDelete, toKeep) = newsResourceEntities.partition { it.id.toInt() % 2 == 0 }
@ -379,7 +351,6 @@ private fun testNewsResource(
millisSinceEpoch: Long = 0
) = NewsResourceEntity(
id = id,
episodeId = "0",
title = "",
content = "",
url = "",
@ -387,11 +358,3 @@ private fun testNewsResource(
publishDate = Instant.fromEpochMilliseconds(millisSinceEpoch),
type = NewsResourceType.DAC,
)
private fun NewsResourceEntity.episodeEntityShell() = EpisodeEntity(
id = episodeId,
name = "",
publishDate = Instant.fromEpochMilliseconds(0),
alternateVideo = null,
alternateAudio = null,
)

@ -17,7 +17,6 @@
package com.google.samples.apps.nowinandroid.core.database
import com.google.samples.apps.nowinandroid.core.database.dao.AuthorDao
import com.google.samples.apps.nowinandroid.core.database.dao.EpisodeDao
import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceDao
import com.google.samples.apps.nowinandroid.core.database.dao.TopicDao
import dagger.Module
@ -38,11 +37,6 @@ object DaosModule {
database: NiaDatabase,
): TopicDao = database.topicDao()
@Provides
fun providesEpisodeDao(
database: NiaDatabase,
): EpisodeDao = database.episodeDao()
@Provides
fun providesNewsResourceDao(
database: NiaDatabase,

@ -16,6 +16,8 @@
package com.google.samples.apps.nowinandroid.core.database
import androidx.room.DeleteColumn
import androidx.room.DeleteTable
import androidx.room.RenameColumn
import androidx.room.migration.AutoMigrationSpec
@ -26,7 +28,7 @@ import androidx.room.migration.AutoMigrationSpec
* from and Y is the schema version you're migrating to. The class should implement
* `AutoMigrationSpec`.
*/
class DatabaseMigrations {
object DatabaseMigrations {
@RenameColumn(
tableName = "topics",
@ -34,4 +36,18 @@ class DatabaseMigrations {
toColumnName = "shortDescription"
)
class Schema2to3 : AutoMigrationSpec
@DeleteColumn(
tableName = "news_resources",
columnName = "episode_id"
)
@DeleteTable.Entries(
DeleteTable(
tableName = "episodes_authors"
),
DeleteTable(
tableName = "episodes"
)
)
class Schema10to11 : AutoMigrationSpec
}

@ -21,12 +21,9 @@ import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.google.samples.apps.nowinandroid.core.database.dao.AuthorDao
import com.google.samples.apps.nowinandroid.core.database.dao.EpisodeDao
import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceDao
import com.google.samples.apps.nowinandroid.core.database.dao.TopicDao
import com.google.samples.apps.nowinandroid.core.database.model.AuthorEntity
import com.google.samples.apps.nowinandroid.core.database.model.EpisodeAuthorCrossRef
import com.google.samples.apps.nowinandroid.core.database.model.EpisodeEntity
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceAuthorCrossRef
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceEntity
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceTopicCrossRef
@ -37,14 +34,12 @@ import com.google.samples.apps.nowinandroid.core.database.util.NewsResourceTypeC
@Database(
entities = [
AuthorEntity::class,
EpisodeAuthorCrossRef::class,
EpisodeEntity::class,
NewsResourceAuthorCrossRef::class,
NewsResourceEntity::class,
NewsResourceTopicCrossRef::class,
TopicEntity::class,
],
version = 10,
version = 11,
autoMigrations = [
AutoMigration(from = 1, to = 2),
AutoMigration(from = 2, to = 3, spec = DatabaseMigrations.Schema2to3::class),
@ -55,6 +50,7 @@ import com.google.samples.apps.nowinandroid.core.database.util.NewsResourceTypeC
AutoMigration(from = 7, to = 8),
AutoMigration(from = 8, to = 9),
AutoMigration(from = 9, to = 10),
AutoMigration(from = 10, to = 11, spec = DatabaseMigrations.Schema10to11::class)
],
exportSchema = true,
)
@ -65,6 +61,5 @@ import com.google.samples.apps.nowinandroid.core.database.util.NewsResourceTypeC
abstract class NiaDatabase : RoomDatabase() {
abstract fun topicDao(): TopicDao
abstract fun authorDao(): AuthorDao
abstract fun episodeDao(): EpisodeDao
abstract fun newsResourceDao(): NewsResourceDao
}

@ -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>)
}

@ -22,7 +22,7 @@ import androidx.room.PrimaryKey
import com.google.samples.apps.nowinandroid.core.model.data.Author
/**
* Defines an author for either an [EpisodeEntity] or [NewsResourceEntity].
* Defines an author for [NewsResourceEntity].
* It has a many to many relationship with both entities
*/
@Entity(

@ -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?,
)

@ -18,8 +18,6 @@ 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
import androidx.room.PrimaryKey
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType
@ -27,27 +25,13 @@ import kotlinx.datetime.Instant
/**
* Defines an NiA news resource.
* It is the child in a 1 to many relationship with [EpisodeEntity]
*/
@Entity(
tableName = "news_resources",
foreignKeys = [
ForeignKey(
entity = EpisodeEntity::class,
parentColumns = ["id"],
childColumns = ["episode_id"],
onDelete = ForeignKey.CASCADE
),
],
indices = [
Index(value = ["episode_id"])
]
tableName = "news_resources"
)
data class NewsResourceEntity(
@PrimaryKey
val id: String,
@ColumnInfo(name = "episode_id")
val episodeId: String,
val title: String,
val content: String,
val url: String,
@ -60,7 +44,6 @@ data class NewsResourceEntity(
fun NewsResourceEntity.asExternalModel() = NewsResource(
id = id,
episodeId = episodeId,
title = title,
content = content,
url = url,

@ -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)
)

@ -27,11 +27,6 @@ import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
data class PopulatedNewsResource(
@Embedded
val entity: NewsResourceEntity,
@Relation(
parentColumn = "episode_id",
entityColumn = "id"
)
val episode: EpisodeEntity,
@Relation(
parentColumn = "id",
entityColumn = "id",
@ -56,7 +51,6 @@ data class PopulatedNewsResource(
fun PopulatedNewsResource.asExternalModel() = NewsResource(
id = entity.id,
episodeId = entity.episodeId,
title = entity.title,
content = entity.content,
url = entity.url,

@ -22,6 +22,5 @@ package com.google.samples.apps.nowinandroid.core.datastore
data class ChangeListVersions(
val topicVersion: Int = -1,
val authorVersion: Int = -1,
val episodeVersion: Int = -1,
val newsResourceVersion: Int = -1,
)

@ -116,7 +116,6 @@ class NiaPreferencesDataSource @Inject constructor(
ChangeListVersions(
topicVersion = it.topicChangeListVersion,
authorVersion = it.authorChangeListVersion,
episodeVersion = it.episodeChangeListVersion,
newsResourceVersion = it.newsResourceChangeListVersion,
)
}
@ -132,7 +131,6 @@ class NiaPreferencesDataSource @Inject constructor(
ChangeListVersions(
topicVersion = currentPreferences.topicChangeListVersion,
authorVersion = currentPreferences.authorChangeListVersion,
episodeVersion = currentPreferences.episodeChangeListVersion,
newsResourceVersion = currentPreferences.newsResourceChangeListVersion
)
)
@ -140,7 +138,6 @@ class NiaPreferencesDataSource @Inject constructor(
currentPreferences.copy {
topicChangeListVersion = updatedChangeListVersions.topicVersion
authorChangeListVersion = updatedChangeListVersions.authorVersion
episodeChangeListVersion = updatedChangeListVersions.episodeVersion
newsResourceChangeListVersion = updatedChangeListVersions.newsResourceVersion
}
}

@ -24,7 +24,6 @@ message UserPreferences {
repeated int32 deprecated_int_followed_topic_ids = 1;
int32 topicChangeListVersion = 3;
int32 authorChangeListVersion = 4;
int32 episodeChangeListVersion = 5;
int32 newsResourceChangeListVersion = 6;
repeated int32 deprecated_int_followed_author_ids = 7;
bool has_done_int_to_string_id_migration = 8;

@ -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>
)

@ -30,7 +30,6 @@ import kotlinx.datetime.toInstant
*/
data class NewsResource(
val id: String,
val episodeId: String,
val title: String,
val content: String,
val url: String,
@ -44,7 +43,6 @@ data class NewsResource(
val previewNewsResources = listOf(
NewsResource(
id = "1",
episodeId = "60",
title = "Android Basics with Compose",
content = "We released the first two units of Android Basics with Compose, our first free course that teaches Android Development with Jetpack Compose to anyone; you do not need any prior programming experience other than basic computer literacy to get started. Youll learn the fundamentals of programming in Kotlin while building Android apps using Jetpack Compose, Androids modern toolkit that simplifies and accelerates native UI development. These two units are just the beginning; more will be coming soon. Check out Android Basics with Compose to get started on your Android development journey",
url = "https://android-developers.googleblog.com/2022/05/new-android-basics-with-compose-course.html",
@ -64,7 +62,6 @@ val previewNewsResources = listOf(
),
NewsResource(
id = "2",
episodeId = "52",
title = "Thanks for helping us reach 1M YouTube Subscribers",
content = "Thank you everyone for following the Now in Android series and everything the " +
"Android Developers YouTube channel has to offer. During the Android Developer " +
@ -79,7 +76,6 @@ val previewNewsResources = listOf(
),
NewsResource(
id = "3",
episodeId = "52",
title = "Transformations and customisations in the Paging Library",
content = "A demonstration of different operations that can be performed " +
"with Paging. Transformations like inserting separators, when to " +

@ -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(),
)

@ -29,7 +29,6 @@ import kotlinx.serialization.Serializable
@Serializable
data class NetworkNewsResource(
val id: String,
val episodeId: String,
val title: String,
val content: String,
val url: String,
@ -48,7 +47,6 @@ data class NetworkNewsResource(
@Serializable
data class NetworkNewsResourceExpanded(
val id: String,
val episodeId: String,
val title: String,
val content: String,
val url: String,

@ -73,7 +73,7 @@ import kotlinx.datetime.Instant
import kotlinx.datetime.toJavaInstant
/**
* [NewsResource] card used on the following screens: For You, Episodes, Saved
* [NewsResource] card used on the following screens: For You, Saved
*/
@OptIn(ExperimentalMaterial3Api::class)

@ -186,7 +186,6 @@ private val testAuthors = listOf(
private val sampleNewsResources = listOf(
NewsResource(
id = "1",
episodeId = "52",
title = "Thanks for helping us reach 1M YouTube Subscribers",
content = "Thank you everyone for following the Now in Android series and everything the " +
"Android Developers YouTube channel has to offer. During the Android Developer " +

@ -284,7 +284,6 @@ private val testOutputAuthors = listOf(
private val sampleNewsResources = listOf(
NewsResource(
id = "1",
episodeId = "52",
title = "Thanks for helping us reach 1M YouTube Subscribers",
content = "Thank you everyone for following the Now in Android series and everything the " +
"Android Developers YouTube channel has to offer. During the Android Developer " +

@ -16,7 +16,6 @@
-->
<resources>
<string name="for_you">For you</string>
<string name="episodes">Episodes</string>
<string name="done">Done</string>
<string name="for_you_loading">Loading for you…</string>
<string name="navigate_up">Navigate up</string>

@ -1428,7 +1428,6 @@ private val sampleTopics = listOf(
private val sampleNewsResources = listOf(
NewsResource(
id = "1",
episodeId = "52",
title = "Thanks for helping us reach 1M YouTube Subscribers",
content = "Thank you everyone for following the Now in Android series and everything the " +
"Android Developers YouTube channel has to offer. During the Android Developer " +
@ -1461,7 +1460,6 @@ private val sampleNewsResources = listOf(
),
NewsResource(
id = "2",
episodeId = "52",
title = "Transformations and customisations in the Paging Library",
content = "A demonstration of different operations that can be performed with Paging. " +
"Transformations like inserting separators, when to create a new pager, and " +
@ -1493,7 +1491,6 @@ private val sampleNewsResources = listOf(
),
NewsResource(
id = "3",
episodeId = "52",
title = "Community tip on Paging",
content = "Tips for using the Paging library from the developer community",
url = "https://youtu.be/r5JgIyS3t3s",

@ -171,7 +171,6 @@ private val testTopics = listOf(
private val sampleNewsResources = listOf(
NewsResource(
id = "1",
episodeId = "52",
title = "Thanks for helping us reach 1M YouTube Subscribers",
content = "Thank you everyone for following the Now in Android series and everything the " +
"Android Developers YouTube channel has to offer. During the Android Developer " +

@ -233,7 +233,6 @@ private val testOutputTopics = listOf(
private val sampleNewsResources = listOf(
NewsResource(
id = "1",
episodeId = "52",
title = "Thanks for helping us reach 1M YouTube Subscribers",
content = "Thank you everyone for following the Now in Android series and everything the " +
"Android Developers YouTube channel has to offer. During the Android Developer " +

Loading…
Cancel
Save