Add DAOs for Room and provisions for their Hilt injections

Change-Id: Ia14c225cc1632ded38abffc201b673a6a60f4451
pull/2/head
Adetunji Dahunsi 3 years ago
parent 9f11f6ae8b
commit cbcb24c08f

@ -163,6 +163,7 @@ dependencies {
kapt libs.hilt.compiler kapt libs.hilt.compiler
implementation libs.room.runtime implementation libs.room.runtime
implementation libs.room.ktx
ksp libs.room.compiler ksp libs.room.compiler
implementation libs.protobuf.kotlin.lite implementation libs.protobuf.kotlin.lite

@ -17,12 +17,18 @@
package com.google.samples.apps.nowinandroid.data.fake 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.NetworkNewsResource
import com.google.samples.apps.nowinandroid.data.network.NetworkTopic
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant import kotlinx.datetime.toInstant
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
object FakeDataSource { object FakeDataSource {
val sampleTopic = NetworkTopic(
id = 0,
name = "Headlines",
description = "",
)
val sampleResource = NetworkNewsResource( val sampleResource = NetworkNewsResource(
id = 1, id = 1,
episodeId = 52, episodeId = 52,

@ -17,8 +17,10 @@
package com.google.samples.apps.nowinandroid.data.fake 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.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.data.network.NiANetwork
import com.google.samples.apps.nowinandroid.di.NiaDispatchers import com.google.samples.apps.nowinandroid.di.NiaDispatchers
import javax.inject.Inject
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
@ -27,10 +29,15 @@ import kotlinx.serialization.json.Json
/** /**
* [NiANetwork] implementation that provides static news resources to aid development * [NiANetwork] implementation that provides static news resources to aid development
*/ */
class FakeNiANetwork( class FakeNiANetwork @Inject constructor(
private val dispatchers: NiaDispatchers, private val dispatchers: NiaDispatchers,
private val networkJson: Json private val networkJson: Json
) : NiANetwork { ) : NiANetwork {
override suspend fun getTopics(): List<NetworkTopic> =
withContext(dispatchers.IO) {
networkJson.decodeFromString(FakeDataSource.topicsData)
}
override suspend fun getNewsResources(): List<NetworkNewsResource> = override suspend fun getNewsResources(): List<NetworkNewsResource> =
withContext(dispatchers.IO) { withContext(dispatchers.IO) {
networkJson.decodeFromString<ResourceData>(FakeDataSource.data).resources networkJson.decodeFromString<ResourceData>(FakeDataSource.data).resources

@ -19,6 +19,10 @@ package com.google.samples.apps.nowinandroid.data.local
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.TypeConverters 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.AuthorEntity
import com.google.samples.apps.nowinandroid.data.local.entities.EpisodeAuthorCrossRef import com.google.samples.apps.nowinandroid.data.local.entities.EpisodeAuthorCrossRef
import com.google.samples.apps.nowinandroid.data.local.entities.EpisodeEntity 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.InstantConverter
import com.google.samples.apps.nowinandroid.data.local.utilities.NewsResourceTypeConverter import com.google.samples.apps.nowinandroid.data.local.utilities.NewsResourceTypeConverter
// TODO: ADD DAOs
@Database( @Database(
entities = [ entities = [
AuthorEntity::class, AuthorEntity::class,
@ -46,4 +49,9 @@ import com.google.samples.apps.nowinandroid.data.local.utilities.NewsResourceTyp
InstantConverter::class, InstantConverter::class,
NewsResourceTypeConverter::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
}

@ -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<List<AuthorEntity>>
@Insert
suspend fun saveAuthorEntities(entities: List<AuthorEntity>)
}

@ -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<List<Episode>>
@Insert
suspend fun saveEpisodeEntities(entities: List<EpisodeEntity>)
}

@ -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<List<NewsResource>>
@Query(
value = """
SELECT * FROM news_resources
WHERE id IN (:filterTopicIds)
"""
)
fun getNewsResourcesStream(filterTopicIds: Set<Int>): Flow<List<NewsResource>>
@Insert
suspend fun saveNewsResourceEntities(entities: List<NewsResourceEntity>)
}

@ -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<List<TopicEntity>>
@Query(
value = """
SELECT * FROM topics
WHERE id IN (:ids)
"""
)
fun getTopicEntitiesStream(ids: Set<Int>): Flow<List<TopicEntity>>
@Insert
suspend fun saveTopics(entities: List<TopicEntity>)
}

@ -36,9 +36,13 @@ data class Episode(
) )
val newsResources: List<NewsResourceEntity>, val newsResources: List<NewsResourceEntity>,
@Relation( @Relation(
parentColumn = "episode_id", parentColumn = "id",
entityColumn = "author_id", entityColumn = "id",
associateBy = Junction(EpisodeAuthorCrossRef::class) associateBy = Junction(
value = EpisodeAuthorCrossRef::class,
parentColumn = "episode_id",
entityColumn = "author_id",
)
) )
val authors: List<AuthorEntity> val authors: List<AuthorEntity>
) )

@ -38,15 +38,23 @@ data class NewsResource(
) )
val episode: EpisodeEntity, val episode: EpisodeEntity,
@Relation( @Relation(
parentColumn = "news_resource_id", parentColumn = "id",
entityColumn = "author_id", entityColumn = "id",
associateBy = Junction(NewsResourceAuthorCrossRef::class) associateBy = Junction(
value = NewsResourceAuthorCrossRef::class,
parentColumn = "news_resource_id",
entityColumn = "author_id",
)
) )
val authors: List<AuthorEntity>, val authors: List<AuthorEntity>,
@Relation( @Relation(
parentColumn = "news_resource_id", parentColumn = "id",
entityColumn = "topic_id", entityColumn = "id",
associateBy = Junction(NewsResourceTopicCrossRef::class) associateBy = Junction(
value = NewsResourceTopicCrossRef::class,
parentColumn = "news_resource_id",
entityColumn = "topic_id",
)
) )
val topics: List<TopicEntity> val topics: List<TopicEntity>
) )

@ -20,5 +20,7 @@ package com.google.samples.apps.nowinandroid.data.network
* Interface representing network calls to the NIA backend * Interface representing network calls to the NIA backend
*/ */
interface NiANetwork { interface NiANetwork {
suspend fun getTopics(): List<NetworkTopic>
suspend fun getNewsResources(): List<NetworkNewsResource> suspend fun getNewsResources(): List<NetworkNewsResource>
} }

@ -47,7 +47,9 @@ interface AppModule {
): NiANetwork ): NiANetwork
@Binds @Binds
fun bindsTopicRepository(fakeTopicsRepository: FakeTopicsRepository): TopicsRepository fun bindsTopicRepository(
fakeTopicsRepository: FakeTopicsRepository
): TopicsRepository
@Binds @Binds
fun bindsNewsResourceRepository( fun bindsNewsResourceRepository(

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

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

@ -36,6 +36,14 @@ class FakeNiANetworkTest {
) )
} }
@Test
fun testDeserializationOfTopics() = runTest {
assertEquals(
FakeDataSource.sampleTopic,
subject.getTopics().first()
)
}
@Test @Test
fun testDeserializationOfNewsResources() = runTest { fun testDeserializationOfNewsResources() = runTest {
assertEquals( assertEquals(

@ -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" } protobuf-kotlin-lite = { group = "com.google.protobuf", name = "protobuf-kotlin-lite", version.ref = "protobuf" }
turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" } turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" }
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } 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" } room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
[plugins] [plugins]

Loading…
Cancel
Save