diff --git a/core-database/schemas/com.google.samples.apps.nowinandroid.core.database.NiADatabase/5.json b/core-database/schemas/com.google.samples.apps.nowinandroid.core.database.NiADatabase/5.json new file mode 100644 index 000000000..789cfd864 --- /dev/null +++ b/core-database/schemas/com.google.samples.apps.nowinandroid.core.database.NiADatabase/5.json @@ -0,0 +1,401 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "fdb65d28086c5a10d129b905ce940aa4", + "entities": [ + { + "tableName": "authors", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER 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": "INTEGER", + "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": [ + { + "name": "index_authors_name", + "unique": true, + "columnNames": [ + "name" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_authors_name` ON `${TABLE_NAME}` (`name`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "episodes_authors", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`episode_id` INTEGER NOT NULL, `author_id` INTEGER 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": "INTEGER", + "notNull": true + }, + { + "fieldPath": "authorId", + "columnName": "author_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "episode_id", + "author_id" + ], + "autoGenerate": false + }, + "indices": [], + "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` INTEGER 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": "INTEGER", + "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` INTEGER NOT NULL, `author_id` INTEGER 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": "INTEGER", + "notNull": true + }, + { + "fieldPath": "authorId", + "columnName": "author_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "news_resource_id", + "author_id" + ], + "autoGenerate": false + }, + "indices": [], + "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` INTEGER NOT NULL, `episode_id` INTEGER 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": "INTEGER", + "notNull": true + }, + { + "fieldPath": "episodeId", + "columnName": "episode_id", + "affinity": "INTEGER", + "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` INTEGER NOT NULL, `topic_id` INTEGER 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": "INTEGER", + "notNull": true + }, + { + "fieldPath": "topicId", + "columnName": "topic_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "news_resource_id", + "topic_id" + ], + "autoGenerate": false + }, + "indices": [], + "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` INTEGER 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": "INTEGER", + "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": [ + { + "name": "index_topics_name", + "unique": true, + "columnNames": [ + "name" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_topics_name` ON `${TABLE_NAME}` (`name`)" + } + ], + "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, 'fdb65d28086c5a10d129b905ce940aa4')" + ] + } +} \ No newline at end of file diff --git a/core-database/src/main/java/com/google/samples/apps/nowinandroid/core/database/NiADatabase.kt b/core-database/src/main/java/com/google/samples/apps/nowinandroid/core/database/NiADatabase.kt index 270ddb418..9554da640 100644 --- a/core-database/src/main/java/com/google/samples/apps/nowinandroid/core/database/NiADatabase.kt +++ b/core-database/src/main/java/com/google/samples/apps/nowinandroid/core/database/NiADatabase.kt @@ -44,11 +44,12 @@ import com.google.samples.apps.nowinandroid.core.database.util.NewsResourceTypeC NewsResourceTopicCrossRef::class, TopicEntity::class, ], - version = 4, + version = 5, autoMigrations = [ AutoMigration(from = 1, to = 2), AutoMigration(from = 2, to = 3, spec = DatabaseMigrations.Schema2to3::class), AutoMigration(from = 3, to = 4), + AutoMigration(from = 4, to = 5), ], exportSchema = true, ) diff --git a/core-database/src/main/java/com/google/samples/apps/nowinandroid/core/database/model/AuthorEntity.kt b/core-database/src/main/java/com/google/samples/apps/nowinandroid/core/database/model/AuthorEntity.kt index be219fd78..7e6d30d2b 100644 --- a/core-database/src/main/java/com/google/samples/apps/nowinandroid/core/database/model/AuthorEntity.kt +++ b/core-database/src/main/java/com/google/samples/apps/nowinandroid/core/database/model/AuthorEntity.kt @@ -38,10 +38,16 @@ data class AuthorEntity( val name: String, @ColumnInfo(name = "image_url") val imageUrl: String, + @ColumnInfo(defaultValue = "") + val twitter: String, + @ColumnInfo(name = "medium_page", defaultValue = "") + val mediumPage: String, ) fun AuthorEntity.asExternalModel() = Author( id = id, name = name, imageUrl = imageUrl, + twitter = twitter, + mediumPage = mediumPage, ) diff --git a/core-domain-test/src/main/java/com/google/samples/apps/nowinandroid/core/domain/test/TestDomainModule.kt b/core-domain-test/src/main/java/com/google/samples/apps/nowinandroid/core/domain/test/TestDomainModule.kt index 08f514562..7a1bfab15 100644 --- a/core-domain-test/src/main/java/com/google/samples/apps/nowinandroid/core/domain/test/TestDomainModule.kt +++ b/core-domain-test/src/main/java/com/google/samples/apps/nowinandroid/core/domain/test/TestDomainModule.kt @@ -17,8 +17,10 @@ package com.google.samples.apps.nowinandroid.core.domain.test import com.google.samples.apps.nowinandroid.core.domain.di.DomainModule +import com.google.samples.apps.nowinandroid.core.domain.repository.AuthorsRepository import com.google.samples.apps.nowinandroid.core.domain.repository.NewsRepository import com.google.samples.apps.nowinandroid.core.domain.repository.TopicsRepository +import com.google.samples.apps.nowinandroid.core.domain.repository.fake.FakeAuthorsRepository import com.google.samples.apps.nowinandroid.core.domain.repository.fake.FakeNewsRepository import com.google.samples.apps.nowinandroid.core.domain.repository.fake.FakeTopicsRepository import dagger.Binds @@ -37,6 +39,11 @@ interface TestDomainModule { fakeTopicsRepository: FakeTopicsRepository ): TopicsRepository + @Binds + fun bindsAuthorRepository( + fakeAuthorsRepository: FakeAuthorsRepository + ): AuthorsRepository + @Binds fun bindsNewsResourceRepository( fakeNewsRepository: FakeNewsRepository diff --git a/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/di/DomainModule.kt b/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/di/DomainModule.kt index 8a549ca9f..212866a33 100644 --- a/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/di/DomainModule.kt +++ b/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/di/DomainModule.kt @@ -16,6 +16,8 @@ package com.google.samples.apps.nowinandroid.core.domain.di +import com.google.samples.apps.nowinandroid.core.domain.repository.AuthorsRepository +import com.google.samples.apps.nowinandroid.core.domain.repository.LocalAuthorsRepository import com.google.samples.apps.nowinandroid.core.domain.repository.LocalNewsRepository import com.google.samples.apps.nowinandroid.core.domain.repository.LocalTopicsRepository import com.google.samples.apps.nowinandroid.core.domain.repository.NewsRepository @@ -34,6 +36,11 @@ interface DomainModule { topicsRepository: LocalTopicsRepository ): TopicsRepository + @Binds + fun bindsAuthorsRepository( + authorsRepository: LocalAuthorsRepository + ): AuthorsRepository + @Binds fun bindsNewsResourceRepository( newsRepository: LocalNewsRepository diff --git a/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/Author.kt b/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/Author.kt index 4cf2e93ed..434545fa0 100644 --- a/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/Author.kt +++ b/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/Author.kt @@ -22,5 +22,7 @@ import com.google.samples.apps.nowinandroid.core.network.model.NetworkAuthor fun NetworkAuthor.asEntity() = AuthorEntity( id = id, name = name, - imageUrl = imageUrl + imageUrl = imageUrl, + twitter = twitter, + mediumPage = mediumPage, ) diff --git a/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/NewsResource.kt b/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/NewsResource.kt index 0d6e8d7b7..be6ac50d1 100644 --- a/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/NewsResource.kt +++ b/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/NewsResource.kt @@ -69,6 +69,8 @@ fun NetworkNewsResource.authorEntityShells() = id = authorId, name = "", imageUrl = "", + twitter = "", + mediumPage = "", ) } diff --git a/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/repository/AuthorsRepository.kt b/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/repository/AuthorsRepository.kt new file mode 100644 index 000000000..2fc5e6f7d --- /dev/null +++ b/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/repository/AuthorsRepository.kt @@ -0,0 +1,33 @@ +/* + * 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.domain.repository + +import com.google.samples.apps.nowinandroid.core.model.data.Author +import kotlinx.coroutines.flow.Flow + +interface AuthorsRepository { + /** + * Gets the available Authors as a stream + */ + fun getAuthorsStream(): Flow> + + /** + * Synchronizes the local database in backing the repository with the network. + * Returns if the sync was successful or not. + */ + suspend fun sync(): Boolean +} diff --git a/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/repository/LocalAuthorsRepository.kt b/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/repository/LocalAuthorsRepository.kt new file mode 100644 index 000000000..0e4ddb59a --- /dev/null +++ b/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/repository/LocalAuthorsRepository.kt @@ -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.domain.repository + +import com.google.samples.apps.nowinandroid.core.database.dao.AuthorDao +import com.google.samples.apps.nowinandroid.core.database.model.AuthorEntity +import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel +import com.google.samples.apps.nowinandroid.core.domain.model.asEntity +import com.google.samples.apps.nowinandroid.core.domain.suspendRunCatching +import com.google.samples.apps.nowinandroid.core.model.data.Author +import com.google.samples.apps.nowinandroid.core.network.NiANetwork +import com.google.samples.apps.nowinandroid.core.network.model.NetworkAuthor +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** + * Room database backed implementation of the [AuthorsRepository]. + */ +class LocalAuthorsRepository @Inject constructor( + private val authorDao: AuthorDao, + private val network: NiANetwork, +) : AuthorsRepository { + + override fun getAuthorsStream(): Flow> = + authorDao.getAuthorEntitiesStream() + .map { it.map(AuthorEntity::asExternalModel) } + + // TODO: Pass change list for incremental sync. See b/227206738 + override suspend fun sync(): Boolean = suspendRunCatching { + val networkAuthors = network.getAuthors() + authorDao.upsertAuthors( + entities = networkAuthors.map(NetworkAuthor::asEntity) + ) + }.isSuccess +} diff --git a/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/repository/fake/FakeAuthorsRepository.kt b/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/repository/fake/FakeAuthorsRepository.kt new file mode 100644 index 000000000..5ac6fb4aa --- /dev/null +++ b/core-domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/repository/fake/FakeAuthorsRepository.kt @@ -0,0 +1,60 @@ +/* + * 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.domain.repository.fake + +import com.google.samples.apps.nowinandroid.core.domain.repository.AuthorsRepository +import com.google.samples.apps.nowinandroid.core.model.data.Author +import com.google.samples.apps.nowinandroid.core.network.Dispatcher +import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO +import com.google.samples.apps.nowinandroid.core.network.fake.FakeDataSource +import com.google.samples.apps.nowinandroid.core.network.model.NetworkAuthor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json + +/** + * Fake implementation of the [AuthorsRepository] that retrieves the Authors from a JSON String, and + * uses a local DataStore instance to save and retrieve followed Author ids. + * + * This allows us to run the app with fake data, without needing an internet connection or working + * backend. + */ +class FakeAuthorsRepository @Inject constructor( + @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, + private val networkJson: Json, +) : AuthorsRepository { + override fun getAuthorsStream(): Flow> = flow { + emit( + networkJson.decodeFromString>(FakeDataSource.authors).map { + Author( + id = it.id, + name = it.name, + imageUrl = it.imageUrl, + twitter = it.twitter, + mediumPage = it.mediumPage, + ) + } + ) + } + .flowOn(ioDispatcher) + + override suspend fun sync() = true +} diff --git a/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/database/model/PopulatedEpisodeKtTest.kt b/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/database/model/PopulatedEpisodeKtTest.kt index be1d41af6..b818e8196 100644 --- a/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/database/model/PopulatedEpisodeKtTest.kt +++ b/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/database/model/PopulatedEpisodeKtTest.kt @@ -51,7 +51,9 @@ class PopulatedEpisodeKtTest { AuthorEntity( id = 2, name = "name", - imageUrl = "imageUrl" + imageUrl = "imageUrl", + twitter = "twitter", + mediumPage = "mediumPage", ) ), ) @@ -82,7 +84,9 @@ class PopulatedEpisodeKtTest { Author( id = 2, name = "name", - imageUrl = "imageUrl" + imageUrl = "imageUrl", + twitter = "twitter", + mediumPage = "mediumPage", ) ), ), diff --git a/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/database/model/PopulatedNewsResourceKtTest.kt b/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/database/model/PopulatedNewsResourceKtTest.kt index ef9559f2b..e998dbae5 100644 --- a/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/database/model/PopulatedNewsResourceKtTest.kt +++ b/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/database/model/PopulatedNewsResourceKtTest.kt @@ -49,7 +49,9 @@ class PopulatedNewsResourceKtTest { AuthorEntity( id = 2, name = "name", - imageUrl = "imageUrl" + imageUrl = "imageUrl", + twitter = "twitter", + mediumPage = "mediumPage", ) ), topics = listOf( @@ -79,7 +81,9 @@ class PopulatedNewsResourceKtTest { Author( id = 2, name = "name", - imageUrl = "imageUrl" + imageUrl = "imageUrl", + twitter = "twitter", + mediumPage = "mediumPage", ) ), topics = listOf( diff --git a/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/model/NetworkEntityKtTest.kt b/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/model/NetworkEntityKtTest.kt index bd36dba82..4ddc2fdba 100644 --- a/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/model/NetworkEntityKtTest.kt +++ b/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/model/NetworkEntityKtTest.kt @@ -34,7 +34,9 @@ class NetworkEntityKtTest { val networkModel = NetworkAuthor( id = 0, name = "Test", - imageUrl = "something" + imageUrl = "something", + twitter = "twitter", + mediumPage = "mediumPage", ) val entity = networkModel.asEntity() diff --git a/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/repository/LocalAuthorsRepositoryTest.kt b/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/repository/LocalAuthorsRepositoryTest.kt new file mode 100644 index 000000000..b53998cdb --- /dev/null +++ b/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/repository/LocalAuthorsRepositoryTest.kt @@ -0,0 +1,80 @@ +/* + * 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.domain.repository + +import com.google.samples.apps.nowinandroid.core.database.dao.AuthorDao +import com.google.samples.apps.nowinandroid.core.database.model.AuthorEntity +import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel +import com.google.samples.apps.nowinandroid.core.domain.model.asEntity +import com.google.samples.apps.nowinandroid.core.domain.testdoubles.TestAuthorDao +import com.google.samples.apps.nowinandroid.core.domain.testdoubles.TestNiaNetwork +import com.google.samples.apps.nowinandroid.core.network.NiANetwork +import com.google.samples.apps.nowinandroid.core.network.model.NetworkAuthor +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class LocalAuthorsRepositoryTest { + + private lateinit var subject: LocalAuthorsRepository + + private lateinit var authorDao: AuthorDao + + private lateinit var network: NiANetwork + + @Before + fun setup() { + authorDao = TestAuthorDao() + network = TestNiaNetwork() + + subject = LocalAuthorsRepository( + authorDao = authorDao, + network = network, + ) + } + + @Test + fun localAuthorsRepository_Authors_stream_is_backed_by_Authors_dao() = + runTest { + Assert.assertEquals( + authorDao.getAuthorEntitiesStream() + .first() + .map(AuthorEntity::asExternalModel), + subject.getAuthorsStream() + .first() + ) + } + + @Test + fun localAuthorsRepository_sync_pulls_from_network() = + runTest { + subject.sync() + + val network = network.getAuthors() + .map(NetworkAuthor::asEntity) + + val db = authorDao.getAuthorEntitiesStream() + .first() + + Assert.assertEquals( + network.map(AuthorEntity::id), + db.map(AuthorEntity::id) + ) + } +} diff --git a/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/testdoubles/TestAuthorDao.kt b/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/testdoubles/TestAuthorDao.kt index d3092af45..15b6d4ac0 100644 --- a/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/testdoubles/TestAuthorDao.kt +++ b/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/testdoubles/TestAuthorDao.kt @@ -32,6 +32,8 @@ class TestAuthorDao : AuthorDao { id = 1, name = "Topic", imageUrl = "imageUrl", + twitter = "twitter", + mediumPage = "mediumPage", ) ) ) diff --git a/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/testdoubles/TestNewsResourceDao.kt b/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/testdoubles/TestNewsResourceDao.kt index fba1760dd..827849536 100644 --- a/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/testdoubles/TestNewsResourceDao.kt +++ b/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/testdoubles/TestNewsResourceDao.kt @@ -110,7 +110,9 @@ private fun NewsResourceEntity.asPopulatedNewsResource() = PopulatedNewsResource AuthorEntity( id = 2, name = "name", - imageUrl = "imageUrl" + imageUrl = "imageUrl", + twitter = "twitter", + mediumPage = "mediumPage", ) ), topics = listOf( diff --git a/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/testdoubles/TestNiaNetwork.kt b/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/testdoubles/TestNiaNetwork.kt index 96334e11c..99879f46e 100644 --- a/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/testdoubles/TestNiaNetwork.kt +++ b/core-domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/testdoubles/TestNiaNetwork.kt @@ -18,6 +18,7 @@ package com.google.samples.apps.nowinandroid.core.domain.testdoubles import com.google.samples.apps.nowinandroid.core.network.NiANetwork import com.google.samples.apps.nowinandroid.core.network.fake.FakeDataSource +import com.google.samples.apps.nowinandroid.core.network.model.NetworkAuthor import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic import kotlinx.serialization.decodeFromString @@ -33,6 +34,9 @@ class TestNiaNetwork : NiANetwork { override suspend fun getTopics(itemsPerPage: Int): List = networkJson.decodeFromString(FakeDataSource.topicsData) + override suspend fun getAuthors(itemsPerPage: Int): List = + networkJson.decodeFromString(FakeDataSource.authors) + override suspend fun getNewsResources(itemsPerPage: Int): List = networkJson.decodeFromString(FakeDataSource.data) } diff --git a/core-model/src/main/java/com/google/samples/apps/nowinandroid/core/model/data/Author.kt b/core-model/src/main/java/com/google/samples/apps/nowinandroid/core/model/data/Author.kt index 76b6e7c2a..387a97adc 100644 --- a/core-model/src/main/java/com/google/samples/apps/nowinandroid/core/model/data/Author.kt +++ b/core-model/src/main/java/com/google/samples/apps/nowinandroid/core/model/data/Author.kt @@ -23,4 +23,6 @@ data class Author( val id: Int, val name: String, val imageUrl: String, + val twitter: String, + val mediumPage: String, ) diff --git a/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/NiANetwork.kt b/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/NiANetwork.kt index 16cc47a44..a35533f04 100644 --- a/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/NiANetwork.kt +++ b/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/NiANetwork.kt @@ -16,6 +16,7 @@ package com.google.samples.apps.nowinandroid.core.network +import com.google.samples.apps.nowinandroid.core.network.model.NetworkAuthor import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic @@ -25,5 +26,7 @@ import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic interface NiANetwork { suspend fun getTopics(itemsPerPage: Int = 200): List + suspend fun getAuthors(itemsPerPage: Int = 200): List + suspend fun getNewsResources(itemsPerPage: Int = 200): List } diff --git a/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/fake/FakeData.kt b/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/fake/FakeData.kt index 2e65e5871..a356e66c3 100644 --- a/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/fake/FakeData.kt +++ b/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/fake/FakeData.kt @@ -27,9 +27,9 @@ import org.intellij.lang.annotations.Language object FakeDataSource { val sampleTopic = NetworkTopic( id = 1, - name = "UI", + name = "UI", shortDescription = "Material Design, Navigation, Text, Paging, Compose, Accessibility (a11y), Internationalization (i18n), Localization (l10n), Animations, Large Screens, Widgets", - longDescription = "Learn how to optimize your app's user interface - everything that users can see and interact with. Stay up to date on tocpis such as Material Design, Navigation, Text, Paging, Compose, Accessibility (a11y), Internationalization (i18n), Localization (l10n), Animations, Large Screens, Widgets, and many more!", + longDescription = "Learn how to optimize your app's user interface - everything that users can see and interact with. Stay up to date on tocpis such as Material Design, Navigation, Text, Paging, Compose, Accessibility (a11y), Internationalization (i18n), Localization (l10n), Animations, Large Screens, Widgets, and many more!", url = "url", imageUrl = "https://firebasestorage.googleapis.com/v0/b/now-in-android.appspot.com/o/img%2Fic_topic_UI.svg?alt=media&token=5d1d25a8-db1b-4cf1-9706-82ba0d133bf9" ) @@ -1638,4 +1638,472 @@ object FakeDataSource { } ] """.trimIndent() + + @Language("JSON") + val authors = """ + [ + { + "id": "1", + "name": "Márton Braun", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "2", + "name": "Greg Hartrell", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "3", + "name": "Simona Stojanovic", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "4", + "name": "Andrew Flynn", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "5", + "name": "Jon Boekenoogen", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "6", + "name": "Florina Muntenescu", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "7", + "name": "Lidia Gaymond", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "8", + "name": "Vicki Amin", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "9", + "name": "Marcel Pintó", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "10", + "name": "Krish Vitaldevara", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "11", + "name": "Gerry Fan", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "12", + "name": "Pietro Maggi", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "13", + "name": "Rohan Shah", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "14", + "name": "Dave Burke", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "15", + "name": "Meghan Mehta", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "16", + "name": "Anna Bernbaum", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "17", + "name": "Adarsh Fernando", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "18", + "name": "Madan Ankapura", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "19", + "name": "Kateryna Semenova", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "20", + "name": "Rahul Ravikumar", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "21", + "name": "Chris Craik", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "22", + "name": "Marcel Pintó Biescas", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "23", + "name": "Alex Vanyo", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "24", + "name": "Manuel Vicente Vivo", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "25", + "name": "Arjun Dayal", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "26", + "name": "Murat Yener", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "27", + "name": "Alex Saveau", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "28", + "name": "Paul Lammertsma", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "29", + "name": "Caren Chang", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "30", + "name": "Mayuri Khinvasara Khabya", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "31", + "name": "Romain Guy", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "32", + "name": "Chet Hasse", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "33", + "name": "Tor Norbye", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "34", + "name": "Nicole Laure", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "35", + "name": "Yigit Boyar", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "36", + "name": "Sean McQuillan", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "37", + "name": "Ben Weiss", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "38", + "name": "Chet Haase", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "39", + "name": "Carmen Jackson", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "40", + "name": "Manuel Vivo", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "41", + "name": "TJ Dahunsi", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "42", + "name": "Shailen Tuli", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "43", + "name": "Murat", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "44", + "name": "Kailiang Chen", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "45", + "name": "Meghan", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "46", + "name": "Jeremy Walker", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "47", + "name": "Don Turner", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "48", + "name": "Lilian Young", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "49", + "name": "Wenhung Teng", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "50", + "name": "Charcoal Chen", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "51", + "name": "Mike Yerou", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "52", + "name": "Peter Visontay", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "53", + "name": "Marcelo Hernandez", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "54", + "name": "Daniel Santiago", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "55", + "name": "Brad Corso", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "56", + "name": "Jonathan Koren", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "57", + "name": "Anna-Chiara Bellini", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "58", + "name": "Amanda Alexander", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "59", + "name": "Android Developers Backstage", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "60", + "name": "Nicole Borrelli", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "61", + "name": "Dan Saadati", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "62", + "name": "Nick Butcher", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "63", + "name": "Ian Lake", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "64", + "name": "Diana Wong", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "65", + "name": "Patricia Correa", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + }, + { + "id": "66", + "name": "The Modern Android Development Team", + "mediumPage": "", + "twitter": "", + "imageUrl": "" + } + ] + """.trimIndent() } diff --git a/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/fake/FakeNiANetwork.kt b/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/fake/FakeNiANetwork.kt index 087a08fcf..6b3542a6b 100644 --- a/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/fake/FakeNiANetwork.kt +++ b/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/fake/FakeNiANetwork.kt @@ -19,6 +19,7 @@ package com.google.samples.apps.nowinandroid.core.network.fake import com.google.samples.apps.nowinandroid.core.network.Dispatcher import com.google.samples.apps.nowinandroid.core.network.NiANetwork import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO +import com.google.samples.apps.nowinandroid.core.network.model.NetworkAuthor import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic import javax.inject.Inject @@ -43,4 +44,9 @@ class FakeNiANetwork @Inject constructor( withContext(ioDispatcher) { networkJson.decodeFromString(FakeDataSource.data) } + + override suspend fun getAuthors(itemsPerPage: Int): List = + withContext(ioDispatcher) { + networkJson.decodeFromString(FakeDataSource.authors) + } } diff --git a/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/model/NetworkAuthor.kt b/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/model/NetworkAuthor.kt index 4ec4da518..2f60f3ac8 100644 --- a/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/model/NetworkAuthor.kt +++ b/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/model/NetworkAuthor.kt @@ -27,4 +27,6 @@ data class NetworkAuthor( val id: Int, val name: String, val imageUrl: String, + val twitter: String, + val mediumPage: String, ) diff --git a/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/retrofit/RetrofitNiANetwork.kt b/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/retrofit/RetrofitNiANetwork.kt index 54def1763..718451b6e 100644 --- a/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/retrofit/RetrofitNiANetwork.kt +++ b/core-network/src/main/java/com/google/samples/apps/nowinandroid/core/network/retrofit/RetrofitNiANetwork.kt @@ -17,6 +17,7 @@ package com.google.samples.apps.nowinandroid.core.network.retrofit import com.google.samples.apps.nowinandroid.core.network.NiANetwork +import com.google.samples.apps.nowinandroid.core.network.model.NetworkAuthor import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory @@ -40,6 +41,11 @@ private interface RetrofitNiANetworkApi { @Query("pageSize") itemsPerPage: Int, ): NetworkResponse> + @GET(value = "authors") + suspend fun getAuthors( + @Query("pageSize") itemsPerPage: Int, + ): NetworkResponse> + @GET(value = "newsresources") suspend fun getNewsResources( @Query("pageSize") itemsPerPage: Int, @@ -83,6 +89,9 @@ class RetrofitNiANetwork @Inject constructor( override suspend fun getTopics(itemsPerPage: Int): List = networkApi.getTopics(itemsPerPage = itemsPerPage).data + override suspend fun getAuthors(itemsPerPage: Int): List = + networkApi.getAuthors(itemsPerPage = itemsPerPage).data + override suspend fun getNewsResources(itemsPerPage: Int): List = networkApi.getNewsResources(itemsPerPage = itemsPerPage).data } diff --git a/core-ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt b/core-ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt index 20dbea518..e73c84b89 100644 --- a/core-ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt +++ b/core-ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt @@ -284,7 +284,9 @@ private val newsResource = NewsResource( Author( id = 1, name = "Name", - imageUrl = "" + imageUrl = "", + twitter = "", + mediumPage = "", ) ), topics = listOf( diff --git a/sync/src/main/java/com/google/samples/apps/nowinandroid/sync/workers/SyncWorker.kt b/sync/src/main/java/com/google/samples/apps/nowinandroid/sync/workers/SyncWorker.kt index 8ecf6c48a..de52ff51d 100644 --- a/sync/src/main/java/com/google/samples/apps/nowinandroid/sync/workers/SyncWorker.kt +++ b/sync/src/main/java/com/google/samples/apps/nowinandroid/sync/workers/SyncWorker.kt @@ -24,6 +24,7 @@ import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OutOfQuotaPolicy import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkerParameters +import com.google.samples.apps.nowinandroid.core.domain.repository.AuthorsRepository import com.google.samples.apps.nowinandroid.core.domain.repository.NewsRepository import com.google.samples.apps.nowinandroid.core.domain.repository.TopicsRepository import com.google.samples.apps.nowinandroid.sync.SyncRepository @@ -32,6 +33,9 @@ import com.google.samples.apps.nowinandroid.sync.initializers.syncForegroundInfo import dagger.assisted.Assisted import dagger.assisted.AssistedInject import java.util.concurrent.TimeUnit +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope /** * Syncs the data layer by delegating to the appropriate repository instances with @@ -44,14 +48,21 @@ class SyncWorker @AssistedInject constructor( private val syncRepository: SyncRepository, private val topicRepository: TopicsRepository, private val newsRepository: NewsRepository, + private val authorsRepository: AuthorsRepository, ) : CoroutineWorker(appContext, workerParams) { override suspend fun getForegroundInfo(): ForegroundInfo = appContext.syncForegroundInfo() - override suspend fun doWork(): Result = - // First sync the repositories - when (topicRepository.sync() && newsRepository.sync()) { + override suspend fun doWork(): Result = coroutineScope { + // First sync the repositories in parallel + val syncedSuccessfully = awaitAll( + async { topicRepository.sync() }, + async { authorsRepository.sync() }, + async { newsRepository.sync() }, + ).all { it } + + when (syncedSuccessfully) { // Sync ran successfully, notify the SyncRepository that sync has been run true -> { syncRepository.notifyFirstTimeSyncRun() @@ -59,6 +70,7 @@ class SyncWorker @AssistedInject constructor( } false -> Result.retry() } + } companion object { private const val SyncInterval = 1L