Converted model ids from ints to strings

Change-Id: Iad07926e04113bcd2c6d9e06e5e2a4e802a79b80
pull/2/head
Adetunji Dahunsi 3 years ago committed by Don Turner
parent 4e718016da
commit 643b230187

@ -74,7 +74,7 @@ fun NiaNavGraph(
InterestsDestinations.TOPIC_ROUTE, InterestsDestinations.TOPIC_ROUTE,
arguments = listOf( arguments = listOf(
navArgument(TopicDestinationsArgs.TOPIC_ID_ARG) { navArgument(TopicDestinationsArgs.TOPIC_ID_ARG) {
type = NavType.IntType type = NavType.StringType
} }
) )
) { ) {

@ -0,0 +1,438 @@
{
"formatVersion": 1,
"database": {
"version": 8,
"identityHash": "28ce6650a00ef6281cef0045f5539112",
"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 '', 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": "''"
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "episodes_authors",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`episode_id` TEXT NOT NULL, `author_id` TEXT NOT NULL, PRIMARY KEY(`episode_id`, `author_id`), FOREIGN KEY(`episode_id`) REFERENCES `episodes`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`author_id`) REFERENCES `authors`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "episodeId",
"columnName": "episode_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "authorId",
"columnName": "author_id",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"episode_id",
"author_id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_episodes_authors_episode_id",
"unique": false,
"columnNames": [
"episode_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_episodes_authors_episode_id` ON `${TABLE_NAME}` (`episode_id`)"
},
{
"name": "index_episodes_authors_author_id",
"unique": false,
"columnNames": [
"author_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_episodes_authors_author_id` ON `${TABLE_NAME}` (`author_id`)"
}
],
"foreignKeys": [
{
"table": "episodes",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"episode_id"
],
"referencedColumns": [
"id"
]
},
{
"table": "authors",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"author_id"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "episodes",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `publish_date` INTEGER NOT NULL, `alternate_video` TEXT, `alternate_audio` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "publishDate",
"columnName": "publish_date",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "alternateVideo",
"columnName": "alternate_video",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "alternateAudio",
"columnName": "alternate_audio",
"affinity": "TEXT",
"notNull": false
}
],
"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, `episode_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`), FOREIGN KEY(`episode_id`) REFERENCES `episodes`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "episodeId",
"columnName": "episode_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": [
{
"table": "episodes",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"episode_id"
],
"referencedColumns": [
"id"
]
}
]
},
{
"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, '28ce6650a00ef6281cef0045f5539112')"
]
}
}

@ -60,19 +60,19 @@ class NewsResourceDaoTest {
fun newsResourceDao_fetches_items_by_descending_publish_date() = runTest { fun newsResourceDao_fetches_items_by_descending_publish_date() = runTest {
val newsResourceEntities = listOf( val newsResourceEntities = listOf(
testNewsResource( testNewsResource(
id = 0, id = "0",
millisSinceEpoch = 0, millisSinceEpoch = 0,
), ),
testNewsResource( testNewsResource(
id = 1, id = "1",
millisSinceEpoch = 3, millisSinceEpoch = 3,
), ),
testNewsResource( testNewsResource(
id = 2, id = "2",
millisSinceEpoch = 1, millisSinceEpoch = 1,
), ),
testNewsResource( testNewsResource(
id = 3, id = "3",
millisSinceEpoch = 2, millisSinceEpoch = 2,
), ),
) )
@ -102,29 +102,29 @@ class NewsResourceDaoTest {
fun newsResourceDao_filters_items_by_topic_ids_by_descending_publish_date() = runTest { fun newsResourceDao_filters_items_by_topic_ids_by_descending_publish_date() = runTest {
val topicEntities = listOf( val topicEntities = listOf(
testTopicEntity( testTopicEntity(
id = 1, id = "1",
name = "1" name = "1"
), ),
testTopicEntity( testTopicEntity(
id = 2, id = "2",
name = "2" name = "2"
), ),
) )
val newsResourceEntities = listOf( val newsResourceEntities = listOf(
testNewsResource( testNewsResource(
id = 0, id = "0",
millisSinceEpoch = 0, millisSinceEpoch = 0,
), ),
testNewsResource( testNewsResource(
id = 1, id = "1",
millisSinceEpoch = 3, millisSinceEpoch = 3,
), ),
testNewsResource( testNewsResource(
id = 2, id = "2",
millisSinceEpoch = 1, millisSinceEpoch = 1,
), ),
testNewsResource( testNewsResource(
id = 3, id = "3",
millisSinceEpoch = 2, millisSinceEpoch = 2,
), ),
) )
@ -133,7 +133,7 @@ class NewsResourceDaoTest {
.distinct() .distinct()
val newsResourceTopicCrossRefEntities = topicEntities.mapIndexed { index, topicEntity -> val newsResourceTopicCrossRefEntities = topicEntities.mapIndexed { index, topicEntity ->
NewsResourceTopicCrossRef( NewsResourceTopicCrossRef(
newsResourceId = index, newsResourceId = index.toString(),
topicId = topicEntity.id topicId = topicEntity.id
) )
} }
@ -158,7 +158,7 @@ class NewsResourceDaoTest {
).first() ).first()
assertEquals( assertEquals(
listOf(1, 0), listOf("1", "0"),
filteredNewsResources.map { it.entity.id } filteredNewsResources.map { it.entity.id }
) )
} }
@ -167,29 +167,29 @@ class NewsResourceDaoTest {
fun newsResourceDao_filters_items_by_author_ids_by_descending_publish_date() = runTest { fun newsResourceDao_filters_items_by_author_ids_by_descending_publish_date() = runTest {
val authorEntities = listOf( val authorEntities = listOf(
testAuthorEntity( testAuthorEntity(
id = 1, id = "1",
name = "1" name = "1"
), ),
testAuthorEntity( testAuthorEntity(
id = 2, id = "2",
name = "2" name = "2"
), ),
) )
val newsResourceEntities = listOf( val newsResourceEntities = listOf(
testNewsResource( testNewsResource(
id = 0, id = "0",
millisSinceEpoch = 0, millisSinceEpoch = 0,
), ),
testNewsResource( testNewsResource(
id = 1, id = "1",
millisSinceEpoch = 3, millisSinceEpoch = 3,
), ),
testNewsResource( testNewsResource(
id = 2, id = "2",
millisSinceEpoch = 1, millisSinceEpoch = 1,
), ),
testNewsResource( testNewsResource(
id = 3, id = "3",
millisSinceEpoch = 2, millisSinceEpoch = 2,
), ),
) )
@ -198,7 +198,7 @@ class NewsResourceDaoTest {
.distinct() .distinct()
val newsResourceAuthorCrossRefEntities = authorEntities.mapIndexed { index, authorEntity -> val newsResourceAuthorCrossRefEntities = authorEntities.mapIndexed { index, authorEntity ->
NewsResourceAuthorCrossRef( NewsResourceAuthorCrossRef(
newsResourceId = index, newsResourceId = index.toString(),
authorId = authorEntity.id authorId = authorEntity.id
) )
} }
@ -215,7 +215,7 @@ class NewsResourceDaoTest {
).first() ).first()
assertEquals( assertEquals(
listOf(1, 0), listOf("1", "0"),
filteredNewsResources.map { it.entity.id } filteredNewsResources.map { it.entity.id }
) )
} }
@ -225,44 +225,44 @@ class NewsResourceDaoTest {
runTest { runTest {
val topicEntities = listOf( val topicEntities = listOf(
testTopicEntity( testTopicEntity(
id = 1, id = "1",
name = "1" name = "1"
), ),
testTopicEntity( testTopicEntity(
id = 2, id = "2",
name = "2" name = "2"
), ),
) )
val authorEntities = listOf( val authorEntities = listOf(
testAuthorEntity( testAuthorEntity(
id = 1, id = "1",
name = "1" name = "1"
), ),
testAuthorEntity( testAuthorEntity(
id = 2, id = "2",
name = "2" name = "2"
), ),
) )
val newsResourceEntities = listOf( val newsResourceEntities = listOf(
testNewsResource( testNewsResource(
id = 0, id = "0",
millisSinceEpoch = 0, millisSinceEpoch = 0,
), ),
testNewsResource( testNewsResource(
id = 1, id = "1",
millisSinceEpoch = 3, millisSinceEpoch = 3,
), ),
testNewsResource( testNewsResource(
id = 2, id = "2",
millisSinceEpoch = 1, millisSinceEpoch = 1,
), ),
testNewsResource( testNewsResource(
id = 3, id = "3",
millisSinceEpoch = 2, millisSinceEpoch = 2,
), ),
// Should be missing as no topics or authors match it // Should be missing as no topics or authors match it
testNewsResource( testNewsResource(
id = 4, id = "4",
millisSinceEpoch = 10, millisSinceEpoch = 10,
), ),
) )
@ -271,7 +271,7 @@ class NewsResourceDaoTest {
.distinct() .distinct()
val newsResourceTopicCrossRefEntities = topicEntities.mapIndexed { index, topicEntity -> val newsResourceTopicCrossRefEntities = topicEntities.mapIndexed { index, topicEntity ->
NewsResourceTopicCrossRef( NewsResourceTopicCrossRef(
newsResourceId = index, newsResourceId = index.toString(),
topicId = topicEntity.id topicId = topicEntity.id
) )
} }
@ -279,7 +279,7 @@ class NewsResourceDaoTest {
authorEntities.mapIndexed { index, authorEntity -> authorEntities.mapIndexed { index, authorEntity ->
NewsResourceAuthorCrossRef( NewsResourceAuthorCrossRef(
// Offset news resources by two // Offset news resources by two
newsResourceId = index + 2, newsResourceId = (index + 2).toString(),
authorId = authorEntity.id authorId = authorEntity.id
) )
} }
@ -301,7 +301,7 @@ class NewsResourceDaoTest {
).first() ).first()
assertEquals( assertEquals(
listOf(1, 3, 2, 0), listOf("1", "3", "2", "0"),
filteredNewsResources.map { it.entity.id } filteredNewsResources.map { it.entity.id }
) )
} }
@ -311,19 +311,19 @@ class NewsResourceDaoTest {
runTest { runTest {
val newsResourceEntities = listOf( val newsResourceEntities = listOf(
testNewsResource( testNewsResource(
id = 0, id = "0",
millisSinceEpoch = 0, millisSinceEpoch = 0,
), ),
testNewsResource( testNewsResource(
id = 1, id = "1",
millisSinceEpoch = 3, millisSinceEpoch = 3,
), ),
testNewsResource( testNewsResource(
id = 2, id = "2",
millisSinceEpoch = 1, millisSinceEpoch = 1,
), ),
testNewsResource( testNewsResource(
id = 3, id = "3",
millisSinceEpoch = 2, millisSinceEpoch = 2,
), ),
) )
@ -334,7 +334,7 @@ class NewsResourceDaoTest {
episodeDao.upsertEpisodes(episodeEntityShells) episodeDao.upsertEpisodes(episodeEntityShells)
newsResourceDao.upsertNewsResources(newsResourceEntities) newsResourceDao.upsertNewsResources(newsResourceEntities)
val (toDelete, toKeep) = newsResourceEntities.partition { it.id % 2 == 0 } val (toDelete, toKeep) = newsResourceEntities.partition { it.id.toInt() % 2 == 0 }
newsResourceDao.deleteNewsResources( newsResourceDao.deleteNewsResources(
toDelete.map(NewsResourceEntity::id) toDelete.map(NewsResourceEntity::id)
@ -351,7 +351,7 @@ class NewsResourceDaoTest {
} }
private fun testAuthorEntity( private fun testAuthorEntity(
id: Int = 0, id: String = "0",
name: String name: String
) = AuthorEntity( ) = AuthorEntity(
id = id, id = id,
@ -362,7 +362,7 @@ private fun testAuthorEntity(
) )
private fun testTopicEntity( private fun testTopicEntity(
id: Int = 0, id: String = "0",
name: String name: String
) = TopicEntity( ) = TopicEntity(
id = id, id = id,
@ -374,11 +374,11 @@ private fun testTopicEntity(
) )
private fun testNewsResource( private fun testNewsResource(
id: Int = 0, id: String = "0",
millisSinceEpoch: Long = 0 millisSinceEpoch: Long = 0
) = NewsResourceEntity( ) = NewsResourceEntity(
id = id, id = id,
episodeId = 0, episodeId = "0",
title = "", title = "",
content = "", content = "",
url = "", url = "",

@ -44,7 +44,7 @@ import com.google.samples.apps.nowinandroid.core.database.util.NewsResourceTypeC
NewsResourceTopicCrossRef::class, NewsResourceTopicCrossRef::class,
TopicEntity::class, TopicEntity::class,
], ],
version = 7, version = 8,
autoMigrations = [ autoMigrations = [
AutoMigration(from = 1, to = 2), AutoMigration(from = 1, to = 2),
AutoMigration(from = 2, to = 3, spec = DatabaseMigrations.Schema2to3::class), AutoMigration(from = 2, to = 3, spec = DatabaseMigrations.Schema2to3::class),
@ -52,6 +52,7 @@ import com.google.samples.apps.nowinandroid.core.database.util.NewsResourceTypeC
AutoMigration(from = 4, to = 5), AutoMigration(from = 4, to = 5),
AutoMigration(from = 5, to = 6), AutoMigration(from = 5, to = 6),
AutoMigration(from = 6, to = 7), AutoMigration(from = 6, to = 7),
AutoMigration(from = 7, to = 8),
], ],
exportSchema = true, exportSchema = true,
) )

@ -64,5 +64,5 @@ interface AuthorDao {
WHERE id in (:ids) WHERE id in (:ids)
""" """
) )
suspend fun deleteAuthors(ids: List<Int>) suspend fun deleteAuthors(ids: List<String>)
} }

@ -66,5 +66,5 @@ interface EpisodeDao {
WHERE id in (:ids) WHERE id in (:ids)
""" """
) )
suspend fun deleteEpisodes(ids: List<Int>) suspend fun deleteEpisodes(ids: List<String>)
} }

@ -59,8 +59,8 @@ interface NewsResourceDao {
""" """
) )
fun getNewsResourcesStream( fun getNewsResourcesStream(
filterAuthorIds: Set<Int> = emptySet(), filterAuthorIds: Set<String> = emptySet(),
filterTopicIds: Set<Int> = emptySet(), filterTopicIds: Set<String> = emptySet(),
): Flow<List<PopulatedNewsResource>> ): Flow<List<PopulatedNewsResource>>
/** /**
@ -104,5 +104,5 @@ interface NewsResourceDao {
WHERE id in (:ids) WHERE id in (:ids)
""" """
) )
suspend fun deleteNewsResources(ids: List<Int>) suspend fun deleteNewsResources(ids: List<String>)
} }

@ -36,7 +36,7 @@ interface TopicDao {
WHERE id = :topicId WHERE id = :topicId
""" """
) )
fun getTopicEntity(topicId: Int): Flow<TopicEntity> fun getTopicEntity(topicId: String): Flow<TopicEntity>
@Query(value = "SELECT * FROM topics") @Query(value = "SELECT * FROM topics")
fun getTopicEntitiesStream(): Flow<List<TopicEntity>> fun getTopicEntitiesStream(): Flow<List<TopicEntity>>
@ -47,7 +47,7 @@ interface TopicDao {
WHERE id IN (:ids) WHERE id IN (:ids)
""" """
) )
fun getTopicEntitiesStream(ids: Set<Int>): Flow<List<TopicEntity>> fun getTopicEntitiesStream(ids: Set<String>): Flow<List<TopicEntity>>
/** /**
* Inserts [topicEntities] into the db if they don't exist, and ignores those that do * Inserts [topicEntities] into the db if they don't exist, and ignores those that do
@ -80,5 +80,5 @@ interface TopicDao {
WHERE id in (:ids) WHERE id in (:ids)
""" """
) )
suspend fun deleteTopics(ids: List<Int>) suspend fun deleteTopics(ids: List<String>)
} }

@ -30,7 +30,7 @@ import com.google.samples.apps.nowinandroid.core.model.data.Author
) )
data class AuthorEntity( data class AuthorEntity(
@PrimaryKey @PrimaryKey
val id: Int, val id: String,
val name: String, val name: String,
@ColumnInfo(name = "image_url") @ColumnInfo(name = "image_url")
val imageUrl: String, val imageUrl: String,

@ -48,7 +48,7 @@ import androidx.room.Index
) )
data class EpisodeAuthorCrossRef( data class EpisodeAuthorCrossRef(
@ColumnInfo(name = "episode_id") @ColumnInfo(name = "episode_id")
val episodeId: Int, val episodeId: String,
@ColumnInfo(name = "author_id") @ColumnInfo(name = "author_id")
val authorId: Long, val authorId: String,
) )

@ -30,7 +30,7 @@ import kotlinx.datetime.Instant
) )
data class EpisodeEntity( data class EpisodeEntity(
@PrimaryKey @PrimaryKey
val id: Int, val id: String,
val name: String, val name: String,
@ColumnInfo(name = "publish_date") @ColumnInfo(name = "publish_date")
val publishDate: Instant, val publishDate: Instant,

@ -48,7 +48,7 @@ import androidx.room.Index
) )
data class NewsResourceAuthorCrossRef( data class NewsResourceAuthorCrossRef(
@ColumnInfo(name = "news_resource_id") @ColumnInfo(name = "news_resource_id")
val newsResourceId: Int, val newsResourceId: String,
@ColumnInfo(name = "author_id") @ColumnInfo(name = "author_id")
val authorId: Int, val authorId: String,
) )

@ -41,9 +41,9 @@ import kotlinx.datetime.Instant
) )
data class NewsResourceEntity( data class NewsResourceEntity(
@PrimaryKey @PrimaryKey
val id: Int, val id: String,
@ColumnInfo(name = "episode_id") @ColumnInfo(name = "episode_id")
val episodeId: Int, val episodeId: String,
val title: String, val title: String,
val content: String, val content: String,
val url: String, val url: String,

@ -48,7 +48,7 @@ import androidx.room.Index
) )
data class NewsResourceTopicCrossRef( data class NewsResourceTopicCrossRef(
@ColumnInfo(name = "news_resource_id") @ColumnInfo(name = "news_resource_id")
val newsResourceId: Int, val newsResourceId: String,
@ColumnInfo(name = "topic_id") @ColumnInfo(name = "topic_id")
val topicId: Int, val topicId: String,
) )

@ -30,7 +30,7 @@ import com.google.samples.apps.nowinandroid.core.model.data.Topic
) )
data class TopicEntity( data class TopicEntity(
@PrimaryKey @PrimaryKey
val id: Int, val id: String,
val name: String, val name: String,
val shortDescription: String, val shortDescription: String,
@ColumnInfo(defaultValue = "") @ColumnInfo(defaultValue = "")

@ -0,0 +1,50 @@
/*
* 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.datastore
import androidx.datastore.core.DataMigration
/**
* Migrates saved ids from [Int] to [String] types
*/
object IntToStringIdsMigration : DataMigration<UserPreferences> {
override suspend fun cleanUp() = Unit
override suspend fun migrate(currentData: UserPreferences): UserPreferences =
currentData.copy {
// Migrate topic ids
followedTopicIds.clear()
followedTopicIds.addAll(
currentData.deprecatedIntFollowedTopicIdsList.map(Int::toString)
)
deprecatedIntFollowedTopicIds.clear()
// Migrate author ids
followedAuthorIds.clear()
followedAuthorIds.addAll(
currentData.deprecatedIntFollowedAuthorIdsList.map(Int::toString)
)
deprecatedIntFollowedAuthorIds.clear()
// Mark migration as complete
hasDoneIntToStringIdMigration = true
}
override suspend fun shouldMigrate(currentData: UserPreferences): Boolean =
!currentData.hasDoneIntToStringIdMigration
}

@ -28,7 +28,7 @@ import kotlinx.coroutines.flow.retry
class NiaPreferences @Inject constructor( class NiaPreferences @Inject constructor(
private val userPreferences: DataStore<UserPreferences> private val userPreferences: DataStore<UserPreferences>
) { ) {
suspend fun setFollowedTopicIds(followedTopicIds: Set<Int>) { suspend fun setFollowedTopicIds(followedTopicIds: Set<String>) {
try { try {
userPreferences.updateData { userPreferences.updateData {
it.copy { it.copy {
@ -41,7 +41,7 @@ class NiaPreferences @Inject constructor(
} }
} }
suspend fun toggleFollowedTopicId(followedTopicId: Int, followed: Boolean) { suspend fun toggleFollowedTopicId(followedTopicId: String, followed: Boolean) {
try { try {
userPreferences.updateData { userPreferences.updateData {
it.copy { it.copy {
@ -60,7 +60,7 @@ class NiaPreferences @Inject constructor(
} }
} }
val followedTopicIds: Flow<Set<Int>> = userPreferences.data val followedTopicIds: Flow<Set<String>> = userPreferences.data
.retry { .retry {
Log.e("NiaPreferences", "Failed to read user preferences", it) Log.e("NiaPreferences", "Failed to read user preferences", it)
true true
@ -105,7 +105,7 @@ class NiaPreferences @Inject constructor(
} }
} }
suspend fun setFollowedAuthorIds(followedAuthorIds: Set<Int>) { suspend fun setFollowedAuthorIds(followedAuthorIds: Set<String>) {
try { try {
userPreferences.updateData { userPreferences.updateData {
it.copy { it.copy {
@ -118,7 +118,7 @@ class NiaPreferences @Inject constructor(
} }
} }
suspend fun toggleFollowedAuthorId(followedAuthorId: Int, followed: Boolean) { suspend fun toggleFollowedAuthorId(followedAuthorId: String, followed: Boolean) {
try { try {
userPreferences.updateData { userPreferences.updateData {
it.copy { it.copy {
@ -137,7 +137,7 @@ class NiaPreferences @Inject constructor(
} }
} }
val followedAuthorIds: Flow<Set<Int>> = userPreferences.data val followedAuthorIds: Flow<Set<String>> = userPreferences.data
.retry { .retry {
Log.e("NiaPreferences", "Failed to read user preferences", it) Log.e("NiaPreferences", "Failed to read user preferences", it)
true true

@ -20,6 +20,7 @@ import android.content.Context
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import androidx.datastore.core.DataStoreFactory import androidx.datastore.core.DataStoreFactory
import androidx.datastore.dataStoreFile import androidx.datastore.dataStoreFile
import com.google.samples.apps.nowinandroid.core.datastore.IntToStringIdsMigration
import com.google.samples.apps.nowinandroid.core.datastore.UserPreferences import com.google.samples.apps.nowinandroid.core.datastore.UserPreferences
import com.google.samples.apps.nowinandroid.core.datastore.UserPreferencesSerializer import com.google.samples.apps.nowinandroid.core.datastore.UserPreferencesSerializer
import com.google.samples.apps.nowinandroid.core.network.Dispatcher import com.google.samples.apps.nowinandroid.core.network.Dispatcher
@ -47,7 +48,10 @@ object DataStoreModule {
): DataStore<UserPreferences> = ): DataStore<UserPreferences> =
DataStoreFactory.create( DataStoreFactory.create(
serializer = userPreferencesSerializer, serializer = userPreferencesSerializer,
scope = CoroutineScope(ioDispatcher + SupervisorJob()) scope = CoroutineScope(ioDispatcher + SupervisorJob()),
migrations = listOf(
IntToStringIdsMigration,
)
) { ) {
context.dataStoreFile("user_preferences.pb") context.dataStoreFile("user_preferences.pb")
} }

@ -21,10 +21,13 @@ option java_multiple_files = true;
message UserPreferences { message UserPreferences {
reserved 2; reserved 2;
repeated int32 followed_topic_ids = 1; repeated int32 deprecated_int_followed_topic_ids = 1;
int32 topicChangeListVersion = 3; int32 topicChangeListVersion = 3;
int32 authorChangeListVersion = 4; int32 authorChangeListVersion = 4;
int32 episodeChangeListVersion = 5; int32 episodeChangeListVersion = 5;
int32 newsResourceChangeListVersion = 6; int32 newsResourceChangeListVersion = 6;
repeated int32 followed_author_ids = 7; repeated int32 deprecated_int_followed_author_ids = 7;
bool has_done_int_to_string_id_migration = 8;
repeated string followed_topic_ids = 9;
repeated string followed_author_ids = 10;
} }

@ -0,0 +1,86 @@
/*
* 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.datastore
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
/**
* Unit test for [IntToStringIdsMigration]
*/
class IntToStringIdsMigrationTest {
@Test
fun IntToStringIdsMigration_should_migrate_topic_ids() = runTest {
// Set up existing preferences with topic int ids
val preMigrationUserPreferences = userPreferences {
deprecatedIntFollowedTopicIds.addAll(listOf(1, 2, 3))
}
// Assert that there are no string topic ids yet
assertEquals(
emptyList<String>(),
preMigrationUserPreferences.followedTopicIdsList
)
// Run the migration
val postMigrationUserPreferences =
IntToStringIdsMigration.migrate(preMigrationUserPreferences)
// Assert the deprecated int topic ids have been migrated to the string topic ids
assertEquals(
userPreferences {
followedTopicIds.addAll(listOf("1", "2", "3"))
hasDoneIntToStringIdMigration = true
},
postMigrationUserPreferences
)
// Assert that the migration has been marked complete
assertTrue(postMigrationUserPreferences.hasDoneIntToStringIdMigration)
}
@Test
fun IntToStringIdsMigration_should_migrate_author_ids() = runTest {
// Set up existing preferences with author int ids
val preMigrationUserPreferences = userPreferences {
deprecatedIntFollowedAuthorIds.addAll(listOf(4, 5, 6))
}
// Assert that there are no string author ids yet
assertEquals(
emptyList<String>(),
preMigrationUserPreferences.followedAuthorIdsList
)
// Run the migration
val postMigrationUserPreferences =
IntToStringIdsMigration.migrate(preMigrationUserPreferences)
// Assert the deprecated int author ids have been migrated to the string author ids
assertEquals(
userPreferences {
followedAuthorIds.addAll(listOf("4", "5", "6"))
hasDoneIntToStringIdMigration = true
},
postMigrationUserPreferences
)
// Assert that the migration has been marked complete
assertTrue(postMigrationUserPreferences.hasDoneIntToStringIdMigration)
}
}

@ -39,8 +39,8 @@ class UserPreferencesSerializerTest {
@Test @Test
fun writingAndReadingUserPreferences_outputsCorrectValue() = runTest { fun writingAndReadingUserPreferences_outputsCorrectValue() = runTest {
val expectedUserPreferences = userPreferences { val expectedUserPreferences = userPreferences {
followedTopicIds.add(0) followedTopicIds.add("0")
followedTopicIds.add(1) followedTopicIds.add("1")
} }
val outputStream = ByteArrayOutputStream() val outputStream = ByteArrayOutputStream()

@ -82,8 +82,8 @@ suspend fun Synchronizer.changeListSync(
versionReader: (ChangeListVersions) -> Int, versionReader: (ChangeListVersions) -> Int,
changeListFetcher: suspend (Int) -> List<NetworkChangeList>, changeListFetcher: suspend (Int) -> List<NetworkChangeList>,
versionUpdater: ChangeListVersions.(Int) -> ChangeListVersions, versionUpdater: ChangeListVersions.(Int) -> ChangeListVersions,
modelDeleter: suspend (List<Int>) -> Unit, modelDeleter: suspend (List<String>) -> Unit,
modelUpdater: suspend (List<Int>) -> Unit, modelUpdater: suspend (List<String>) -> Unit,
) = suspendRunCatching { ) = suspendRunCatching {
// Fetch the change list since last sync (akin to a git fetch) // Fetch the change list since last sync (akin to a git fetch)
val currentVersion = versionReader(getChangeListVersions()) val currentVersion = versionReader(getChangeListVersions())

@ -29,15 +29,15 @@ interface AuthorsRepository : Syncable {
/** /**
* Sets the user's currently followed authors * Sets the user's currently followed authors
*/ */
suspend fun setFollowedAuthorIds(followedAuthorIds: Set<Int>) suspend fun setFollowedAuthorIds(followedAuthorIds: Set<String>)
/** /**
* Toggles the user's newly followed/unfollowed author * Toggles the user's newly followed/unfollowed author
*/ */
suspend fun toggleFollowedAuthorId(followedAuthorId: Int, followed: Boolean) suspend fun toggleFollowedAuthorId(followedAuthorId: String, followed: Boolean)
/** /**
* Returns the users currently followed authors * Returns the users currently followed authors
*/ */
fun getFollowedAuthorIdsStream(): Flow<Set<Int>> fun getFollowedAuthorIdsStream(): Flow<Set<String>>
} }

@ -44,13 +44,13 @@ class LocalAuthorsRepository @Inject constructor(
authorDao.getAuthorEntitiesStream() authorDao.getAuthorEntitiesStream()
.map { it.map(AuthorEntity::asExternalModel) } .map { it.map(AuthorEntity::asExternalModel) }
override suspend fun setFollowedAuthorIds(followedAuthorIds: Set<Int>) = override suspend fun setFollowedAuthorIds(followedAuthorIds: Set<String>) =
niaPreferences.setFollowedAuthorIds(followedAuthorIds) niaPreferences.setFollowedAuthorIds(followedAuthorIds)
override suspend fun toggleFollowedAuthorId(followedAuthorId: Int, followed: Boolean) = override suspend fun toggleFollowedAuthorId(followedAuthorId: String, followed: Boolean) =
niaPreferences.toggleFollowedAuthorId(followedAuthorId, followed) niaPreferences.toggleFollowedAuthorId(followedAuthorId, followed)
override fun getFollowedAuthorIdsStream(): Flow<Set<Int>> = niaPreferences.followedAuthorIds override fun getFollowedAuthorIdsStream(): Flow<Set<String>> = niaPreferences.followedAuthorIds
override suspend fun syncWith(synchronizer: Synchronizer): Boolean = override suspend fun syncWith(synchronizer: Synchronizer): Boolean =
synchronizer.changeListSync( synchronizer.changeListSync(

@ -57,8 +57,8 @@ class LocalNewsRepository @Inject constructor(
.map { it.map(PopulatedNewsResource::asExternalModel) } .map { it.map(PopulatedNewsResource::asExternalModel) }
override fun getNewsResourcesStream( override fun getNewsResourcesStream(
filterAuthorIds: Set<Int>, filterAuthorIds: Set<String>,
filterTopicIds: Set<Int> filterTopicIds: Set<String>
): Flow<List<NewsResource>> = newsResourceDao.getNewsResourcesStream( ): Flow<List<NewsResource>> = newsResourceDao.getNewsResourcesStream(
filterAuthorIds = filterAuthorIds, filterAuthorIds = filterAuthorIds,
filterTopicIds = filterTopicIds filterTopicIds = filterTopicIds

@ -44,13 +44,13 @@ class LocalTopicsRepository @Inject constructor(
topicDao.getTopicEntitiesStream() topicDao.getTopicEntitiesStream()
.map { it.map(TopicEntity::asExternalModel) } .map { it.map(TopicEntity::asExternalModel) }
override fun getTopic(id: Int): Flow<Topic> = override fun getTopic(id: String): Flow<Topic> =
topicDao.getTopicEntity(id).map { it.asExternalModel() } topicDao.getTopicEntity(id).map { it.asExternalModel() }
override suspend fun setFollowedTopicIds(followedTopicIds: Set<Int>) = override suspend fun setFollowedTopicIds(followedTopicIds: Set<String>) =
niaPreferences.setFollowedTopicIds(followedTopicIds) niaPreferences.setFollowedTopicIds(followedTopicIds)
override suspend fun toggleFollowedTopicId(followedTopicId: Int, followed: Boolean) = override suspend fun toggleFollowedTopicId(followedTopicId: String, followed: Boolean) =
niaPreferences.toggleFollowedTopicId(followedTopicId, followed) niaPreferences.toggleFollowedTopicId(followedTopicId, followed)
override fun getFollowedTopicIdsStream() = niaPreferences.followedTopicIds override fun getFollowedTopicIdsStream() = niaPreferences.followedTopicIds

@ -33,7 +33,7 @@ interface NewsRepository : Syncable {
* Returns available news resources as a stream filtered by authors or topics. * Returns available news resources as a stream filtered by authors or topics.
*/ */
fun getNewsResourcesStream( fun getNewsResourcesStream(
filterAuthorIds: Set<Int> = emptySet(), filterAuthorIds: Set<String> = emptySet(),
filterTopicIds: Set<Int> = emptySet(), filterTopicIds: Set<String> = emptySet(),
): Flow<List<NewsResource>> ): Flow<List<NewsResource>>
} }

@ -29,20 +29,20 @@ interface TopicsRepository : Syncable {
/** /**
* Gets data for a specific topic * Gets data for a specific topic
*/ */
fun getTopic(id: Int): Flow<Topic> fun getTopic(id: String): Flow<Topic>
/** /**
* Sets the user's currently followed topics * Sets the user's currently followed topics
*/ */
suspend fun setFollowedTopicIds(followedTopicIds: Set<Int>) suspend fun setFollowedTopicIds(followedTopicIds: Set<String>)
/** /**
* Toggles the user's newly followed/unfollowed topic * Toggles the user's newly followed/unfollowed topic
*/ */
suspend fun toggleFollowedTopicId(followedTopicId: Int, followed: Boolean) suspend fun toggleFollowedTopicId(followedTopicId: String, followed: Boolean)
/** /**
* Returns the users currently followed topics * Returns the users currently followed topics
*/ */
fun getFollowedTopicIdsStream(): Flow<Set<Int>> fun getFollowedTopicIdsStream(): Flow<Set<String>>
} }

@ -59,15 +59,15 @@ class FakeAuthorsRepository @Inject constructor(
} }
.flowOn(ioDispatcher) .flowOn(ioDispatcher)
override suspend fun setFollowedAuthorIds(followedAuthorIds: Set<Int>) { override suspend fun setFollowedAuthorIds(followedAuthorIds: Set<String>) {
niaPreferences.setFollowedAuthorIds(followedAuthorIds) niaPreferences.setFollowedAuthorIds(followedAuthorIds)
} }
override suspend fun toggleFollowedAuthorId(followedAuthorId: Int, followed: Boolean) { override suspend fun toggleFollowedAuthorId(followedAuthorId: String, followed: Boolean) {
niaPreferences.toggleFollowedAuthorId(followedAuthorId, followed) niaPreferences.toggleFollowedAuthorId(followedAuthorId, followed)
} }
override fun getFollowedAuthorIdsStream(): Flow<Set<Int>> = niaPreferences.followedAuthorIds override fun getFollowedAuthorIdsStream(): Flow<Set<String>> = niaPreferences.followedAuthorIds
override suspend fun syncWith(synchronizer: Synchronizer) = true override suspend fun syncWith(synchronizer: Synchronizer) = true
} }

@ -56,8 +56,8 @@ class FakeNewsRepository @Inject constructor(
.flowOn(ioDispatcher) .flowOn(ioDispatcher)
override fun getNewsResourcesStream( override fun getNewsResourcesStream(
filterAuthorIds: Set<Int>, filterAuthorIds: Set<String>,
filterTopicIds: Set<Int>, filterTopicIds: Set<String>,
): Flow<List<NewsResource>> = ): Flow<List<NewsResource>> =
flow { flow {
emit( emit(

@ -61,14 +61,14 @@ class FakeTopicsRepository @Inject constructor(
} }
.flowOn(ioDispatcher) .flowOn(ioDispatcher)
override fun getTopic(id: Int): Flow<Topic> { override fun getTopic(id: String): Flow<Topic> {
return getTopicsStream().map { it.first { topic -> topic.id == id } } return getTopicsStream().map { it.first { topic -> topic.id == id } }
} }
override suspend fun setFollowedTopicIds(followedTopicIds: Set<Int>) = override suspend fun setFollowedTopicIds(followedTopicIds: Set<String>) =
niaPreferences.setFollowedTopicIds(followedTopicIds) niaPreferences.setFollowedTopicIds(followedTopicIds)
override suspend fun toggleFollowedTopicId(followedTopicId: Int, followed: Boolean) = override suspend fun toggleFollowedTopicId(followedTopicId: String, followed: Boolean) =
niaPreferences.toggleFollowedTopicId(followedTopicId, followed) niaPreferences.toggleFollowedTopicId(followedTopicId, followed)
override fun getFollowedTopicIdsStream() = niaPreferences.followedTopicIds override fun getFollowedTopicIdsStream() = niaPreferences.followedTopicIds

@ -29,7 +29,7 @@ class PopulatedEpisodeKtTest {
fun populated_episode_can_be_mapped_to_episode() { fun populated_episode_can_be_mapped_to_episode() {
val populatedEpisode = PopulatedEpisode( val populatedEpisode = PopulatedEpisode(
entity = EpisodeEntity( entity = EpisodeEntity(
id = 0, id = "0",
name = "Test", name = "Test",
publishDate = Instant.fromEpochMilliseconds(1), publishDate = Instant.fromEpochMilliseconds(1),
alternateAudio = "audio", alternateAudio = "audio",
@ -37,8 +37,8 @@ class PopulatedEpisodeKtTest {
), ),
newsResources = listOf( newsResources = listOf(
NewsResourceEntity( NewsResourceEntity(
id = 1, id = "1",
episodeId = 0, episodeId = "0",
title = "news", title = "news",
content = "Hilt", content = "Hilt",
url = "url", url = "url",
@ -49,7 +49,7 @@ class PopulatedEpisodeKtTest {
), ),
authors = listOf( authors = listOf(
AuthorEntity( AuthorEntity(
id = 2, id = "2",
name = "name", name = "name",
imageUrl = "imageUrl", imageUrl = "imageUrl",
twitter = "twitter", twitter = "twitter",
@ -61,15 +61,15 @@ class PopulatedEpisodeKtTest {
assertEquals( assertEquals(
Episode( Episode(
id = 0, id = "0",
name = "Test", name = "Test",
publishDate = Instant.fromEpochMilliseconds(1), publishDate = Instant.fromEpochMilliseconds(1),
alternateAudio = "audio", alternateAudio = "audio",
alternateVideo = "video", alternateVideo = "video",
newsResources = listOf( newsResources = listOf(
NewsResource( NewsResource(
id = 1, id = "1",
episodeId = 0, episodeId = "0",
title = "news", title = "news",
content = "Hilt", content = "Hilt",
url = "url", url = "url",
@ -82,7 +82,7 @@ class PopulatedEpisodeKtTest {
), ),
authors = listOf( authors = listOf(
Author( Author(
id = 2, id = "2",
name = "name", name = "name",
imageUrl = "imageUrl", imageUrl = "imageUrl",
twitter = "twitter", twitter = "twitter",

@ -29,8 +29,8 @@ class PopulatedNewsResourceKtTest {
fun populated_news_resource_can_be_mapped_to_news_resource() { fun populated_news_resource_can_be_mapped_to_news_resource() {
val populatedNewsResource = PopulatedNewsResource( val populatedNewsResource = PopulatedNewsResource(
entity = NewsResourceEntity( entity = NewsResourceEntity(
id = 1, id = "1",
episodeId = 0, episodeId = "0",
title = "news", title = "news",
content = "Hilt", content = "Hilt",
url = "url", url = "url",
@ -39,7 +39,7 @@ class PopulatedNewsResourceKtTest {
publishDate = Instant.fromEpochMilliseconds(1), publishDate = Instant.fromEpochMilliseconds(1),
), ),
episode = EpisodeEntity( episode = EpisodeEntity(
id = 4, id = "4",
name = "episode 4", name = "episode 4",
publishDate = Instant.fromEpochMilliseconds(2), publishDate = Instant.fromEpochMilliseconds(2),
alternateAudio = "audio", alternateAudio = "audio",
@ -47,7 +47,7 @@ class PopulatedNewsResourceKtTest {
), ),
authors = listOf( authors = listOf(
AuthorEntity( AuthorEntity(
id = 2, id = "2",
name = "name", name = "name",
imageUrl = "imageUrl", imageUrl = "imageUrl",
twitter = "twitter", twitter = "twitter",
@ -56,7 +56,7 @@ class PopulatedNewsResourceKtTest {
), ),
topics = listOf( topics = listOf(
TopicEntity( TopicEntity(
id = 3, id = "3",
name = "name", name = "name",
shortDescription = "short description", shortDescription = "short description",
longDescription = "long description", longDescription = "long description",
@ -69,8 +69,8 @@ class PopulatedNewsResourceKtTest {
assertEquals( assertEquals(
NewsResource( NewsResource(
id = 1, id = "1",
episodeId = 0, episodeId = "0",
title = "news", title = "news",
content = "Hilt", content = "Hilt",
url = "url", url = "url",
@ -79,7 +79,7 @@ class PopulatedNewsResourceKtTest {
publishDate = Instant.fromEpochMilliseconds(1), publishDate = Instant.fromEpochMilliseconds(1),
authors = listOf( authors = listOf(
Author( Author(
id = 2, id = "2",
name = "name", name = "name",
imageUrl = "imageUrl", imageUrl = "imageUrl",
twitter = "twitter", twitter = "twitter",
@ -88,7 +88,7 @@ class PopulatedNewsResourceKtTest {
), ),
topics = listOf( topics = listOf(
Topic( Topic(
id = 3, id = "3",
name = "name", name = "name",
shortDescription = "short description", shortDescription = "short description",
longDescription = "long description", longDescription = "long description",

@ -32,7 +32,7 @@ class NetworkEntityKtTest {
@Test @Test
fun network_author_can_be_mapped_to_author_entity() { fun network_author_can_be_mapped_to_author_entity() {
val networkModel = NetworkAuthor( val networkModel = NetworkAuthor(
id = 0, id = "0",
name = "Test", name = "Test",
imageUrl = "something", imageUrl = "something",
twitter = "twitter", twitter = "twitter",
@ -40,7 +40,7 @@ class NetworkEntityKtTest {
) )
val entity = networkModel.asEntity() val entity = networkModel.asEntity()
assertEquals(0, entity.id) assertEquals("0", entity.id)
assertEquals("Test", entity.name) assertEquals("Test", entity.name)
assertEquals("something", entity.imageUrl) assertEquals("something", entity.imageUrl)
} }
@ -48,7 +48,7 @@ class NetworkEntityKtTest {
@Test @Test
fun network_topic_can_be_mapped_to_topic_entity() { fun network_topic_can_be_mapped_to_topic_entity() {
val networkModel = NetworkTopic( val networkModel = NetworkTopic(
id = 0, id = "0",
name = "Test", name = "Test",
shortDescription = "short description", shortDescription = "short description",
longDescription = "long description", longDescription = "long description",
@ -57,7 +57,7 @@ class NetworkEntityKtTest {
) )
val entity = networkModel.asEntity() val entity = networkModel.asEntity()
assertEquals(0, entity.id) assertEquals("0", entity.id)
assertEquals("Test", entity.name) assertEquals("Test", entity.name)
assertEquals("short description", entity.shortDescription) assertEquals("short description", entity.shortDescription)
assertEquals("long description", entity.longDescription) assertEquals("long description", entity.longDescription)
@ -69,8 +69,8 @@ class NetworkEntityKtTest {
fun network_news_resource_can_be_mapped_to_news_resource_entity() { fun network_news_resource_can_be_mapped_to_news_resource_entity() {
val networkModel = val networkModel =
NetworkNewsResource( NetworkNewsResource(
id = 0, id = "0",
episodeId = 2, episodeId = "2",
title = "title", title = "title",
content = "content", content = "content",
url = "url", url = "url",
@ -80,8 +80,8 @@ class NetworkEntityKtTest {
) )
val entity = networkModel.asEntity() val entity = networkModel.asEntity()
assertEquals(0, entity.id) assertEquals("0", entity.id)
assertEquals(2, entity.episodeId) assertEquals("2", entity.episodeId)
assertEquals("title", entity.title) assertEquals("title", entity.title)
assertEquals("content", entity.content) assertEquals("content", entity.content)
assertEquals("url", entity.url) assertEquals("url", entity.url)
@ -91,8 +91,8 @@ class NetworkEntityKtTest {
val expandedNetworkModel = val expandedNetworkModel =
NetworkNewsResourceExpanded( NetworkNewsResourceExpanded(
id = 0, id = "0",
episodeId = 2, episodeId = "2",
title = "title", title = "title",
content = "content", content = "content",
url = "url", url = "url",
@ -103,8 +103,8 @@ class NetworkEntityKtTest {
val entityFromExpanded = expandedNetworkModel.asEntity() val entityFromExpanded = expandedNetworkModel.asEntity()
assertEquals(0, entityFromExpanded.id) assertEquals("0", entityFromExpanded.id)
assertEquals(2, entityFromExpanded.episodeId) assertEquals("2", entityFromExpanded.episodeId)
assertEquals("title", entityFromExpanded.title) assertEquals("title", entityFromExpanded.title)
assertEquals("content", entityFromExpanded.content) assertEquals("content", entityFromExpanded.content)
assertEquals("url", entityFromExpanded.url) assertEquals("url", entityFromExpanded.url)
@ -116,7 +116,7 @@ class NetworkEntityKtTest {
@Test @Test
fun network_episode_can_be_mapped_to_episode_entity() { fun network_episode_can_be_mapped_to_episode_entity() {
val networkModel = NetworkEpisode( val networkModel = NetworkEpisode(
id = 0, id = "0",
name = "name", name = "name",
publishDate = Instant.fromEpochMilliseconds(1), publishDate = Instant.fromEpochMilliseconds(1),
alternateVideo = "alternateVideo", alternateVideo = "alternateVideo",
@ -124,7 +124,7 @@ class NetworkEntityKtTest {
) )
val entity = networkModel.asEntity() val entity = networkModel.asEntity()
assertEquals(0, entity.id) assertEquals("0", entity.id)
assertEquals("name", entity.name) assertEquals("name", entity.name)
assertEquals("alternateVideo", entity.alternateVideo) assertEquals("alternateVideo", entity.alternateVideo)
assertEquals("alternateAudio", entity.alternateAudio) assertEquals("alternateAudio", entity.alternateAudio)
@ -132,7 +132,7 @@ class NetworkEntityKtTest {
val expandedNetworkModel = val expandedNetworkModel =
NetworkEpisodeExpanded( NetworkEpisodeExpanded(
id = 0, id = "0",
name = "name", name = "name",
publishDate = Instant.fromEpochMilliseconds(1), publishDate = Instant.fromEpochMilliseconds(1),
alternateVideo = "alternateVideo", alternateVideo = "alternateVideo",
@ -141,7 +141,7 @@ class NetworkEntityKtTest {
val entityFromExpanded = expandedNetworkModel.asEntity() val entityFromExpanded = expandedNetworkModel.asEntity()
assertEquals(0, entityFromExpanded.id) assertEquals("0", entityFromExpanded.id)
assertEquals("name", entityFromExpanded.name) assertEquals("name", entityFromExpanded.name)
assertEquals("alternateVideo", entityFromExpanded.alternateVideo) assertEquals("alternateVideo", entityFromExpanded.alternateVideo)
assertEquals("alternateAudio", entityFromExpanded.alternateAudio) assertEquals("alternateAudio", entityFromExpanded.alternateAudio)

@ -145,9 +145,10 @@ class LocalAuthorsRepositoryTest {
.map(NetworkAuthor::asEntity) .map(NetworkAuthor::asEntity)
.map(AuthorEntity::asExternalModel) .map(AuthorEntity::asExternalModel)
// Delete half of the items on the network
val deletedItems = networkAuthors val deletedItems = networkAuthors
.map(Author::id) .map(Author::id)
.partition { it % 2 == 0 } .partition { it.chars().sum() % 2 == 0 }
.first .first
.toSet() .toSet()
@ -165,6 +166,7 @@ class LocalAuthorsRepositoryTest {
.first() .first()
.map(AuthorEntity::asExternalModel) .map(AuthorEntity::asExternalModel)
// Assert that items marked deleted on the network have been deleted locally
Assert.assertEquals( Assert.assertEquals(
networkAuthors.map(Author::id) - deletedItems, networkAuthors.map(Author::id) - deletedItems,
dbAuthors.map(Author::id) dbAuthors.map(Author::id)

@ -184,9 +184,10 @@ class LocalNewsRepositoryTest {
.map(NetworkNewsResource::asEntity) .map(NetworkNewsResource::asEntity)
.map(NewsResourceEntity::asExternalModel) .map(NewsResourceEntity::asExternalModel)
// Delete half of the items on the network
val deletedItems = newsResourcesFromNetwork val deletedItems = newsResourcesFromNetwork
.map(NewsResource::id) .map(NewsResource::id)
.partition { it % 2 == 0 } .partition { it.chars().sum() % 2 == 0 }
.first .first
.toSet() .toSet()
@ -204,6 +205,7 @@ class LocalNewsRepositoryTest {
.first() .first()
.map(PopulatedNewsResource::asExternalModel) .map(PopulatedNewsResource::asExternalModel)
// Assert that items marked deleted on the network have been deleted locally
assertEquals( assertEquals(
newsResourcesFromNetwork.map(NewsResource::id) - deletedItems, newsResourcesFromNetwork.map(NewsResource::id) - deletedItems,
newsResourcesFromDb.map(NewsResource::id) newsResourcesFromDb.map(NewsResource::id)

@ -150,9 +150,10 @@ class LocalTopicsRepositoryTest {
.map(NetworkTopic::asEntity) .map(NetworkTopic::asEntity)
.map(TopicEntity::asExternalModel) .map(TopicEntity::asExternalModel)
// Delete half of the items on the network
val deletedItems = networkTopics val deletedItems = networkTopics
.map(Topic::id) .map(Topic::id)
.partition { it % 2 == 0 } .partition { it.chars().sum() % 2 == 0 }
.first .first
.toSet() .toSet()
@ -170,6 +171,7 @@ class LocalTopicsRepositoryTest {
.first() .first()
.map(TopicEntity::asExternalModel) .map(TopicEntity::asExternalModel)
// Assert that items marked deleted on the network have been deleted locally
Assert.assertEquals( Assert.assertEquals(
networkTopics.map(Topic::id) - deletedItems, networkTopics.map(Topic::id) - deletedItems,
dbTopics.map(Topic::id) dbTopics.map(Topic::id)
@ -185,18 +187,18 @@ class LocalTopicsRepositoryTest {
@Test @Test
fun localTopicsRepository_toggle_followed_topics_logic_delegates_to_nia_preferences() = fun localTopicsRepository_toggle_followed_topics_logic_delegates_to_nia_preferences() =
runTest { runTest {
subject.toggleFollowedTopicId(followedTopicId = 0, followed = true) subject.toggleFollowedTopicId(followedTopicId = "0", followed = true)
Assert.assertEquals( Assert.assertEquals(
setOf(0), setOf("0"),
subject.getFollowedTopicIdsStream() subject.getFollowedTopicIdsStream()
.first() .first()
) )
subject.toggleFollowedTopicId(followedTopicId = 1, followed = true) subject.toggleFollowedTopicId(followedTopicId = "1", followed = true)
Assert.assertEquals( Assert.assertEquals(
setOf(0, 1), setOf("0", "1"),
subject.getFollowedTopicIdsStream() subject.getFollowedTopicIdsStream()
.first() .first()
) )
@ -212,10 +214,10 @@ class LocalTopicsRepositoryTest {
@Test @Test
fun localTopicsRepository_set_followed_topics_logic_delegates_to_nia_preferences() = fun localTopicsRepository_set_followed_topics_logic_delegates_to_nia_preferences() =
runTest { runTest {
subject.setFollowedTopicIds(followedTopicIds = setOf(1, 2)) subject.setFollowedTopicIds(followedTopicIds = setOf("1", "2"))
Assert.assertEquals( Assert.assertEquals(
setOf(1, 2), setOf("1", "2"),
subject.getFollowedTopicIdsStream() subject.getFollowedTopicIdsStream()
.first() .first()
) )

@ -30,7 +30,7 @@ class TestAuthorDao : AuthorDao {
private var entitiesStateFlow = MutableStateFlow( private var entitiesStateFlow = MutableStateFlow(
listOf( listOf(
AuthorEntity( AuthorEntity(
id = 1, id = "1",
name = "Topic", name = "Topic",
imageUrl = "imageUrl", imageUrl = "imageUrl",
twitter = "twitter", twitter = "twitter",
@ -52,7 +52,7 @@ class TestAuthorDao : AuthorDao {
throw NotImplementedError("Unused in tests") throw NotImplementedError("Unused in tests")
} }
override suspend fun deleteAuthors(ids: List<Int>) { override suspend fun deleteAuthors(ids: List<String>) {
val idSet = ids.toSet() val idSet = ids.toSet()
entitiesStateFlow.update { entities -> entitiesStateFlow.update { entities ->
entities.filterNot { idSet.contains(it.id) } entities.filterNot { idSet.contains(it.id) }

@ -33,7 +33,7 @@ class TestEpisodeDao : EpisodeDao {
private var entitiesStateFlow = MutableStateFlow( private var entitiesStateFlow = MutableStateFlow(
listOf( listOf(
EpisodeEntity( EpisodeEntity(
id = 1, id = "1",
name = "Episode", name = "Episode",
publishDate = Instant.fromEpochMilliseconds(0), publishDate = Instant.fromEpochMilliseconds(0),
alternateVideo = null, alternateVideo = null,
@ -57,7 +57,7 @@ class TestEpisodeDao : EpisodeDao {
throw NotImplementedError("Unused in tests") throw NotImplementedError("Unused in tests")
} }
override suspend fun deleteEpisodes(ids: List<Int>) { override suspend fun deleteEpisodes(ids: List<String>) {
val idSet = ids.toSet() val idSet = ids.toSet()
entitiesStateFlow.update { entities -> entitiesStateFlow.update { entities ->
entities.filterNot { idSet.contains(it.id) } entities.filterNot { idSet.contains(it.id) }

@ -31,8 +31,8 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
val filteredInterestsIds = setOf(1) val filteredInterestsIds = setOf("1")
val nonPresentInterestsIds = setOf(2) val nonPresentInterestsIds = setOf("2")
/** /**
* Test double for [NewsResourceDao] * Test double for [NewsResourceDao]
@ -42,8 +42,8 @@ class TestNewsResourceDao : NewsResourceDao {
private var entitiesStateFlow = MutableStateFlow( private var entitiesStateFlow = MutableStateFlow(
listOf( listOf(
NewsResourceEntity( NewsResourceEntity(
id = 1, id = "1",
episodeId = 0, episodeId = "0",
title = "news", title = "news",
content = "Hilt", content = "Hilt",
url = "url", url = "url",
@ -64,8 +64,8 @@ class TestNewsResourceDao : NewsResourceDao {
} }
override fun getNewsResourcesStream( override fun getNewsResourcesStream(
filterAuthorIds: Set<Int>, filterAuthorIds: Set<String>,
filterTopicIds: Set<Int> filterTopicIds: Set<String>
): Flow<List<PopulatedNewsResource>> = ): Flow<List<PopulatedNewsResource>> =
getNewsResourcesStream() getNewsResourcesStream()
.map { resources -> .map { resources ->
@ -99,7 +99,7 @@ class TestNewsResourceDao : NewsResourceDao {
authorCrossReferences = newsResourceAuthorCrossReferences authorCrossReferences = newsResourceAuthorCrossReferences
} }
override suspend fun deleteNewsResources(ids: List<Int>) { override suspend fun deleteNewsResources(ids: List<String>) {
val idSet = ids.toSet() val idSet = ids.toSet()
entitiesStateFlow.update { entities -> entitiesStateFlow.update { entities ->
entities.filterNot { idSet.contains(it.id) } entities.filterNot { idSet.contains(it.id) }

@ -58,19 +58,19 @@ class TestNiaNetwork : NiANetwork {
.mapToChangeList(idGetter = NetworkNewsResource::id), .mapToChangeList(idGetter = NetworkNewsResource::id),
) )
override suspend fun getTopics(ids: List<Int>?): List<NetworkTopic> = override suspend fun getTopics(ids: List<String>?): List<NetworkTopic> =
allTopics.matchIds( allTopics.matchIds(
ids = ids, ids = ids,
idGetter = NetworkTopic::id idGetter = NetworkTopic::id
) )
override suspend fun getAuthors(ids: List<Int>?): List<NetworkAuthor> = override suspend fun getAuthors(ids: List<String>?): List<NetworkAuthor> =
allAuthors.matchIds( allAuthors.matchIds(
ids = ids, ids = ids,
idGetter = NetworkAuthor::id idGetter = NetworkAuthor::id
) )
override suspend fun getNewsResources(ids: List<Int>?): List<NetworkNewsResource> = override suspend fun getNewsResources(ids: List<String>?): List<NetworkNewsResource> =
allNewsResources.matchIds( allNewsResources.matchIds(
ids = ids, ids = ids,
idGetter = NetworkNewsResource::id idGetter = NetworkNewsResource::id
@ -95,7 +95,7 @@ class TestNiaNetwork : NiANetwork {
* Edits the change list for the backing [collectionType] for the given [id] mimicking * Edits the change list for the backing [collectionType] for the given [id] mimicking
* the server's change list registry * the server's change list registry
*/ */
fun editCollection(collectionType: CollectionType, id: Int, isDelete: Boolean) { fun editCollection(collectionType: CollectionType, id: String, isDelete: Boolean) {
val changeList = changeLists.getValue(collectionType) val changeList = changeLists.getValue(collectionType)
val latestVersion = changeList.lastOrNull()?.changeListVersion ?: 0 val latestVersion = changeList.lastOrNull()?.changeListVersion ?: 0
val change = NetworkChangeList( val change = NetworkChangeList(
@ -117,8 +117,8 @@ fun List<NetworkChangeList>.after(version: Int?): List<NetworkChangeList> =
* Return items from [this] whose id defined by [idGetter] is in [ids] if [ids] is not null * Return items from [this] whose id defined by [idGetter] is in [ids] if [ids] is not null
*/ */
private fun <T> List<T>.matchIds( private fun <T> List<T>.matchIds(
ids: List<Int>?, ids: List<String>?,
idGetter: (T) -> Int idGetter: (T) -> String
) = when (ids) { ) = when (ids) {
null -> this null -> this
else -> ids.toSet().let { idSet -> this.filter { idSet.contains(idGetter(it)) } } else -> ids.toSet().let { idSet -> this.filter { idSet.contains(idGetter(it)) } }
@ -129,7 +129,7 @@ private fun <T> List<T>.matchIds(
* [after] simulates which models have changed by excluding items before it * [after] simulates which models have changed by excluding items before it
*/ */
private fun <T> List<T>.mapToChangeList( private fun <T> List<T>.mapToChangeList(
idGetter: (T) -> Int idGetter: (T) -> String
) = mapIndexed { index, item -> ) = mapIndexed { index, item ->
NetworkChangeList( NetworkChangeList(
id = idGetter(item), id = idGetter(item),

@ -31,7 +31,7 @@ class TestTopicDao : TopicDao {
private var entitiesStateFlow = MutableStateFlow( private var entitiesStateFlow = MutableStateFlow(
listOf( listOf(
TopicEntity( TopicEntity(
id = 1, id = "1",
name = "Topic", name = "Topic",
shortDescription = "short description", shortDescription = "short description",
longDescription = "long description", longDescription = "long description",
@ -41,14 +41,14 @@ class TestTopicDao : TopicDao {
) )
) )
override fun getTopicEntity(topicId: Int): Flow<TopicEntity> { override fun getTopicEntity(topicId: String): Flow<TopicEntity> {
throw NotImplementedError("Unused in tests") throw NotImplementedError("Unused in tests")
} }
override fun getTopicEntitiesStream(): Flow<List<TopicEntity>> = override fun getTopicEntitiesStream(): Flow<List<TopicEntity>> =
entitiesStateFlow entitiesStateFlow
override fun getTopicEntitiesStream(ids: Set<Int>): Flow<List<TopicEntity>> = override fun getTopicEntitiesStream(ids: Set<String>): Flow<List<TopicEntity>> =
getTopicEntitiesStream() getTopicEntitiesStream()
.map { topics -> topics.filter { it.id in ids } } .map { topics -> topics.filter { it.id in ids } }
@ -62,7 +62,7 @@ class TestTopicDao : TopicDao {
throw NotImplementedError("Unused in tests") throw NotImplementedError("Unused in tests")
} }
override suspend fun deleteTopics(ids: List<Int>) { override suspend fun deleteTopics(ids: List<String>) {
val idSet = ids.toSet() val idSet = ids.toSet()
entitiesStateFlow.update { entities -> entitiesStateFlow.update { entities ->
entities.filterNot { idSet.contains(it.id) } entities.filterNot { idSet.contains(it.id) }

@ -20,7 +20,7 @@ package com.google.samples.apps.nowinandroid.core.model.data
* External data layer representation of an NiA Author * External data layer representation of an NiA Author
*/ */
data class Author( data class Author(
val id: Int, val id: String,
val name: String, val name: String,
val imageUrl: String, val imageUrl: String,
val twitter: String, val twitter: String,

@ -22,7 +22,7 @@ import kotlinx.datetime.Instant
* External data layer representation of an NiA episode * External data layer representation of an NiA episode
*/ */
data class Episode( data class Episode(
val id: Int, val id: String,
val name: String, val name: String,
val publishDate: Instant, val publishDate: Instant,
val alternateVideo: String?, val alternateVideo: String?,

@ -22,8 +22,8 @@ import kotlinx.datetime.Instant
* External data layer representation of a fully populated NiA news resource * External data layer representation of a fully populated NiA news resource
*/ */
data class NewsResource( data class NewsResource(
val id: Int, val id: String,
val episodeId: Int, val episodeId: String,
val title: String, val title: String,
val content: String, val content: String,
val url: String, val url: String,

@ -20,7 +20,7 @@ package com.google.samples.apps.nowinandroid.core.model.data
* External data layer representation of a NiA Topic * External data layer representation of a NiA Topic
*/ */
data class Topic( data class Topic(
val id: Int, val id: String,
val name: String, val name: String,
val shortDescription: String, val shortDescription: String,
val longDescription: String, val longDescription: String,

@ -25,11 +25,11 @@ import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic
* Interface representing network calls to the NIA backend * Interface representing network calls to the NIA backend
*/ */
interface NiANetwork { interface NiANetwork {
suspend fun getTopics(ids: List<Int>? = null): List<NetworkTopic> suspend fun getTopics(ids: List<String>? = null): List<NetworkTopic>
suspend fun getAuthors(ids: List<Int>? = null): List<NetworkAuthor> suspend fun getAuthors(ids: List<String>? = null): List<NetworkAuthor>
suspend fun getNewsResources(ids: List<Int>? = null): List<NetworkNewsResource> suspend fun getNewsResources(ids: List<String>? = null): List<NetworkNewsResource>
suspend fun getTopicChangeList(after: Int? = null): List<NetworkChangeList> suspend fun getTopicChangeList(after: Int? = null): List<NetworkChangeList>

@ -36,17 +36,17 @@ class FakeNiANetwork @Inject constructor(
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
private val networkJson: Json private val networkJson: Json
) : NiANetwork { ) : NiANetwork {
override suspend fun getTopics(ids: List<Int>?): List<NetworkTopic> = override suspend fun getTopics(ids: List<String>?): List<NetworkTopic> =
withContext(ioDispatcher) { withContext(ioDispatcher) {
networkJson.decodeFromString(FakeDataSource.topicsData) networkJson.decodeFromString(FakeDataSource.topicsData)
} }
override suspend fun getNewsResources(ids: List<Int>?): List<NetworkNewsResource> = override suspend fun getNewsResources(ids: List<String>?): List<NetworkNewsResource> =
withContext(ioDispatcher) { withContext(ioDispatcher) {
networkJson.decodeFromString(FakeDataSource.data) networkJson.decodeFromString(FakeDataSource.data)
} }
override suspend fun getAuthors(ids: List<Int>?): List<NetworkAuthor> = override suspend fun getAuthors(ids: List<String>?): List<NetworkAuthor> =
withContext(ioDispatcher) { withContext(ioDispatcher) {
networkJson.decodeFromString(FakeDataSource.authors) networkJson.decodeFromString(FakeDataSource.authors)
} }
@ -66,7 +66,7 @@ class FakeNiANetwork @Inject constructor(
* [NetworkChangeList.id] * [NetworkChangeList.id]
*/ */
private fun <T> List<T>.mapToChangeList( private fun <T> List<T>.mapToChangeList(
idGetter: (T) -> Int idGetter: (T) -> String
) = mapIndexed { index, item -> ) = mapIndexed { index, item ->
NetworkChangeList( NetworkChangeList(
id = idGetter(item), id = idGetter(item),

@ -24,7 +24,7 @@ import kotlinx.serialization.Serializable
*/ */
@Serializable @Serializable
data class NetworkAuthor( data class NetworkAuthor(
val id: Int, val id: String,
val name: String, val name: String,
val imageUrl: String, val imageUrl: String,
val twitter: String, val twitter: String,

@ -29,7 +29,7 @@ data class NetworkChangeList(
/** /**
* The id of the model that was changed * The id of the model that was changed
*/ */
val id: Int, val id: String,
/** /**
* Unique consecutive, monotonically increasing version number in the collection describing * Unique consecutive, monotonically increasing version number in the collection describing
* the relative point of change between models in the collection * the relative point of change between models in the collection

@ -26,14 +26,14 @@ import kotlinx.serialization.Serializable
*/ */
@Serializable @Serializable
data class NetworkEpisode( data class NetworkEpisode(
val id: Int, val id: String,
val name: String, val name: String,
@Serializable(InstantSerializer::class) @Serializable(InstantSerializer::class)
val publishDate: Instant, val publishDate: Instant,
val alternateVideo: String?, val alternateVideo: String?,
val alternateAudio: String?, val alternateAudio: String?,
val newsResources: List<Int> = listOf(), val newsResources: List<String> = listOf(),
val authors: List<Int> = listOf(), val authors: List<String> = listOf(),
) )
/** /**
@ -41,7 +41,7 @@ data class NetworkEpisode(
*/ */
@Serializable @Serializable
data class NetworkEpisodeExpanded( data class NetworkEpisodeExpanded(
val id: Int, val id: String,
val name: String, val name: String,
@Serializable(InstantSerializer::class) @Serializable(InstantSerializer::class)
val publishDate: Instant, val publishDate: Instant,

@ -28,8 +28,8 @@ import kotlinx.serialization.Serializable
*/ */
@Serializable @Serializable
data class NetworkNewsResource( data class NetworkNewsResource(
val id: Int, val id: String,
val episodeId: Int, val episodeId: String,
val title: String, val title: String,
val content: String, val content: String,
val url: String, val url: String,
@ -38,8 +38,8 @@ data class NetworkNewsResource(
val publishDate: Instant, val publishDate: Instant,
@Serializable(NewsResourceTypeSerializer::class) @Serializable(NewsResourceTypeSerializer::class)
val type: NewsResourceType, val type: NewsResourceType,
val authors: List<Int> = listOf(), val authors: List<String> = listOf(),
val topics: List<Int> = listOf(), val topics: List<String> = listOf(),
) )
/** /**
@ -47,8 +47,8 @@ data class NetworkNewsResource(
*/ */
@Serializable @Serializable
data class NetworkNewsResourceExpanded( data class NetworkNewsResourceExpanded(
val id: Int, val id: String,
val episodeId: Int, val episodeId: String,
val title: String, val title: String,
val content: String, val content: String,
val url: String, val url: String,

@ -24,7 +24,7 @@ import kotlinx.serialization.Serializable
*/ */
@Serializable @Serializable
data class NetworkTopic( data class NetworkTopic(
val id: Int, val id: String,
val name: String = "", val name: String = "",
val shortDescription: String = "", val shortDescription: String = "",
val longDescription: String = "", val longDescription: String = "",

@ -39,17 +39,17 @@ import retrofit2.http.Query
private interface RetrofitNiANetworkApi { private interface RetrofitNiANetworkApi {
@GET(value = "topics") @GET(value = "topics")
suspend fun getTopics( suspend fun getTopics(
@Query("id") ids: List<Int>?, @Query("id") ids: List<String>?,
): NetworkResponse<List<NetworkTopic>> ): NetworkResponse<List<NetworkTopic>>
@GET(value = "authors") @GET(value = "authors")
suspend fun getAuthors( suspend fun getAuthors(
@Query("id") ids: List<Int>?, @Query("id") ids: List<String>?,
): NetworkResponse<List<NetworkAuthor>> ): NetworkResponse<List<NetworkAuthor>>
@GET(value = "newsresources") @GET(value = "newsresources")
suspend fun getNewsResources( suspend fun getNewsResources(
@Query("id") ids: List<Int>?, @Query("id") ids: List<String>?,
): NetworkResponse<List<NetworkNewsResource>> ): NetworkResponse<List<NetworkNewsResource>>
@GET(value = "changelists/topics") @GET(value = "changelists/topics")
@ -102,13 +102,13 @@ class RetrofitNiANetwork @Inject constructor(
.build() .build()
.create(RetrofitNiANetworkApi::class.java) .create(RetrofitNiANetworkApi::class.java)
override suspend fun getTopics(ids: List<Int>?): List<NetworkTopic> = override suspend fun getTopics(ids: List<String>?): List<NetworkTopic> =
networkApi.getTopics(ids = ids).data networkApi.getTopics(ids = ids).data
override suspend fun getAuthors(ids: List<Int>?): List<NetworkAuthor> = override suspend fun getAuthors(ids: List<String>?): List<NetworkAuthor> =
networkApi.getAuthors(ids = ids).data networkApi.getAuthors(ids = ids).data
override suspend fun getNewsResources(ids: List<Int>?): List<NetworkNewsResource> = override suspend fun getNewsResources(ids: List<String>?): List<NetworkNewsResource> =
networkApi.getNewsResources(ids = ids).data networkApi.getNewsResources(ids = ids).data
override suspend fun getTopicChangeList(after: Int?): List<NetworkChangeList> = override suspend fun getTopicChangeList(after: Int?): List<NetworkChangeList> =

@ -27,7 +27,7 @@ class TestAuthorsRepository : AuthorsRepository {
/** /**
* The backing hot flow for the list of followed author ids for testing. * The backing hot flow for the list of followed author ids for testing.
*/ */
private val _followedAuthorIds: MutableSharedFlow<Set<Int>> = private val _followedAuthorIds: MutableSharedFlow<Set<String>> =
MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
/** /**
@ -38,13 +38,13 @@ class TestAuthorsRepository : AuthorsRepository {
override fun getAuthorsStream(): Flow<List<Author>> = authorsFlow override fun getAuthorsStream(): Flow<List<Author>> = authorsFlow
override fun getFollowedAuthorIdsStream(): Flow<Set<Int>> = _followedAuthorIds override fun getFollowedAuthorIdsStream(): Flow<Set<String>> = _followedAuthorIds
override suspend fun setFollowedAuthorIds(followedAuthorIds: Set<Int>) { override suspend fun setFollowedAuthorIds(followedAuthorIds: Set<String>) {
_followedAuthorIds.tryEmit(followedAuthorIds) _followedAuthorIds.tryEmit(followedAuthorIds)
} }
override suspend fun toggleFollowedAuthorId(followedAuthorId: Int, followed: Boolean) { override suspend fun toggleFollowedAuthorId(followedAuthorId: String, followed: Boolean) {
getCurrentFollowedAuthors()?.let { current -> getCurrentFollowedAuthors()?.let { current ->
_followedAuthorIds.tryEmit( _followedAuthorIds.tryEmit(
if (followed) current.plus(followedAuthorId) if (followed) current.plus(followedAuthorId)
@ -65,5 +65,5 @@ class TestAuthorsRepository : AuthorsRepository {
/** /**
* A test-only API to allow querying the current followed topics. * A test-only API to allow querying the current followed topics.
*/ */
fun getCurrentFollowedAuthors(): Set<Int>? = _followedAuthorIds.replayCache.firstOrNull() fun getCurrentFollowedAuthors(): Set<String>? = _followedAuthorIds.replayCache.firstOrNull()
} }

@ -37,8 +37,8 @@ class TestNewsRepository : NewsRepository {
override fun getNewsResourcesStream(): Flow<List<NewsResource>> = newsResourcesFlow override fun getNewsResourcesStream(): Flow<List<NewsResource>> = newsResourcesFlow
override fun getNewsResourcesStream( override fun getNewsResourcesStream(
filterAuthorIds: Set<Int>, filterAuthorIds: Set<String>,
filterTopicIds: Set<Int> filterTopicIds: Set<String>
): Flow<List<NewsResource>> = ): Flow<List<NewsResource>> =
getNewsResourcesStream().map { newsResources -> getNewsResourcesStream().map { newsResources ->
newsResources newsResources

@ -28,7 +28,7 @@ class TestTopicsRepository : TopicsRepository {
/** /**
* The backing hot flow for the list of followed topic ids for testing. * The backing hot flow for the list of followed topic ids for testing.
*/ */
private val _followedTopicIds: MutableSharedFlow<Set<Int>> = private val _followedTopicIds: MutableSharedFlow<Set<String>> =
MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
/** /**
@ -39,15 +39,15 @@ class TestTopicsRepository : TopicsRepository {
override fun getTopicsStream(): Flow<List<Topic>> = topicsFlow override fun getTopicsStream(): Flow<List<Topic>> = topicsFlow
override fun getTopic(id: Int): Flow<Topic> { override fun getTopic(id: String): Flow<Topic> {
return topicsFlow.map { topics -> topics.find { it.id == id }!! } return topicsFlow.map { topics -> topics.find { it.id == id }!! }
} }
override suspend fun setFollowedTopicIds(followedTopicIds: Set<Int>) { override suspend fun setFollowedTopicIds(followedTopicIds: Set<String>) {
_followedTopicIds.tryEmit(followedTopicIds) _followedTopicIds.tryEmit(followedTopicIds)
} }
override suspend fun toggleFollowedTopicId(followedTopicId: Int, followed: Boolean) { override suspend fun toggleFollowedTopicId(followedTopicId: String, followed: Boolean) {
getCurrentFollowedTopics()?.let { current -> getCurrentFollowedTopics()?.let { current ->
_followedTopicIds.tryEmit( _followedTopicIds.tryEmit(
if (followed) current.plus(followedTopicId) if (followed) current.plus(followedTopicId)
@ -56,7 +56,7 @@ class TestTopicsRepository : TopicsRepository {
} }
} }
override fun getFollowedTopicIdsStream(): Flow<Set<Int>> = _followedTopicIds override fun getFollowedTopicIdsStream(): Flow<Set<String>> = _followedTopicIds
/** /**
* A test-only API to allow controlling the list of topics from tests. * A test-only API to allow controlling the list of topics from tests.
@ -68,7 +68,7 @@ class TestTopicsRepository : TopicsRepository {
/** /**
* A test-only API to allow querying the current followed topics. * A test-only API to allow querying the current followed topics.
*/ */
fun getCurrentFollowedTopics(): Set<Int>? = _followedTopicIds.replayCache.firstOrNull() fun getCurrentFollowedTopics(): Set<String>? = _followedTopicIds.replayCache.firstOrNull()
override suspend fun syncWith(synchronizer: Synchronizer) = true override suspend fun syncWith(synchronizer: Synchronizer) = true
} }

@ -300,8 +300,8 @@ fun ExpandedNewsResourcePreview() {
} }
private val newsResource = NewsResource( private val newsResource = NewsResource(
id = 1, id = "1",
episodeId = 1, episodeId = "1",
title = "Title", title = "Title",
content = "Content", content = "Content",
url = "url", url = "url",
@ -310,7 +310,7 @@ private val newsResource = NewsResource(
type = Article, type = Article,
authors = listOf( authors = listOf(
Author( Author(
id = 1, id = "1",
name = "Name", name = "Name",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -319,7 +319,7 @@ private val newsResource = NewsResource(
), ),
topics = listOf( topics = listOf(
Topic( Topic(
id = 1, id = "1",
name = "Name", name = "Name",
shortDescription = "Short description", shortDescription = "Short description",
longDescription = "Long description", longDescription = "Long description",

@ -196,7 +196,7 @@ private const val TOPIC_IMAGE_URL = "Image URL"
private val testTopics = listOf( private val testTopics = listOf(
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 0, id = "0",
name = TOPIC_1_NAME, name = TOPIC_1_NAME,
shortDescription = TOPIC_SHORT_DESC, shortDescription = TOPIC_SHORT_DESC,
longDescription = TOPIC_LONG_DESC, longDescription = TOPIC_LONG_DESC,
@ -207,7 +207,7 @@ private val testTopics = listOf(
), ),
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 1, id = "1",
name = TOPIC_2_NAME, name = TOPIC_2_NAME,
shortDescription = TOPIC_SHORT_DESC, shortDescription = TOPIC_SHORT_DESC,
longDescription = TOPIC_LONG_DESC, longDescription = TOPIC_LONG_DESC,
@ -218,7 +218,7 @@ private val testTopics = listOf(
), ),
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 2, id = "2",
name = TOPIC_3_NAME, name = TOPIC_3_NAME,
shortDescription = TOPIC_SHORT_DESC, shortDescription = TOPIC_SHORT_DESC,
longDescription = TOPIC_LONG_DESC, longDescription = TOPIC_LONG_DESC,
@ -232,7 +232,7 @@ private val testTopics = listOf(
private val testAuthors = listOf( private val testAuthors = listOf(
FollowableAuthor( FollowableAuthor(
Author( Author(
id = 0, id = "0",
name = "Android Dev", name = "Android Dev",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -242,7 +242,7 @@ private val testAuthors = listOf(
), ),
FollowableAuthor( FollowableAuthor(
Author( Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -252,7 +252,7 @@ private val testAuthors = listOf(
), ),
FollowableAuthor( FollowableAuthor(
Author( Author(
id = 2, id = "2",
name = "Android Dev 3", name = "Android Dev 3",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",

@ -39,7 +39,7 @@ import com.google.samples.apps.nowinandroid.core.ui.component.NiaTopAppBar
fun InterestsRoute( fun InterestsRoute(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
navigateToAuthor: () -> Unit, navigateToAuthor: () -> Unit,
navigateToTopic: (Int) -> Unit, navigateToTopic: (String) -> Unit,
viewModel: FollowingViewModel = hiltViewModel() viewModel: FollowingViewModel = hiltViewModel()
) { ) {
val uiState by viewModel.uiState.collectAsState() val uiState by viewModel.uiState.collectAsState()
@ -61,10 +61,10 @@ fun InterestsRoute(
fun FollowingScreen( fun FollowingScreen(
uiState: FollowingUiState, uiState: FollowingUiState,
tabState: FollowingTabState, tabState: FollowingTabState,
followAuthor: (Int, Boolean) -> Unit, followAuthor: (String, Boolean) -> Unit,
followTopic: (Int, Boolean) -> Unit, followTopic: (String, Boolean) -> Unit,
navigateToAuthor: () -> Unit, navigateToAuthor: () -> Unit,
navigateToTopic: (Int) -> Unit, navigateToTopic: (String) -> Unit,
switchTab: (Int) -> Unit, switchTab: (Int) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
@ -104,10 +104,10 @@ private fun FollowingContent(
tabState: FollowingTabState, tabState: FollowingTabState,
switchTab: (Int) -> Unit, switchTab: (Int) -> Unit,
uiState: FollowingUiState.Interests, uiState: FollowingUiState.Interests,
navigateToTopic: (Int) -> Unit, navigateToTopic: (String) -> Unit,
followTopic: (Int, Boolean) -> Unit, followTopic: (String, Boolean) -> Unit,
navigateToAuthor: () -> Unit, navigateToAuthor: () -> Unit,
followAuthor: (Int, Boolean) -> Unit, followAuthor: (String, Boolean) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Column(modifier) { Column(modifier) {

@ -79,13 +79,13 @@ class FollowingViewModel @Inject constructor(
initialValue = FollowingUiState.Loading initialValue = FollowingUiState.Loading
) )
fun followTopic(followedTopicId: Int, followed: Boolean) { fun followTopic(followedTopicId: String, followed: Boolean) {
viewModelScope.launch { viewModelScope.launch {
topicsRepository.toggleFollowedTopicId(followedTopicId, followed) topicsRepository.toggleFollowedTopicId(followedTopicId, followed)
} }
} }
fun followAuthor(followedAuthorId: Int, followed: Boolean) { fun followAuthor(followedAuthorId: String, followed: Boolean) {
viewModelScope.launch { viewModelScope.launch {
authorsRepository.toggleFollowedAuthorId(followedAuthorId, followed) authorsRepository.toggleFollowedAuthorId(followedAuthorId, followed)
} }

@ -29,8 +29,8 @@ import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
@Composable @Composable
fun TopicsTabContent( fun TopicsTabContent(
topics: List<FollowableTopic>, topics: List<FollowableTopic>,
onTopicClick: (Int) -> Unit, onTopicClick: (String) -> Unit,
onFollowButtonClick: (Int, Boolean) -> Unit, onFollowButtonClick: (String, Boolean) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
LazyColumn( LazyColumn(
@ -55,7 +55,7 @@ fun TopicsTabContent(
fun AuthorsTabContent( fun AuthorsTabContent(
authors: List<FollowableAuthor>, authors: List<FollowableAuthor>,
onAuthorClick: () -> Unit, onAuthorClick: () -> Unit,
onFollowButtonClick: (Int, Boolean) -> Unit, onFollowButtonClick: (String, Boolean) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
LazyColumn( LazyColumn(

@ -58,7 +58,7 @@ class FollowingViewModelTest {
fun uiState_whenFollowedTopicsAreLoading_thenShowLoading() = runTest { fun uiState_whenFollowedTopicsAreLoading_thenShowLoading() = runTest {
viewModel.uiState.test { viewModel.uiState.test {
assertEquals(FollowingUiState.Loading, awaitItem()) assertEquals(FollowingUiState.Loading, awaitItem())
authorsRepository.setFollowedAuthorIds(setOf(1)) authorsRepository.setFollowedAuthorIds(setOf("1"))
topicsRepository.setFollowedTopicIds(emptySet()) topicsRepository.setFollowedTopicIds(emptySet())
cancel() cancel()
} }
@ -69,7 +69,7 @@ class FollowingViewModelTest {
viewModel.uiState.test { viewModel.uiState.test {
assertEquals(FollowingUiState.Loading, awaitItem()) assertEquals(FollowingUiState.Loading, awaitItem())
authorsRepository.setFollowedAuthorIds(emptySet()) authorsRepository.setFollowedAuthorIds(emptySet())
topicsRepository.setFollowedTopicIds(setOf(1)) topicsRepository.setFollowedTopicIds(setOf("1"))
cancel() cancel()
} }
} }
@ -198,7 +198,7 @@ private const val TOPIC_IMAGE_URL = "Image URL"
private val testInputAuthors = listOf( private val testInputAuthors = listOf(
FollowableAuthor( FollowableAuthor(
Author( Author(
id = 0, id = "0",
name = "Android Dev", name = "Android Dev",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -208,7 +208,7 @@ private val testInputAuthors = listOf(
), ),
FollowableAuthor( FollowableAuthor(
Author( Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -218,7 +218,7 @@ private val testInputAuthors = listOf(
), ),
FollowableAuthor( FollowableAuthor(
Author( Author(
id = 2, id = "2",
name = "Android Dev 3", name = "Android Dev 3",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -231,7 +231,7 @@ private val testInputAuthors = listOf(
private val testOutputAuthors = listOf( private val testOutputAuthors = listOf(
FollowableAuthor( FollowableAuthor(
Author( Author(
id = 0, id = "0",
name = "Android Dev", name = "Android Dev",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -241,7 +241,7 @@ private val testOutputAuthors = listOf(
), ),
FollowableAuthor( FollowableAuthor(
Author( Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -251,7 +251,7 @@ private val testOutputAuthors = listOf(
), ),
FollowableAuthor( FollowableAuthor(
Author( Author(
id = 2, id = "2",
name = "Android Dev 3", name = "Android Dev 3",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -264,7 +264,7 @@ private val testOutputAuthors = listOf(
private val testInputTopics = listOf( private val testInputTopics = listOf(
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 0, id = "0",
name = TOPIC_1_NAME, name = TOPIC_1_NAME,
shortDescription = TOPIC_SHORT_DESC, shortDescription = TOPIC_SHORT_DESC,
longDescription = TOPIC_LONG_DESC, longDescription = TOPIC_LONG_DESC,
@ -275,7 +275,7 @@ private val testInputTopics = listOf(
), ),
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 1, id = "1",
name = TOPIC_2_NAME, name = TOPIC_2_NAME,
shortDescription = TOPIC_SHORT_DESC, shortDescription = TOPIC_SHORT_DESC,
longDescription = TOPIC_LONG_DESC, longDescription = TOPIC_LONG_DESC,
@ -286,7 +286,7 @@ private val testInputTopics = listOf(
), ),
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 2, id = "2",
name = TOPIC_3_NAME, name = TOPIC_3_NAME,
shortDescription = TOPIC_SHORT_DESC, shortDescription = TOPIC_SHORT_DESC,
longDescription = TOPIC_LONG_DESC, longDescription = TOPIC_LONG_DESC,
@ -300,7 +300,7 @@ private val testInputTopics = listOf(
private val testOutputTopics = listOf( private val testOutputTopics = listOf(
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 0, id = "0",
name = TOPIC_1_NAME, name = TOPIC_1_NAME,
shortDescription = TOPIC_SHORT_DESC, shortDescription = TOPIC_SHORT_DESC,
longDescription = TOPIC_LONG_DESC, longDescription = TOPIC_LONG_DESC,
@ -311,7 +311,7 @@ private val testOutputTopics = listOf(
), ),
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 1, id = "1",
name = TOPIC_2_NAME, name = TOPIC_2_NAME,
shortDescription = TOPIC_SHORT_DESC, shortDescription = TOPIC_SHORT_DESC,
longDescription = TOPIC_LONG_DESC, longDescription = TOPIC_LONG_DESC,
@ -322,7 +322,7 @@ private val testOutputTopics = listOf(
), ),
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 2, id = "2",
name = TOPIC_3_NAME, name = TOPIC_3_NAME,
shortDescription = TOPIC_SHORT_DESC, shortDescription = TOPIC_SHORT_DESC,
longDescription = TOPIC_LONG_DESC, longDescription = TOPIC_LONG_DESC,

@ -74,7 +74,7 @@ class ForYouScreenTest {
topics = listOf( topics = listOf(
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 0, id = "0",
name = "Headlines", name = "Headlines",
shortDescription = "", shortDescription = "",
longDescription = "", longDescription = "",
@ -85,7 +85,7 @@ class ForYouScreenTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 1, id = "1",
name = "UI", name = "UI",
shortDescription = "", shortDescription = "",
longDescription = "", longDescription = "",
@ -96,7 +96,7 @@ class ForYouScreenTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 2, id = "2",
name = "Tools", name = "Tools",
shortDescription = "", shortDescription = "",
longDescription = "", longDescription = "",
@ -109,7 +109,7 @@ class ForYouScreenTest {
authors = listOf( authors = listOf(
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 0, id = "0",
name = "Android Dev", name = "Android Dev",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -119,7 +119,7 @@ class ForYouScreenTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -173,7 +173,7 @@ class ForYouScreenTest {
topics = listOf( topics = listOf(
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 0, id = "0",
name = "Headlines", name = "Headlines",
shortDescription = "", shortDescription = "",
longDescription = "", longDescription = "",
@ -184,7 +184,7 @@ class ForYouScreenTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 1, id = "1",
name = "UI", name = "UI",
shortDescription = "", shortDescription = "",
longDescription = "", longDescription = "",
@ -195,7 +195,7 @@ class ForYouScreenTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 2, id = "2",
name = "Tools", name = "Tools",
shortDescription = "", shortDescription = "",
longDescription = "", longDescription = "",
@ -208,7 +208,7 @@ class ForYouScreenTest {
authors = listOf( authors = listOf(
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 0, id = "0",
name = "Android Dev", name = "Android Dev",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -218,7 +218,7 @@ class ForYouScreenTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -278,7 +278,7 @@ class ForYouScreenTest {
topics = listOf( topics = listOf(
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 0, id = "0",
name = "Headlines", name = "Headlines",
shortDescription = "", shortDescription = "",
longDescription = "", longDescription = "",
@ -289,7 +289,7 @@ class ForYouScreenTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 1, id = "1",
name = "UI", name = "UI",
shortDescription = "", shortDescription = "",
longDescription = "", longDescription = "",
@ -300,7 +300,7 @@ class ForYouScreenTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 2, id = "2",
name = "Tools", name = "Tools",
shortDescription = "", shortDescription = "",
longDescription = "", longDescription = "",
@ -313,7 +313,7 @@ class ForYouScreenTest {
authors = listOf( authors = listOf(
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 0, id = "0",
name = "Android Dev", name = "Android Dev",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -323,7 +323,7 @@ class ForYouScreenTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",

@ -52,7 +52,7 @@ import com.google.samples.apps.nowinandroid.core.ui.FollowButton
@Composable @Composable
fun AuthorsCarousel( fun AuthorsCarousel(
authors: List<FollowableAuthor>, authors: List<FollowableAuthor>,
onAuthorClick: (Int, Boolean) -> Unit, onAuthorClick: (String, Boolean) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
LazyRow(modifier) { LazyRow(modifier) {
@ -132,15 +132,15 @@ fun AuthorCarouselPreview() {
AuthorsCarousel( AuthorsCarousel(
authors = listOf( authors = listOf(
FollowableAuthor( FollowableAuthor(
Author(1, "Android Dev", "", "", ""), Author("1", "Android Dev", "", "", ""),
false false
), ),
FollowableAuthor( FollowableAuthor(
Author(2, "Android Dev2", "", "", ""), Author("2", "Android Dev2", "", "", ""),
true true
), ),
FollowableAuthor( FollowableAuthor(
Author(3, "Android Dev3", "", "", ""), Author("3", "Android Dev3", "", "", ""),
false false
) )
), ),
@ -156,7 +156,7 @@ fun AuthorItemPreview() {
MaterialTheme { MaterialTheme {
Surface { Surface {
AuthorItem( AuthorItem(
author = Author(0, "Android Dev", "", "", ""), author = Author("0", "Android Dev", "", "", ""),
following = true, following = true,
onAuthorClick = { } onAuthorClick = { }
) )

@ -94,10 +94,10 @@ fun ForYouRoute(
@Composable @Composable
fun ForYouScreen( fun ForYouScreen(
uiState: ForYouFeedUiState, uiState: ForYouFeedUiState,
onTopicCheckedChanged: (Int, Boolean) -> Unit, onTopicCheckedChanged: (String, Boolean) -> Unit,
onAuthorCheckedChanged: (Int, Boolean) -> Unit, onAuthorCheckedChanged: (String, Boolean) -> Unit,
saveFollowedTopics: () -> Unit, saveFollowedTopics: () -> Unit,
onNewsResourcesCheckedChanged: (Int, Boolean) -> Unit, onNewsResourcesCheckedChanged: (String, Boolean) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
LazyColumn( LazyColumn(
@ -206,7 +206,7 @@ fun ForYouScreen(
@Composable @Composable
private fun TopicSelection( private fun TopicSelection(
uiState: FeedWithInterestsSelection, uiState: FeedWithInterestsSelection,
onTopicCheckedChanged: (Int, Boolean) -> Unit, onTopicCheckedChanged: (String, Boolean) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
LazyHorizontalGrid( LazyHorizontalGrid(
@ -242,9 +242,9 @@ private fun TopicSelection(
@Composable @Composable
private fun SingleTopicButton( private fun SingleTopicButton(
name: String, name: String,
topicId: Int, topicId: String,
isSelected: Boolean, isSelected: Boolean,
onClick: (Int, Boolean) -> Unit onClick: (String, Boolean) -> Unit
) { ) {
Surface( Surface(
modifier = Modifier modifier = Modifier
@ -306,7 +306,7 @@ fun ForYouScreenTopicSelection() {
topics = listOf( topics = listOf(
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 0, id = "0",
name = "Headlines", name = "Headlines",
shortDescription = "", shortDescription = "",
longDescription = "", longDescription = "",
@ -317,7 +317,7 @@ fun ForYouScreenTopicSelection() {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 1, id = "1",
name = "UI", name = "UI",
shortDescription = "", shortDescription = "",
longDescription = "", longDescription = "",
@ -328,7 +328,7 @@ fun ForYouScreenTopicSelection() {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 2, id = "2",
name = "Tools", name = "Tools",
shortDescription = "", shortDescription = "",
longDescription = "", longDescription = "",
@ -341,8 +341,8 @@ fun ForYouScreenTopicSelection() {
feed = listOf( feed = listOf(
SaveableNewsResource( SaveableNewsResource(
newsResource = NewsResource( newsResource = NewsResource(
id = 1, id = "1",
episodeId = 52, episodeId = "52",
title = "Thanks for helping us reach 1M YouTube Subscribers", title = "Thanks for helping us reach 1M YouTube Subscribers",
content = "Thank you everyone for following the Now in Android series " + content = "Thank you everyone for following the Now in Android series " +
"and everything the Android Developers YouTube channel has to offer. " + "and everything the Android Developers YouTube channel has to offer. " +
@ -354,7 +354,7 @@ fun ForYouScreenTopicSelection() {
type = Video, type = Video,
topics = listOf( topics = listOf(
Topic( Topic(
id = 0, id = "0",
name = "Headlines", name = "Headlines",
shortDescription = "", shortDescription = "",
longDescription = "", longDescription = "",
@ -368,8 +368,8 @@ fun ForYouScreenTopicSelection() {
), ),
SaveableNewsResource( SaveableNewsResource(
newsResource = NewsResource( newsResource = NewsResource(
id = 2, id = "2",
episodeId = 52, episodeId = "52",
title = "Transformations and customisations in the Paging Library", title = "Transformations and customisations in the Paging Library",
content = "A demonstration of different operations that can be performed " + content = "A demonstration of different operations that can be performed " +
"with Paging. Transformations like inserting separators, when to " + "with Paging. Transformations like inserting separators, when to " +
@ -381,7 +381,7 @@ fun ForYouScreenTopicSelection() {
type = Video, type = Video,
topics = listOf( topics = listOf(
Topic( Topic(
id = 1, id = "1",
name = "UI", name = "UI",
shortDescription = "", shortDescription = "",
longDescription = "", longDescription = "",
@ -395,8 +395,8 @@ fun ForYouScreenTopicSelection() {
), ),
SaveableNewsResource( SaveableNewsResource(
newsResource = NewsResource( newsResource = NewsResource(
id = 3, id = "3",
episodeId = 52, episodeId = "52",
title = "Community tip on Paging", title = "Community tip on Paging",
content = "Tips for using the Paging library from the developer community", content = "Tips for using the Paging library from the developer community",
url = "https://youtu.be/r5JgIyS3t3s", url = "https://youtu.be/r5JgIyS3t3s",
@ -405,7 +405,7 @@ fun ForYouScreenTopicSelection() {
type = Video, type = Video,
topics = listOf( topics = listOf(
Topic( Topic(
id = 1, id = "1",
name = "UI", name = "UI",
shortDescription = "", shortDescription = "",
longDescription = "", longDescription = "",
@ -421,7 +421,7 @@ fun ForYouScreenTopicSelection() {
authors = listOf( authors = listOf(
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 0, id = "0",
name = "Android Dev", name = "Android Dev",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -431,7 +431,7 @@ fun ForYouScreenTopicSelection() {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -441,7 +441,7 @@ fun ForYouScreenTopicSelection() {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 2, id = "2",
name = "Android Dev 3", name = "Android Dev 3",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",

@ -79,7 +79,7 @@ class ForYouViewModel @Inject constructor(
* This should be persisted to disk instead. * This should be persisted to disk instead.
*/ */
private var savedNewsResources by savedStateHandle.saveable { private var savedNewsResources by savedStateHandle.saveable {
mutableStateOf<Set<Int>>(emptySet()) mutableStateOf<Set<String>>(emptySet())
} }
/** /**
@ -87,7 +87,7 @@ class ForYouViewModel @Inject constructor(
* [SavedStateHandle]. * [SavedStateHandle].
*/ */
private var inProgressTopicSelection by savedStateHandle.saveable { private var inProgressTopicSelection by savedStateHandle.saveable {
mutableStateOf<Set<Int>>(emptySet()) mutableStateOf<Set<String>>(emptySet())
} }
/** /**
@ -95,7 +95,7 @@ class ForYouViewModel @Inject constructor(
* [SavedStateHandle]. * [SavedStateHandle].
*/ */
private var inProgressAuthorSelection by savedStateHandle.saveable { private var inProgressAuthorSelection by savedStateHandle.saveable {
mutableStateOf<Set<Int>>(emptySet()) mutableStateOf<Set<String>>(emptySet())
} }
val uiState: StateFlow<ForYouFeedUiState> = combine( val uiState: StateFlow<ForYouFeedUiState> = combine(
@ -170,7 +170,7 @@ class ForYouViewModel @Inject constructor(
initialValue = ForYouFeedUiState.Loading initialValue = ForYouFeedUiState.Loading
) )
fun updateTopicSelection(topicId: Int, isChecked: Boolean) { fun updateTopicSelection(topicId: String, isChecked: Boolean) {
withMutableSnapshot { withMutableSnapshot {
inProgressTopicSelection = inProgressTopicSelection =
// Update the in-progress selection based on whether the topic id was checked // Update the in-progress selection based on whether the topic id was checked
@ -182,7 +182,7 @@ class ForYouViewModel @Inject constructor(
} }
} }
fun updateAuthorSelection(authorId: Int, isChecked: Boolean) { fun updateAuthorSelection(authorId: String, isChecked: Boolean) {
withMutableSnapshot { withMutableSnapshot {
inProgressAuthorSelection = inProgressAuthorSelection =
// Update the in-progress selection based on whether the author id was checked // Update the in-progress selection based on whether the author id was checked
@ -194,7 +194,7 @@ class ForYouViewModel @Inject constructor(
} }
} }
fun updateNewsResourceSaved(newsResourceId: Int, isChecked: Boolean) { fun updateNewsResourceSaved(newsResourceId: String, isChecked: Boolean) {
withMutableSnapshot { withMutableSnapshot {
savedNewsResources = savedNewsResources =
if (isChecked) { if (isChecked) {
@ -242,8 +242,8 @@ private sealed interface FollowedInterestsState {
* The user has followed the given (non-empty) set of [topicIds] or [authorIds]. * The user has followed the given (non-empty) set of [topicIds] or [authorIds].
*/ */
data class FollowedInterests( data class FollowedInterests(
val topicIds: Set<Int>, val topicIds: Set<String>,
val authorIds: Set<Int> val authorIds: Set<String>
) : FollowedInterestsState ) : FollowedInterestsState
} }

@ -132,7 +132,7 @@ class ForYouViewModelTest {
topics = listOf( topics = listOf(
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 0, id = "0",
name = "Headlines", name = "Headlines",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -143,7 +143,7 @@ class ForYouViewModelTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 1, id = "1",
name = "UI", name = "UI",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -154,7 +154,7 @@ class ForYouViewModelTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 2, id = "2",
name = "Tools", name = "Tools",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -167,7 +167,7 @@ class ForYouViewModelTest {
authors = listOf( authors = listOf(
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 0, id = "0",
name = "Android Dev", name = "Android Dev",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -177,7 +177,7 @@ class ForYouViewModelTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -187,7 +187,7 @@ class ForYouViewModelTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 2, id = "2",
name = "Android Dev 3", name = "Android Dev 3",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -210,9 +210,9 @@ class ForYouViewModelTest {
.test { .test {
awaitItem() awaitItem()
authorsRepository.sendAuthors(sampleAuthors) authorsRepository.sendAuthors(sampleAuthors)
authorsRepository.setFollowedAuthorIds(setOf(0, 1)) authorsRepository.setFollowedAuthorIds(setOf("0", "1"))
topicsRepository.sendTopics(sampleTopics) topicsRepository.sendTopics(sampleTopics)
topicsRepository.setFollowedTopicIds(setOf(0, 1)) topicsRepository.setFollowedTopicIds(setOf("0", "1"))
newsRepository.sendNewsResources(sampleNewsResources) newsRepository.sendNewsResources(sampleNewsResources)
assertEquals( assertEquals(
@ -236,9 +236,9 @@ class ForYouViewModelTest {
.test { .test {
awaitItem() awaitItem()
authorsRepository.sendAuthors(sampleAuthors) authorsRepository.sendAuthors(sampleAuthors)
authorsRepository.setFollowedAuthorIds(setOf(0, 1)) authorsRepository.setFollowedAuthorIds(setOf("0", "1"))
topicsRepository.sendTopics(sampleTopics) topicsRepository.sendTopics(sampleTopics)
topicsRepository.setFollowedTopicIds(setOf(0, 1)) topicsRepository.setFollowedTopicIds(setOf("0", "1"))
newsRepository.sendNewsResources(sampleNewsResources) newsRepository.sendNewsResources(sampleNewsResources)
assertEquals( assertEquals(
@ -268,14 +268,14 @@ class ForYouViewModelTest {
newsRepository.sendNewsResources(sampleNewsResources) newsRepository.sendNewsResources(sampleNewsResources)
awaitItem() awaitItem()
viewModel.updateTopicSelection(1, isChecked = true) viewModel.updateTopicSelection("1", isChecked = true)
assertEquals( assertEquals(
ForYouFeedUiState.PopulatedFeed.FeedWithInterestsSelection( ForYouFeedUiState.PopulatedFeed.FeedWithInterestsSelection(
topics = listOf( topics = listOf(
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 0, id = "0",
name = "Headlines", name = "Headlines",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -286,7 +286,7 @@ class ForYouViewModelTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 1, id = "1",
name = "UI", name = "UI",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -297,7 +297,7 @@ class ForYouViewModelTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 2, id = "2",
name = "Tools", name = "Tools",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -310,7 +310,7 @@ class ForYouViewModelTest {
authors = listOf( authors = listOf(
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 0, id = "0",
name = "Android Dev", name = "Android Dev",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -320,7 +320,7 @@ class ForYouViewModelTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -330,7 +330,7 @@ class ForYouViewModelTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 2, id = "2",
name = "Android Dev 3", name = "Android Dev 3",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -368,14 +368,14 @@ class ForYouViewModelTest {
newsRepository.sendNewsResources(sampleNewsResources) newsRepository.sendNewsResources(sampleNewsResources)
awaitItem() awaitItem()
viewModel.updateAuthorSelection(1, isChecked = true) viewModel.updateAuthorSelection("1", isChecked = true)
assertEquals( assertEquals(
ForYouFeedUiState.PopulatedFeed.FeedWithInterestsSelection( ForYouFeedUiState.PopulatedFeed.FeedWithInterestsSelection(
topics = listOf( topics = listOf(
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 0, id = "0",
name = "Headlines", name = "Headlines",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -386,7 +386,7 @@ class ForYouViewModelTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 1, id = "1",
name = "UI", name = "UI",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -397,7 +397,7 @@ class ForYouViewModelTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 2, id = "2",
name = "Tools", name = "Tools",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -410,7 +410,7 @@ class ForYouViewModelTest {
authors = listOf( authors = listOf(
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 0, id = "0",
name = "Android Dev", name = "Android Dev",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -420,7 +420,7 @@ class ForYouViewModelTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -430,7 +430,7 @@ class ForYouViewModelTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 2, id = "2",
name = "Android Dev 3", name = "Android Dev 3",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -468,17 +468,17 @@ class ForYouViewModelTest {
newsRepository.sendNewsResources(sampleNewsResources) newsRepository.sendNewsResources(sampleNewsResources)
awaitItem() awaitItem()
viewModel.updateTopicSelection(1, isChecked = true) viewModel.updateTopicSelection("1", isChecked = true)
awaitItem() awaitItem()
viewModel.updateTopicSelection(1, isChecked = false) viewModel.updateTopicSelection("1", isChecked = false)
assertEquals( assertEquals(
ForYouFeedUiState.PopulatedFeed.FeedWithInterestsSelection( ForYouFeedUiState.PopulatedFeed.FeedWithInterestsSelection(
topics = listOf( topics = listOf(
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 0, id = "0",
name = "Headlines", name = "Headlines",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -489,7 +489,7 @@ class ForYouViewModelTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 1, id = "1",
name = "UI", name = "UI",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -500,7 +500,7 @@ class ForYouViewModelTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 2, id = "2",
name = "Tools", name = "Tools",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -513,7 +513,7 @@ class ForYouViewModelTest {
authors = listOf( authors = listOf(
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 0, id = "0",
name = "Android Dev", name = "Android Dev",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -523,7 +523,7 @@ class ForYouViewModelTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -533,7 +533,7 @@ class ForYouViewModelTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 2, id = "2",
name = "Android Dev 3", name = "Android Dev 3",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -562,17 +562,17 @@ class ForYouViewModelTest {
newsRepository.sendNewsResources(sampleNewsResources) newsRepository.sendNewsResources(sampleNewsResources)
awaitItem() awaitItem()
viewModel.updateAuthorSelection(1, isChecked = true) viewModel.updateAuthorSelection("1", isChecked = true)
awaitItem() awaitItem()
viewModel.updateAuthorSelection(1, isChecked = false) viewModel.updateAuthorSelection("1", isChecked = false)
assertEquals( assertEquals(
ForYouFeedUiState.PopulatedFeed.FeedWithInterestsSelection( ForYouFeedUiState.PopulatedFeed.FeedWithInterestsSelection(
topics = listOf( topics = listOf(
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 0, id = "0",
name = "Headlines", name = "Headlines",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -583,7 +583,7 @@ class ForYouViewModelTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 1, id = "1",
name = "UI", name = "UI",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -594,7 +594,7 @@ class ForYouViewModelTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 2, id = "2",
name = "Tools", name = "Tools",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -607,7 +607,7 @@ class ForYouViewModelTest {
authors = listOf( authors = listOf(
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 0, id = "0",
name = "Android Dev", name = "Android Dev",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -617,7 +617,7 @@ class ForYouViewModelTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -627,7 +627,7 @@ class ForYouViewModelTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 2, id = "2",
name = "Android Dev 3", name = "Android Dev 3",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -657,7 +657,7 @@ class ForYouViewModelTest {
newsRepository.sendNewsResources(sampleNewsResources) newsRepository.sendNewsResources(sampleNewsResources)
awaitItem() awaitItem()
viewModel.updateTopicSelection(1, isChecked = true) viewModel.updateTopicSelection("1", isChecked = true)
awaitItem() awaitItem()
viewModel.saveFollowedInterests() viewModel.saveFollowedInterests()
@ -678,7 +678,7 @@ class ForYouViewModelTest {
), ),
awaitItem() awaitItem()
) )
assertEquals(setOf(1), topicsRepository.getCurrentFollowedTopics()) assertEquals(setOf("1"), topicsRepository.getCurrentFollowedTopics())
assertEquals(emptySet<Int>(), authorsRepository.getCurrentFollowedAuthors()) assertEquals(emptySet<Int>(), authorsRepository.getCurrentFollowedAuthors())
cancel() cancel()
} }
@ -697,7 +697,7 @@ class ForYouViewModelTest {
newsRepository.sendNewsResources(sampleNewsResources) newsRepository.sendNewsResources(sampleNewsResources)
awaitItem() awaitItem()
viewModel.updateAuthorSelection(0, isChecked = true) viewModel.updateAuthorSelection("0", isChecked = true)
awaitItem() awaitItem()
viewModel.saveFollowedInterests() viewModel.saveFollowedInterests()
@ -715,7 +715,7 @@ class ForYouViewModelTest {
awaitItem() awaitItem()
) )
assertEquals(emptySet<Int>(), topicsRepository.getCurrentFollowedTopics()) assertEquals(emptySet<Int>(), topicsRepository.getCurrentFollowedTopics())
assertEquals(setOf(0), authorsRepository.getCurrentFollowedAuthors()) assertEquals(setOf("0"), authorsRepository.getCurrentFollowedAuthors())
cancel() cancel()
} }
} }
@ -733,8 +733,8 @@ class ForYouViewModelTest {
newsRepository.sendNewsResources(sampleNewsResources) newsRepository.sendNewsResources(sampleNewsResources)
awaitItem() awaitItem()
viewModel.updateAuthorSelection(1, isChecked = true) viewModel.updateAuthorSelection("1", isChecked = true)
viewModel.updateTopicSelection(1, isChecked = true) viewModel.updateTopicSelection("1", isChecked = true)
awaitItem() awaitItem()
viewModel.saveFollowedInterests() viewModel.saveFollowedInterests()
@ -755,8 +755,8 @@ class ForYouViewModelTest {
), ),
awaitItem() awaitItem()
) )
assertEquals(setOf(1), topicsRepository.getCurrentFollowedTopics()) assertEquals(setOf("1"), topicsRepository.getCurrentFollowedTopics())
assertEquals(setOf(1), authorsRepository.getCurrentFollowedAuthors()) assertEquals(setOf("1"), authorsRepository.getCurrentFollowedAuthors())
cancel() cancel()
} }
} }
@ -773,7 +773,7 @@ class ForYouViewModelTest {
newsRepository.sendNewsResources(sampleNewsResources) newsRepository.sendNewsResources(sampleNewsResources)
awaitItem() awaitItem()
viewModel.updateTopicSelection(1, isChecked = true) viewModel.updateTopicSelection("1", isChecked = true)
viewModel.saveFollowedInterests() viewModel.saveFollowedInterests()
awaitItem() awaitItem()
@ -783,7 +783,7 @@ class ForYouViewModelTest {
topics = listOf( topics = listOf(
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 0, id = "0",
name = "Headlines", name = "Headlines",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -794,7 +794,7 @@ class ForYouViewModelTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 1, id = "1",
name = "UI", name = "UI",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -805,7 +805,7 @@ class ForYouViewModelTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 2, id = "2",
name = "Tools", name = "Tools",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -819,7 +819,7 @@ class ForYouViewModelTest {
authors = listOf( authors = listOf(
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 0, id = "0",
name = "Android Dev", name = "Android Dev",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -829,7 +829,7 @@ class ForYouViewModelTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -839,7 +839,7 @@ class ForYouViewModelTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 2, id = "2",
name = "Android Dev 3", name = "Android Dev 3",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -867,7 +867,7 @@ class ForYouViewModelTest {
newsRepository.sendNewsResources(sampleNewsResources) newsRepository.sendNewsResources(sampleNewsResources)
awaitItem() awaitItem()
viewModel.updateAuthorSelection(1, isChecked = true) viewModel.updateAuthorSelection("1", isChecked = true)
viewModel.saveFollowedInterests() viewModel.saveFollowedInterests()
awaitItem() awaitItem()
@ -877,7 +877,7 @@ class ForYouViewModelTest {
topics = listOf( topics = listOf(
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 0, id = "0",
name = "Headlines", name = "Headlines",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -888,7 +888,7 @@ class ForYouViewModelTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 1, id = "1",
name = "UI", name = "UI",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -899,7 +899,7 @@ class ForYouViewModelTest {
), ),
FollowableTopic( FollowableTopic(
topic = Topic( topic = Topic(
id = 2, id = "2",
name = "Tools", name = "Tools",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -913,7 +913,7 @@ class ForYouViewModelTest {
authors = listOf( authors = listOf(
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 0, id = "0",
name = "Android Dev", name = "Android Dev",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -923,7 +923,7 @@ class ForYouViewModelTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -933,7 +933,7 @@ class ForYouViewModelTest {
), ),
FollowableAuthor( FollowableAuthor(
author = Author( author = Author(
id = 2, id = "2",
name = "Android Dev 3", name = "Android Dev 3",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -955,11 +955,11 @@ class ForYouViewModelTest {
.test { .test {
awaitItem() awaitItem()
topicsRepository.sendTopics(sampleTopics) topicsRepository.sendTopics(sampleTopics)
topicsRepository.setFollowedTopicIds(setOf(1)) topicsRepository.setFollowedTopicIds(setOf("1"))
authorsRepository.sendAuthors(sampleAuthors) authorsRepository.sendAuthors(sampleAuthors)
authorsRepository.setFollowedAuthorIds(setOf(1)) authorsRepository.setFollowedAuthorIds(setOf("1"))
newsRepository.sendNewsResources(sampleNewsResources) newsRepository.sendNewsResources(sampleNewsResources)
viewModel.updateNewsResourceSaved(2, true) viewModel.updateNewsResourceSaved("2", true)
assertEquals( assertEquals(
ForYouFeedUiState.PopulatedFeed.FeedWithoutTopicSelection( ForYouFeedUiState.PopulatedFeed.FeedWithoutTopicSelection(
@ -983,21 +983,21 @@ class ForYouViewModelTest {
private val sampleAuthors = listOf( private val sampleAuthors = listOf(
Author( Author(
id = 0, id = "0",
name = "Android Dev", name = "Android Dev",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
mediumPage = "" mediumPage = ""
), ),
Author( Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
mediumPage = "" mediumPage = ""
), ),
Author( Author(
id = 2, id = "2",
name = "Android Dev 3", name = "Android Dev 3",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -1007,7 +1007,7 @@ private val sampleAuthors = listOf(
private val sampleTopics = listOf( private val sampleTopics = listOf(
Topic( Topic(
id = 0, id = "0",
name = "Headlines", name = "Headlines",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -1015,7 +1015,7 @@ private val sampleTopics = listOf(
imageUrl = "image URL", imageUrl = "image URL",
), ),
Topic( Topic(
id = 1, id = "1",
name = "UI", name = "UI",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -1023,7 +1023,7 @@ private val sampleTopics = listOf(
imageUrl = "image URL", imageUrl = "image URL",
), ),
Topic( Topic(
id = 2, id = "2",
name = "Tools", name = "Tools",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -1034,8 +1034,8 @@ private val sampleTopics = listOf(
private val sampleNewsResources = listOf( private val sampleNewsResources = listOf(
NewsResource( NewsResource(
id = 1, id = "1",
episodeId = 52, episodeId = "52",
title = "Thanks for helping us reach 1M YouTube Subscribers", title = "Thanks for helping us reach 1M YouTube Subscribers",
content = "Thank you everyone for following the Now in Android series and everything the " + 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 " + "Android Developers YouTube channel has to offer. During the Android Developer " +
@ -1047,7 +1047,7 @@ private val sampleNewsResources = listOf(
type = Video, type = Video,
topics = listOf( topics = listOf(
Topic( Topic(
id = 0, id = "0",
name = "Headlines", name = "Headlines",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -1057,7 +1057,7 @@ private val sampleNewsResources = listOf(
), ),
authors = listOf( authors = listOf(
Author( Author(
id = 0, id = "0",
name = "Android Dev", name = "Android Dev",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -1066,8 +1066,8 @@ private val sampleNewsResources = listOf(
) )
), ),
NewsResource( NewsResource(
id = 2, id = "2",
episodeId = 52, episodeId = "52",
title = "Transformations and customisations in the Paging Library", title = "Transformations and customisations in the Paging Library",
content = "A demonstration of different operations that can be performed with Paging. " + content = "A demonstration of different operations that can be performed with Paging. " +
"Transformations like inserting separators, when to create a new pager, and " + "Transformations like inserting separators, when to create a new pager, and " +
@ -1078,7 +1078,7 @@ private val sampleNewsResources = listOf(
type = Video, type = Video,
topics = listOf( topics = listOf(
Topic( Topic(
id = 1, id = "1",
name = "UI", name = "UI",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -1088,7 +1088,7 @@ private val sampleNewsResources = listOf(
), ),
authors = listOf( authors = listOf(
Author( Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",
@ -1097,8 +1097,8 @@ private val sampleNewsResources = listOf(
) )
), ),
NewsResource( NewsResource(
id = 3, id = "3",
episodeId = 52, episodeId = "52",
title = "Community tip on Paging", title = "Community tip on Paging",
content = "Tips for using the Paging library from the developer community", content = "Tips for using the Paging library from the developer community",
url = "https://youtu.be/r5JgIyS3t3s", url = "https://youtu.be/r5JgIyS3t3s",
@ -1107,7 +1107,7 @@ private val sampleNewsResources = listOf(
type = Video, type = Video,
topics = listOf( topics = listOf(
Topic( Topic(
id = 1, id = "1",
name = "UI", name = "UI",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",
@ -1117,7 +1117,7 @@ private val sampleNewsResources = listOf(
), ),
authors = listOf( authors = listOf(
Author( Author(
id = 1, id = "1",
name = "Android Dev 2", name = "Android Dev 2",
imageUrl = "", imageUrl = "",
twitter = "", twitter = "",

@ -130,7 +130,7 @@ private const val TOPIC_DESC = "At vero eos et accusamus et iusto odio dignissim
private val testTopics = listOf( private val testTopics = listOf(
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 0, id = "0",
name = TOPIC_1_NAME, name = TOPIC_1_NAME,
shortDescription = "", shortDescription = "",
longDescription = TOPIC_DESC, longDescription = TOPIC_DESC,
@ -141,7 +141,7 @@ private val testTopics = listOf(
), ),
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 1, id = "1",
name = TOPIC_2_NAME, name = TOPIC_2_NAME,
shortDescription = "", shortDescription = "",
longDescription = TOPIC_DESC, longDescription = TOPIC_DESC,
@ -152,7 +152,7 @@ private val testTopics = listOf(
), ),
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 2, id = "2",
name = TOPIC_3_NAME, name = TOPIC_3_NAME,
shortDescription = "", shortDescription = "",
longDescription = TOPIC_DESC, longDescription = TOPIC_DESC,
@ -165,8 +165,8 @@ private val testTopics = listOf(
private val sampleNewsResources = listOf( private val sampleNewsResources = listOf(
NewsResource( NewsResource(
id = 1, id = "1",
episodeId = 52, episodeId = "52",
title = "Thanks for helping us reach 1M YouTube Subscribers", title = "Thanks for helping us reach 1M YouTube Subscribers",
content = "Thank you everyone for following the Now in Android series and everything the " + 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 " + "Android Developers YouTube channel has to offer. During the Android Developer " +
@ -178,7 +178,7 @@ private val sampleNewsResources = listOf(
type = Video, type = Video,
topics = listOf( topics = listOf(
Topic( Topic(
id = 0, id = "0",
name = "Headlines", name = "Headlines",
shortDescription = "", shortDescription = "",
longDescription = TOPIC_DESC, longDescription = TOPIC_DESC,

@ -42,10 +42,10 @@ class TopicViewModel @Inject constructor(
newsRepository: NewsRepository newsRepository: NewsRepository
) : ViewModel() { ) : ViewModel() {
private val topicId: Int = checkNotNull(savedStateHandle[TopicDestinationsArgs.TOPIC_ID_ARG]) private val topicId: String = checkNotNull(savedStateHandle[TopicDestinationsArgs.TOPIC_ID_ARG])
// Observe the followed topics, as they could change over time. // Observe the followed topics, as they could change over time.
private val followedTopicIdsStream: Flow<Result<Set<Int>>> = private val followedTopicIdsStream: Flow<Result<Set<String>>> =
topicsRepository.getFollowedTopicIdsStream().asResult() topicsRepository.getFollowedTopicIdsStream().asResult()
// Observe topic information // Observe topic information

@ -137,7 +137,7 @@ private const val TOPIC_IMAGE_URL = "Image URL"
private val testInputTopics = listOf( private val testInputTopics = listOf(
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 0, id = "0",
name = TOPIC_1_NAME, name = TOPIC_1_NAME,
shortDescription = TOPIC_SHORT_DESC, shortDescription = TOPIC_SHORT_DESC,
longDescription = TOPIC_LONG_DESC, longDescription = TOPIC_LONG_DESC,
@ -148,7 +148,7 @@ private val testInputTopics = listOf(
), ),
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 1, id = "1",
name = TOPIC_2_NAME, name = TOPIC_2_NAME,
shortDescription = TOPIC_SHORT_DESC, shortDescription = TOPIC_SHORT_DESC,
longDescription = TOPIC_LONG_DESC, longDescription = TOPIC_LONG_DESC,
@ -159,7 +159,7 @@ private val testInputTopics = listOf(
), ),
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 2, id = "2",
name = TOPIC_3_NAME, name = TOPIC_3_NAME,
shortDescription = TOPIC_SHORT_DESC, shortDescription = TOPIC_SHORT_DESC,
longDescription = TOPIC_LONG_DESC, longDescription = TOPIC_LONG_DESC,
@ -173,7 +173,7 @@ private val testInputTopics = listOf(
private val testOutputTopics = listOf( private val testOutputTopics = listOf(
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 0, id = "0",
name = TOPIC_1_NAME, name = TOPIC_1_NAME,
shortDescription = TOPIC_SHORT_DESC, shortDescription = TOPIC_SHORT_DESC,
longDescription = TOPIC_LONG_DESC, longDescription = TOPIC_LONG_DESC,
@ -184,7 +184,7 @@ private val testOutputTopics = listOf(
), ),
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 1, id = "1",
name = TOPIC_2_NAME, name = TOPIC_2_NAME,
shortDescription = TOPIC_SHORT_DESC, shortDescription = TOPIC_SHORT_DESC,
longDescription = TOPIC_LONG_DESC, longDescription = TOPIC_LONG_DESC,
@ -195,7 +195,7 @@ private val testOutputTopics = listOf(
), ),
FollowableTopic( FollowableTopic(
Topic( Topic(
id = 2, id = "2",
name = TOPIC_3_NAME, name = TOPIC_3_NAME,
shortDescription = TOPIC_SHORT_DESC, shortDescription = TOPIC_SHORT_DESC,
longDescription = TOPIC_LONG_DESC, longDescription = TOPIC_LONG_DESC,
@ -208,8 +208,8 @@ private val testOutputTopics = listOf(
private val sampleNewsResources = listOf( private val sampleNewsResources = listOf(
NewsResource( NewsResource(
id = 1, id = "1",
episodeId = 52, episodeId = "52",
title = "Thanks for helping us reach 1M YouTube Subscribers", title = "Thanks for helping us reach 1M YouTube Subscribers",
content = "Thank you everyone for following the Now in Android series and everything the " + 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 " + "Android Developers YouTube channel has to offer. During the Android Developer " +
@ -221,7 +221,7 @@ private val sampleNewsResources = listOf(
type = Video, type = Video,
topics = listOf( topics = listOf(
Topic( Topic(
id = 0, id = "0",
name = "Headlines", name = "Headlines",
shortDescription = "", shortDescription = "",
longDescription = "long description", longDescription = "long description",

Loading…
Cancel
Save