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

@ -44,7 +44,7 @@ import com.google.samples.apps.nowinandroid.core.database.util.NewsResourceTypeC
NewsResourceTopicCrossRef::class,
TopicEntity::class,
],
version = 7,
version = 8,
autoMigrations = [
AutoMigration(from = 1, to = 2),
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 = 5, to = 6),
AutoMigration(from = 6, to = 7),
AutoMigration(from = 7, to = 8),
],
exportSchema = true,
)

@ -64,5 +64,5 @@ interface AuthorDao {
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)
"""
)
suspend fun deleteEpisodes(ids: List<Int>)
suspend fun deleteEpisodes(ids: List<String>)
}

@ -59,8 +59,8 @@ interface NewsResourceDao {
"""
)
fun getNewsResourcesStream(
filterAuthorIds: Set<Int> = emptySet(),
filterTopicIds: Set<Int> = emptySet(),
filterAuthorIds: Set<String> = emptySet(),
filterTopicIds: Set<String> = emptySet(),
): Flow<List<PopulatedNewsResource>>
/**
@ -104,5 +104,5 @@ interface NewsResourceDao {
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
"""
)
fun getTopicEntity(topicId: Int): Flow<TopicEntity>
fun getTopicEntity(topicId: String): Flow<TopicEntity>
@Query(value = "SELECT * FROM topics")
fun getTopicEntitiesStream(): Flow<List<TopicEntity>>
@ -47,7 +47,7 @@ interface TopicDao {
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
@ -80,5 +80,5 @@ interface TopicDao {
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(
@PrimaryKey
val id: Int,
val id: String,
val name: String,
@ColumnInfo(name = "image_url")
val imageUrl: String,

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

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

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

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

@ -48,7 +48,7 @@ import androidx.room.Index
)
data class NewsResourceTopicCrossRef(
@ColumnInfo(name = "news_resource_id")
val newsResourceId: Int,
val newsResourceId: String,
@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(
@PrimaryKey
val id: Int,
val id: String,
val name: String,
val shortDescription: String,
@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(
private val userPreferences: DataStore<UserPreferences>
) {
suspend fun setFollowedTopicIds(followedTopicIds: Set<Int>) {
suspend fun setFollowedTopicIds(followedTopicIds: Set<String>) {
try {
userPreferences.updateData {
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 {
userPreferences.updateData {
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 {
Log.e("NiaPreferences", "Failed to read user preferences", it)
true
@ -105,7 +105,7 @@ class NiaPreferences @Inject constructor(
}
}
suspend fun setFollowedAuthorIds(followedAuthorIds: Set<Int>) {
suspend fun setFollowedAuthorIds(followedAuthorIds: Set<String>) {
try {
userPreferences.updateData {
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 {
userPreferences.updateData {
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 {
Log.e("NiaPreferences", "Failed to read user preferences", it)
true

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

@ -21,10 +21,13 @@ option java_multiple_files = true;
message UserPreferences {
reserved 2;
repeated int32 followed_topic_ids = 1;
repeated int32 deprecated_int_followed_topic_ids = 1;
int32 topicChangeListVersion = 3;
int32 authorChangeListVersion = 4;
int32 episodeChangeListVersion = 5;
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
fun writingAndReadingUserPreferences_outputsCorrectValue() = runTest {
val expectedUserPreferences = userPreferences {
followedTopicIds.add(0)
followedTopicIds.add(1)
followedTopicIds.add("0")
followedTopicIds.add("1")
}
val outputStream = ByteArrayOutputStream()

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

@ -29,15 +29,15 @@ interface AuthorsRepository : Syncable {
/**
* 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
*/
suspend fun toggleFollowedAuthorId(followedAuthorId: Int, followed: Boolean)
suspend fun toggleFollowedAuthorId(followedAuthorId: String, followed: Boolean)
/**
* 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()
.map { it.map(AuthorEntity::asExternalModel) }
override suspend fun setFollowedAuthorIds(followedAuthorIds: Set<Int>) =
override suspend fun setFollowedAuthorIds(followedAuthorIds: Set<String>) =
niaPreferences.setFollowedAuthorIds(followedAuthorIds)
override suspend fun toggleFollowedAuthorId(followedAuthorId: Int, followed: Boolean) =
override suspend fun toggleFollowedAuthorId(followedAuthorId: String, followed: Boolean) =
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 =
synchronizer.changeListSync(

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

@ -44,13 +44,13 @@ class LocalTopicsRepository @Inject constructor(
topicDao.getTopicEntitiesStream()
.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() }
override suspend fun setFollowedTopicIds(followedTopicIds: Set<Int>) =
override suspend fun setFollowedTopicIds(followedTopicIds: Set<String>) =
niaPreferences.setFollowedTopicIds(followedTopicIds)
override suspend fun toggleFollowedTopicId(followedTopicId: Int, followed: Boolean) =
override suspend fun toggleFollowedTopicId(followedTopicId: String, followed: Boolean) =
niaPreferences.toggleFollowedTopicId(followedTopicId, followed)
override fun getFollowedTopicIdsStream() = niaPreferences.followedTopicIds

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

@ -29,20 +29,20 @@ interface TopicsRepository : Syncable {
/**
* 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
*/
suspend fun setFollowedTopicIds(followedTopicIds: Set<Int>)
suspend fun setFollowedTopicIds(followedTopicIds: Set<String>)
/**
* 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
*/
fun getFollowedTopicIdsStream(): Flow<Set<Int>>
fun getFollowedTopicIdsStream(): Flow<Set<String>>
}

@ -59,15 +59,15 @@ class FakeAuthorsRepository @Inject constructor(
}
.flowOn(ioDispatcher)
override suspend fun setFollowedAuthorIds(followedAuthorIds: Set<Int>) {
override suspend fun setFollowedAuthorIds(followedAuthorIds: Set<String>) {
niaPreferences.setFollowedAuthorIds(followedAuthorIds)
}
override suspend fun toggleFollowedAuthorId(followedAuthorId: Int, followed: Boolean) {
override suspend fun toggleFollowedAuthorId(followedAuthorId: String, followed: Boolean) {
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
}

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

@ -61,14 +61,14 @@ class FakeTopicsRepository @Inject constructor(
}
.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 } }
}
override suspend fun setFollowedTopicIds(followedTopicIds: Set<Int>) =
override suspend fun setFollowedTopicIds(followedTopicIds: Set<String>) =
niaPreferences.setFollowedTopicIds(followedTopicIds)
override suspend fun toggleFollowedTopicId(followedTopicId: Int, followed: Boolean) =
override suspend fun toggleFollowedTopicId(followedTopicId: String, followed: Boolean) =
niaPreferences.toggleFollowedTopicId(followedTopicId, followed)
override fun getFollowedTopicIdsStream() = niaPreferences.followedTopicIds

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

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

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

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

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

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

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

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

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

@ -58,19 +58,19 @@ class TestNiaNetwork : NiANetwork {
.mapToChangeList(idGetter = NetworkNewsResource::id),
)
override suspend fun getTopics(ids: List<Int>?): List<NetworkTopic> =
override suspend fun getTopics(ids: List<String>?): List<NetworkTopic> =
allTopics.matchIds(
ids = ids,
idGetter = NetworkTopic::id
)
override suspend fun getAuthors(ids: List<Int>?): List<NetworkAuthor> =
override suspend fun getAuthors(ids: List<String>?): List<NetworkAuthor> =
allAuthors.matchIds(
ids = ids,
idGetter = NetworkAuthor::id
)
override suspend fun getNewsResources(ids: List<Int>?): List<NetworkNewsResource> =
override suspend fun getNewsResources(ids: List<String>?): List<NetworkNewsResource> =
allNewsResources.matchIds(
ids = ids,
idGetter = NetworkNewsResource::id
@ -95,7 +95,7 @@ class TestNiaNetwork : NiANetwork {
* Edits the change list for the backing [collectionType] for the given [id] mimicking
* 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 latestVersion = changeList.lastOrNull()?.changeListVersion ?: 0
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
*/
private fun <T> List<T>.matchIds(
ids: List<Int>?,
idGetter: (T) -> Int
ids: List<String>?,
idGetter: (T) -> String
) = when (ids) {
null -> this
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
*/
private fun <T> List<T>.mapToChangeList(
idGetter: (T) -> Int
idGetter: (T) -> String
) = mapIndexed { index, item ->
NetworkChangeList(
id = idGetter(item),

@ -31,7 +31,7 @@ class TestTopicDao : TopicDao {
private var entitiesStateFlow = MutableStateFlow(
listOf(
TopicEntity(
id = 1,
id = "1",
name = "Topic",
shortDescription = "short 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")
}
override fun getTopicEntitiesStream(): Flow<List<TopicEntity>> =
entitiesStateFlow
override fun getTopicEntitiesStream(ids: Set<Int>): Flow<List<TopicEntity>> =
override fun getTopicEntitiesStream(ids: Set<String>): Flow<List<TopicEntity>> =
getTopicEntitiesStream()
.map { topics -> topics.filter { it.id in ids } }
@ -62,7 +62,7 @@ class TestTopicDao : TopicDao {
throw NotImplementedError("Unused in tests")
}
override suspend fun deleteTopics(ids: List<Int>) {
override suspend fun deleteTopics(ids: List<String>) {
val idSet = ids.toSet()
entitiesStateFlow.update { entities ->
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
*/
data class Author(
val id: Int,
val id: String,
val name: String,
val imageUrl: String,
val twitter: String,

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

@ -22,8 +22,8 @@ import kotlinx.datetime.Instant
* External data layer representation of a fully populated NiA news resource
*/
data class NewsResource(
val id: Int,
val episodeId: Int,
val id: String,
val episodeId: String,
val title: String,
val content: 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
*/
data class Topic(
val id: Int,
val id: String,
val name: String,
val shortDescription: 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 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>

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

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

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

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

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

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

@ -39,17 +39,17 @@ import retrofit2.http.Query
private interface RetrofitNiANetworkApi {
@GET(value = "topics")
suspend fun getTopics(
@Query("id") ids: List<Int>?,
@Query("id") ids: List<String>?,
): NetworkResponse<List<NetworkTopic>>
@GET(value = "authors")
suspend fun getAuthors(
@Query("id") ids: List<Int>?,
@Query("id") ids: List<String>?,
): NetworkResponse<List<NetworkAuthor>>
@GET(value = "newsresources")
suspend fun getNewsResources(
@Query("id") ids: List<Int>?,
@Query("id") ids: List<String>?,
): NetworkResponse<List<NetworkNewsResource>>
@GET(value = "changelists/topics")
@ -102,13 +102,13 @@ class RetrofitNiANetwork @Inject constructor(
.build()
.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
override suspend fun getAuthors(ids: List<Int>?): List<NetworkAuthor> =
override suspend fun getAuthors(ids: List<String>?): List<NetworkAuthor> =
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
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.
*/
private val _followedAuthorIds: MutableSharedFlow<Set<Int>> =
private val _followedAuthorIds: MutableSharedFlow<Set<String>> =
MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
/**
@ -38,13 +38,13 @@ class TestAuthorsRepository : AuthorsRepository {
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)
}
override suspend fun toggleFollowedAuthorId(followedAuthorId: Int, followed: Boolean) {
override suspend fun toggleFollowedAuthorId(followedAuthorId: String, followed: Boolean) {
getCurrentFollowedAuthors()?.let { current ->
_followedAuthorIds.tryEmit(
if (followed) current.plus(followedAuthorId)
@ -65,5 +65,5 @@ class TestAuthorsRepository : AuthorsRepository {
/**
* 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(
filterAuthorIds: Set<Int>,
filterTopicIds: Set<Int>
filterAuthorIds: Set<String>,
filterTopicIds: Set<String>
): Flow<List<NewsResource>> =
getNewsResourcesStream().map { newsResources ->
newsResources

@ -28,7 +28,7 @@ class TestTopicsRepository : TopicsRepository {
/**
* 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)
/**
@ -39,15 +39,15 @@ class TestTopicsRepository : TopicsRepository {
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 }!! }
}
override suspend fun setFollowedTopicIds(followedTopicIds: Set<Int>) {
override suspend fun setFollowedTopicIds(followedTopicIds: Set<String>) {
_followedTopicIds.tryEmit(followedTopicIds)
}
override suspend fun toggleFollowedTopicId(followedTopicId: Int, followed: Boolean) {
override suspend fun toggleFollowedTopicId(followedTopicId: String, followed: Boolean) {
getCurrentFollowedTopics()?.let { current ->
_followedTopicIds.tryEmit(
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.
@ -68,7 +68,7 @@ class TestTopicsRepository : TopicsRepository {
/**
* 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
}

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

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

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

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

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

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

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

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

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

@ -79,7 +79,7 @@ class ForYouViewModel @Inject constructor(
* This should be persisted to disk instead.
*/
private var savedNewsResources by savedStateHandle.saveable {
mutableStateOf<Set<Int>>(emptySet())
mutableStateOf<Set<String>>(emptySet())
}
/**
@ -87,7 +87,7 @@ class ForYouViewModel @Inject constructor(
* [SavedStateHandle].
*/
private var inProgressTopicSelection by savedStateHandle.saveable {
mutableStateOf<Set<Int>>(emptySet())
mutableStateOf<Set<String>>(emptySet())
}
/**
@ -95,7 +95,7 @@ class ForYouViewModel @Inject constructor(
* [SavedStateHandle].
*/
private var inProgressAuthorSelection by savedStateHandle.saveable {
mutableStateOf<Set<Int>>(emptySet())
mutableStateOf<Set<String>>(emptySet())
}
val uiState: StateFlow<ForYouFeedUiState> = combine(
@ -170,7 +170,7 @@ class ForYouViewModel @Inject constructor(
initialValue = ForYouFeedUiState.Loading
)
fun updateTopicSelection(topicId: Int, isChecked: Boolean) {
fun updateTopicSelection(topicId: String, isChecked: Boolean) {
withMutableSnapshot {
inProgressTopicSelection =
// 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 {
inProgressAuthorSelection =
// 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 {
savedNewsResources =
if (isChecked) {
@ -242,8 +242,8 @@ private sealed interface FollowedInterestsState {
* The user has followed the given (non-empty) set of [topicIds] or [authorIds].
*/
data class FollowedInterests(
val topicIds: Set<Int>,
val authorIds: Set<Int>
val topicIds: Set<String>,
val authorIds: Set<String>
) : FollowedInterestsState
}

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

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

@ -42,10 +42,10 @@ class TopicViewModel @Inject constructor(
newsRepository: NewsRepository
) : 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.
private val followedTopicIdsStream: Flow<Result<Set<Int>>> =
private val followedTopicIdsStream: Flow<Result<Set<String>>> =
topicsRepository.getFollowedTopicIdsStream().asResult()
// Observe topic information

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

Loading…
Cancel
Save