diff --git a/app/build.gradle b/app/build.gradle index 58c8c465a..c7da3e5bb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -163,6 +163,7 @@ dependencies { kapt libs.hilt.compiler implementation libs.room.runtime + implementation libs.room.ktx ksp libs.room.compiler implementation libs.protobuf.kotlin.lite diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeData.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeData.kt index c6e97f921..27f6e12a6 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeData.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeData.kt @@ -17,12 +17,18 @@ package com.google.samples.apps.nowinandroid.data.fake import com.google.samples.apps.nowinandroid.data.network.NetworkNewsResource +import com.google.samples.apps.nowinandroid.data.network.NetworkTopic import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant import org.intellij.lang.annotations.Language object FakeDataSource { + val sampleTopic = NetworkTopic( + id = 0, + name = "Headlines", + description = "", + ) val sampleResource = NetworkNewsResource( id = 1, episodeId = 52, diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeNiANetwork.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeNiANetwork.kt index eba6b0598..54d025e07 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeNiANetwork.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeNiANetwork.kt @@ -17,8 +17,10 @@ package com.google.samples.apps.nowinandroid.data.fake import com.google.samples.apps.nowinandroid.data.network.NetworkNewsResource +import com.google.samples.apps.nowinandroid.data.network.NetworkTopic import com.google.samples.apps.nowinandroid.data.network.NiANetwork import com.google.samples.apps.nowinandroid.di.NiaDispatchers +import javax.inject.Inject import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString @@ -27,10 +29,15 @@ import kotlinx.serialization.json.Json /** * [NiANetwork] implementation that provides static news resources to aid development */ -class FakeNiANetwork( +class FakeNiANetwork @Inject constructor( private val dispatchers: NiaDispatchers, private val networkJson: Json ) : NiANetwork { + override suspend fun getTopics(): List = + withContext(dispatchers.IO) { + networkJson.decodeFromString(FakeDataSource.topicsData) + } + override suspend fun getNewsResources(): List = withContext(dispatchers.IO) { networkJson.decodeFromString(FakeDataSource.data).resources diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/NiADatabase.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/NiADatabase.kt index af85eb465..44f50ab17 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/NiADatabase.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/NiADatabase.kt @@ -19,6 +19,10 @@ package com.google.samples.apps.nowinandroid.data.local import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters +import com.google.samples.apps.nowinandroid.data.local.dao.AuthorDao +import com.google.samples.apps.nowinandroid.data.local.dao.EpisodeDao +import com.google.samples.apps.nowinandroid.data.local.dao.NewsResourceDao +import com.google.samples.apps.nowinandroid.data.local.dao.TopicDao import com.google.samples.apps.nowinandroid.data.local.entities.AuthorEntity import com.google.samples.apps.nowinandroid.data.local.entities.EpisodeAuthorCrossRef import com.google.samples.apps.nowinandroid.data.local.entities.EpisodeEntity @@ -29,7 +33,6 @@ import com.google.samples.apps.nowinandroid.data.local.entities.TopicEntity import com.google.samples.apps.nowinandroid.data.local.utilities.InstantConverter import com.google.samples.apps.nowinandroid.data.local.utilities.NewsResourceTypeConverter -// TODO: ADD DAOs @Database( entities = [ AuthorEntity::class, @@ -46,4 +49,9 @@ import com.google.samples.apps.nowinandroid.data.local.utilities.NewsResourceTyp InstantConverter::class, NewsResourceTypeConverter::class, ) -abstract class NiADatabase : RoomDatabase() +abstract class NiADatabase : RoomDatabase() { + abstract fun topicDao(): TopicDao + abstract fun authorDao(): AuthorDao + abstract fun episodeDao(): EpisodeDao + abstract fun newsResourceDao(): NewsResourceDao +} diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/dao/AuthorDao.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/dao/AuthorDao.kt new file mode 100644 index 000000000..e38e4be8b --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/dao/AuthorDao.kt @@ -0,0 +1,35 @@ +/* + * 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.data.local.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import com.google.samples.apps.nowinandroid.data.local.entities.AuthorEntity +import kotlinx.coroutines.flow.Flow + +/** + * DAO for [AuthorEntity] access + */ +@Dao +interface AuthorDao { + @Query(value = "SELECT * FROM authors") + fun getAuthorsStream(): Flow> + + @Insert + suspend fun saveAuthorEntities(entities: List) +} diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/dao/EpisodeDao.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/dao/EpisodeDao.kt new file mode 100644 index 000000000..d873f2215 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/dao/EpisodeDao.kt @@ -0,0 +1,36 @@ +/* + * 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.data.local.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import com.google.samples.apps.nowinandroid.data.local.entities.EpisodeEntity +import com.google.samples.apps.nowinandroid.data.model.Episode +import kotlinx.coroutines.flow.Flow + +/** + * DAO for [EpisodeEntity] and [Episode] access + */ +@Dao +interface EpisodeDao { + @Query(value = "SELECT * FROM episodes") + fun getEpisodesStream(): Flow> + + @Insert + suspend fun saveEpisodeEntities(entities: List) +} diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/dao/NewsResourceDao.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/dao/NewsResourceDao.kt new file mode 100644 index 000000000..548d37fc0 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/dao/NewsResourceDao.kt @@ -0,0 +1,44 @@ +/* + * 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.data.local.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceEntity +import com.google.samples.apps.nowinandroid.data.model.NewsResource +import kotlinx.coroutines.flow.Flow + +/** + * DAO for [NewsResource] and [NewsResourceEntity] access + */ +@Dao +interface NewsResourceDao { + @Query(value = "SELECT * FROM news_resources") + fun getNewsResourcesStream(): Flow> + + @Query( + value = """ + SELECT * FROM news_resources + WHERE id IN (:filterTopicIds) + """ + ) + fun getNewsResourcesStream(filterTopicIds: Set): Flow> + + @Insert + suspend fun saveNewsResourceEntities(entities: List) +} diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/dao/TopicDao.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/dao/TopicDao.kt new file mode 100644 index 000000000..c523b886b --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/dao/TopicDao.kt @@ -0,0 +1,43 @@ +/* + * 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.data.local.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import com.google.samples.apps.nowinandroid.data.local.entities.TopicEntity +import kotlinx.coroutines.flow.Flow + +/** + * DAO for [TopicEntity] access + */ +@Dao +interface TopicDao { + @Query(value = "SELECT * FROM topics") + fun getTopicEntitiesStream(): Flow> + + @Query( + value = """ + SELECT * FROM topics + WHERE id IN (:ids) + """ + ) + fun getTopicEntitiesStream(ids: Set): Flow> + + @Insert + suspend fun saveTopics(entities: List) +} diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/Episode.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/Episode.kt index fe86062e6..a4a4c10c0 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/Episode.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/Episode.kt @@ -36,9 +36,13 @@ data class Episode( ) val newsResources: List, @Relation( - parentColumn = "episode_id", - entityColumn = "author_id", - associateBy = Junction(EpisodeAuthorCrossRef::class) + parentColumn = "id", + entityColumn = "id", + associateBy = Junction( + value = EpisodeAuthorCrossRef::class, + parentColumn = "episode_id", + entityColumn = "author_id", + ) ) val authors: List ) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/NewsResource.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/NewsResource.kt index f16bd1322..cc6ca9e97 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/NewsResource.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/NewsResource.kt @@ -38,15 +38,23 @@ data class NewsResource( ) val episode: EpisodeEntity, @Relation( - parentColumn = "news_resource_id", - entityColumn = "author_id", - associateBy = Junction(NewsResourceAuthorCrossRef::class) + parentColumn = "id", + entityColumn = "id", + associateBy = Junction( + value = NewsResourceAuthorCrossRef::class, + parentColumn = "news_resource_id", + entityColumn = "author_id", + ) ) val authors: List, @Relation( - parentColumn = "news_resource_id", - entityColumn = "topic_id", - associateBy = Junction(NewsResourceTopicCrossRef::class) + parentColumn = "id", + entityColumn = "id", + associateBy = Junction( + value = NewsResourceTopicCrossRef::class, + parentColumn = "news_resource_id", + entityColumn = "topic_id", + ) ) val topics: List ) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NiANetwork.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NiANetwork.kt index 3498244b6..227c69a5d 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NiANetwork.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NiANetwork.kt @@ -20,5 +20,7 @@ package com.google.samples.apps.nowinandroid.data.network * Interface representing network calls to the NIA backend */ interface NiANetwork { + suspend fun getTopics(): List + suspend fun getNewsResources(): List } diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/di/AppModule.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/di/AppModule.kt index 2f5a4c997..11177e9d8 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/di/AppModule.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/di/AppModule.kt @@ -47,7 +47,9 @@ interface AppModule { ): NiANetwork @Binds - fun bindsTopicRepository(fakeTopicsRepository: FakeTopicsRepository): TopicsRepository + fun bindsTopicRepository( + fakeTopicsRepository: FakeTopicsRepository + ): TopicsRepository @Binds fun bindsNewsResourceRepository( diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/di/DaosModule.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/di/DaosModule.kt new file mode 100644 index 000000000..33c0aab4d --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/di/DaosModule.kt @@ -0,0 +1,53 @@ +/* + * 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.di + +import com.google.samples.apps.nowinandroid.data.local.NiADatabase +import com.google.samples.apps.nowinandroid.data.local.dao.AuthorDao +import com.google.samples.apps.nowinandroid.data.local.dao.EpisodeDao +import com.google.samples.apps.nowinandroid.data.local.dao.NewsResourceDao +import com.google.samples.apps.nowinandroid.data.local.dao.TopicDao +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +interface DaosModule { + companion object { + @Provides + fun providesAuthorDao( + database: NiADatabase, + ): AuthorDao = database.authorDao() + + @Provides + fun providesTopicsDao( + database: NiADatabase, + ): TopicDao = database.topicDao() + + @Provides + fun providesEpisodeDao( + database: NiADatabase, + ): EpisodeDao = database.episodeDao() + + @Provides + fun providesNewsResourceDao( + database: NiADatabase, + ): NewsResourceDao = database.newsResourceDao() + } +} diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/di/DatabaseModule.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/di/DatabaseModule.kt new file mode 100644 index 000000000..a9c134301 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/di/DatabaseModule.kt @@ -0,0 +1,43 @@ +/* + * 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.di + +import android.content.Context +import androidx.room.Room +import com.google.samples.apps.nowinandroid.data.local.NiADatabase +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +interface DatabaseModule { + companion object { + @Provides + @Singleton + fun providesNiADatabase( + @ApplicationContext context: Context, + ): NiADatabase = Room.databaseBuilder( + context, + NiADatabase::class.java, + "nia-database" + ).build() + } +} diff --git a/app/src/test/java/com/google/samples/apps/nowinandroid/data/fake/FakeNiANetworkTest.kt b/app/src/test/java/com/google/samples/apps/nowinandroid/data/fake/FakeNiANetworkTest.kt index eb2d922df..464e1f972 100644 --- a/app/src/test/java/com/google/samples/apps/nowinandroid/data/fake/FakeNiANetworkTest.kt +++ b/app/src/test/java/com/google/samples/apps/nowinandroid/data/fake/FakeNiANetworkTest.kt @@ -36,6 +36,14 @@ class FakeNiANetworkTest { ) } + @Test + fun testDeserializationOfTopics() = runTest { + assertEquals( + FakeDataSource.sampleTopic, + subject.getTopics().first() + ) + } + @Test fun testDeserializationOfNewsResources() = runTest { assertEquals( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f256673ff..5d51711c3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -74,6 +74,7 @@ protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref protobuf-kotlin-lite = { group = "com.google.protobuf", name = "protobuf-kotlin-lite", version.ref = "protobuf" } turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" } room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } +room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } [plugins]