Add entity relationships and defined network deserialization strategy

Change-Id: I239cdc28237a87a0ed6599892e8ac7c61776a46d
pull/2/head
Adetunji Dahunsi 3 years ago
parent 632bc62ed5
commit c75f7ed025

@ -21,6 +21,7 @@ plugins {
id 'jacoco' id 'jacoco'
id 'dagger.hilt.android.plugin' id 'dagger.hilt.android.plugin'
alias(libs.plugins.protobuf) alias(libs.plugins.protobuf)
alias(libs.plugins.ksp)
} }
def jacocoTestReport = tasks.create("jacocoTestReport") def jacocoTestReport = tasks.create("jacocoTestReport")
@ -161,6 +162,9 @@ dependencies {
implementation libs.hilt.android implementation libs.hilt.android
kapt libs.hilt.compiler kapt libs.hilt.compiler
implementation libs.room.runtime
ksp libs.room.compiler
implementation libs.protobuf.kotlin.lite implementation libs.protobuf.kotlin.lite
debugImplementation libs.androidx.compose.ui.testManifest debugImplementation libs.androidx.compose.ui.testManifest

@ -25,7 +25,7 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onNodeWithText
import com.google.samples.apps.nowinandroid.R import com.google.samples.apps.nowinandroid.R
import com.google.samples.apps.nowinandroid.data.news.Topic import com.google.samples.apps.nowinandroid.data.model.Topic
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test

@ -14,18 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.data.news.fake package com.google.samples.apps.nowinandroid.data.fake
import com.google.samples.apps.nowinandroid.data.news.NewsRepository import com.google.samples.apps.nowinandroid.data.model.NewsResource
import com.google.samples.apps.nowinandroid.data.news.NewsResource import com.google.samples.apps.nowinandroid.data.repository.NewsRepository
import com.google.samples.apps.nowinandroid.di.NiaDispatchers import com.google.samples.apps.nowinandroid.di.NiaDispatchers
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
/** /**
@ -36,21 +32,10 @@ class FakeNewsRepository @Inject constructor(
private val dispatchers: NiaDispatchers, private val dispatchers: NiaDispatchers,
private val networkJson: Json private val networkJson: Json
) : NewsRepository { ) : NewsRepository {
override fun getNewsResourcesStream(): Flow<List<NewsResource>> = flow {
emit(networkJson.decodeFromString<ResourceData>(FakeDataSource.data).resources) override fun getNewsResourcesStream(): Flow<List<NewsResource>> =
} flowOf(emptyList())
.flowOn(dispatchers.IO)
override fun getNewsResourcesStream(filterTopicIds: Set<Int>): Flow<List<NewsResource>> = override fun getNewsResourcesStream(filterTopicIds: Set<Int>): Flow<List<NewsResource>> =
getNewsResourcesStream().map { newsResources -> flowOf(emptyList())
newsResources.filter { it.topics.intersect(filterTopicIds.toSet()).isNotEmpty() }
}
} }
/**
* Representation of resources aas fetched from [FakeDataSource]
*/
@Serializable
private data class ResourceData(
val resources: List<NewsResource>
)

@ -0,0 +1,46 @@
/*
* Copyright 2021 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.fake
import com.google.samples.apps.nowinandroid.data.network.NetworkNewsResource
import com.google.samples.apps.nowinandroid.data.network.NiANetwork
import com.google.samples.apps.nowinandroid.di.NiaDispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
/**
* [NiANetwork] implementation that provides static news resources to aid development
*/
class FakeNiANetwork(
private val dispatchers: NiaDispatchers,
private val networkJson: Json
) : NiANetwork {
override suspend fun getNewsResources(): List<NetworkNewsResource> =
withContext(dispatchers.IO) {
networkJson.decodeFromString<ResourceData>(FakeDataSource.data).resources
}
}
/**
* Representation of resources as fetched from [FakeDataSource]
*/
@Serializable
private data class ResourceData(
val resources: List<NetworkNewsResource>
)

@ -14,11 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.data.news.fake package com.google.samples.apps.nowinandroid.data.fake
import com.google.samples.apps.nowinandroid.data.NiaPreferences import com.google.samples.apps.nowinandroid.data.NiaPreferences
import com.google.samples.apps.nowinandroid.data.news.Topic import com.google.samples.apps.nowinandroid.data.model.Topic
import com.google.samples.apps.nowinandroid.data.news.TopicsRepository import com.google.samples.apps.nowinandroid.data.network.NetworkTopic
import com.google.samples.apps.nowinandroid.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.di.NiaDispatchers import com.google.samples.apps.nowinandroid.di.NiaDispatchers
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -33,7 +34,15 @@ class FakeTopicsRepository @Inject constructor(
private val niaPreferences: NiaPreferences private val niaPreferences: NiaPreferences
) : TopicsRepository { ) : TopicsRepository {
override fun getTopicsStream(): Flow<List<Topic>> = flow<List<Topic>> { override fun getTopicsStream(): Flow<List<Topic>> = flow<List<Topic>> {
emit(networkJson.decodeFromString(FakeDataSource.topicsData)) emit(
networkJson.decodeFromString<List<NetworkTopic>>(FakeDataSource.topicsData).map {
Topic(
id = it.id,
name = it.name,
description = it.description
)
}
)
} }
.flowOn(dispatchers.IO) .flowOn(dispatchers.IO)

@ -0,0 +1,49 @@
/*
* Copyright 2021 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
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
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
import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceAuthorCrossRef
import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceEntity
import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceTopicCrossRef
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,
EpisodeAuthorCrossRef::class,
EpisodeEntity::class,
NewsResourceAuthorCrossRef::class,
NewsResourceEntity::class,
NewsResourceTopicCrossRef::class,
TopicEntity::class,
],
version = 1,
)
@TypeConverters(
InstantConverter::class,
NewsResourceTypeConverter::class,
)
abstract class NiADatabase : RoomDatabase()

@ -0,0 +1,40 @@
/*
* Copyright 2021 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.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
/**
* Defines an author for either an [EpisodeEntity] or [NewsResourceEntity].
* It has a many to many relationship with both entities
*/
@Entity(
tableName = "authors",
indices = [
Index(value = ["name"], unique = true)
],
)
data class AuthorEntity(
@PrimaryKey
val id: Int,
val name: String,
@ColumnInfo(name = "image_url")
val imageUrl: String,
)

@ -0,0 +1,49 @@
/*
* Copyright 2021 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.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
/**
* Cross reference for many to many relationship between [EpisodeEntity] and [AuthorEntity]
*/
@Entity(
tableName = "episodes_authors",
primaryKeys = ["episode_id", "author_id"],
foreignKeys = [
ForeignKey(
entity = EpisodeEntity::class,
parentColumns = ["id"],
childColumns = ["episode_id"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = AuthorEntity::class,
parentColumns = ["id"],
childColumns = ["author_id"],
onDelete = ForeignKey.CASCADE
),
]
)
data class EpisodeAuthorCrossRef(
@ColumnInfo(name = "episode_id")
val episodeId: Int,
@ColumnInfo(name = "author_id")
val authorId: Long,
)

@ -0,0 +1,41 @@
/*
* Copyright 2021 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.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.datetime.Instant
/**
* Defines an NiA episode.
* It is a parent in a 1 to many relationship with [NewsResourceEntity]
*/
@Entity(
tableName = "episodes",
)
data class EpisodeEntity(
@PrimaryKey
val id: Int,
val name: String,
@ColumnInfo(name = "publish_date")
val publishDate: Instant,
@ColumnInfo(name = "alternate_video")
val alternateVideo: String?,
@ColumnInfo(name = "alternate_audio")
val alternateAudio: String?,
)

@ -0,0 +1,49 @@
/*
* Copyright 2021 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.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
/**
* Cross reference for many to many relationship between [NewsResourceEntity] and [AuthorEntity]
*/
@Entity(
tableName = "news_resources_authors",
primaryKeys = ["news_resource_id", "author_id"],
foreignKeys = [
ForeignKey(
entity = NewsResourceEntity::class,
parentColumns = ["id"],
childColumns = ["news_resource_id"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = AuthorEntity::class,
parentColumns = ["id"],
childColumns = ["author_id"],
onDelete = ForeignKey.CASCADE
),
]
)
data class NewsResourceAuthorCrossRef(
@ColumnInfo(name = "news_resource_id")
val newsResourceId: Int,
@ColumnInfo(name = "author_id")
val authorId: Long,
)

@ -0,0 +1,51 @@
/*
* Copyright 2021 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.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import kotlinx.datetime.Instant
/**
* Defines an NiA news resource.
* It is the child in a 1 to many relationship with [EpisodeEntity]
*/
@Entity(
tableName = "news_resources",
foreignKeys = [
ForeignKey(
entity = EpisodeEntity::class,
parentColumns = ["id"],
childColumns = ["episode_id"],
onDelete = ForeignKey.CASCADE
),
]
)
data class NewsResourceEntity(
@PrimaryKey
val id: Int,
@ColumnInfo(name = "episode_id")
val episodeId: Int,
val title: String,
val content: String,
val url: String,
@ColumnInfo(name = "publish_date")
val publishDate: Instant,
val type: String,
)

@ -0,0 +1,49 @@
/*
* Copyright 2021 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.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
/**
* Cross reference for many to many relationship between [NewsResourceEntity] and [TopicEntity]
*/
@Entity(
tableName = "news_resources_topics",
primaryKeys = ["news_resource_id", "topic_id"],
foreignKeys = [
ForeignKey(
entity = NewsResourceEntity::class,
parentColumns = ["id"],
childColumns = ["news_resource_id"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = TopicEntity::class,
parentColumns = ["id"],
childColumns = ["topic_id"],
onDelete = ForeignKey.CASCADE
),
]
)
data class NewsResourceTopicCrossRef(
@ColumnInfo(name = "news_resource_id")
val newsResourceId: Int,
@ColumnInfo(name = "topic_id")
val topicId: Int,
)

@ -0,0 +1,38 @@
/*
* Copyright 2021 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.entities
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
/**
* Defines a topic a user may follow.
* It has a many to many relationship with [NewsResourceEntity]
*/
@Entity(
tableName = "topics",
indices = [
Index(value = ["name"], unique = true)
]
)
data class TopicEntity(
@PrimaryKey
val id: Int,
val name: String,
val description: String,
)

@ -0,0 +1,45 @@
/*
* Copyright 2021 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.utilities
import androidx.room.TypeConverter
import com.google.samples.apps.nowinandroid.data.model.NewsResourceType
import kotlinx.datetime.Instant
class InstantConverter {
@TypeConverter
fun longToInstant(value: Long?): Instant? =
value?.let(Instant::fromEpochMilliseconds)
@TypeConverter
fun instantToLong(instant: Instant?): Long? =
instant?.toEpochMilliseconds()
}
class NewsResourceTypeConverter {
@TypeConverter
fun newsResourceTypeToString(value: NewsResourceType?): String? =
value?.let(NewsResourceType::name)
@TypeConverter
fun stringToNewsResourceType(name: String?): NewsResourceType = when (name) {
null -> NewsResourceType.Unknown
else -> NewsResourceType.values()
.firstOrNull { type -> type.name == name }
?: NewsResourceType.Unknown
}
}

@ -0,0 +1,44 @@
/*
* Copyright 2021 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.model
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
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
import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceEntity
/**
* External data layer representation of an NiA episode
*/
data class Episode(
@Embedded
val entity: EpisodeEntity,
@Relation(
parentColumn = "id",
entityColumn = "episode_id"
)
val newsResources: List<NewsResourceEntity>,
@Relation(
parentColumn = "episode_id",
entityColumn = "author_id",
associateBy = Junction(EpisodeAuthorCrossRef::class)
)
val authors: List<AuthorEntity>
)

@ -0,0 +1,52 @@
/*
* Copyright 2021 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.model
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
import com.google.samples.apps.nowinandroid.data.local.entities.AuthorEntity
import com.google.samples.apps.nowinandroid.data.local.entities.EpisodeEntity
import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceAuthorCrossRef
import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceEntity
import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceTopicCrossRef
import com.google.samples.apps.nowinandroid.data.local.entities.TopicEntity
/**
* External data layer representation of a fully populated NiA news resource
*/
data class NewsResource(
@Embedded
val entity: NewsResourceEntity,
@Relation(
parentColumn = "episode_id",
entityColumn = "id"
)
val episode: EpisodeEntity,
@Relation(
parentColumn = "news_resource_id",
entityColumn = "author_id",
associateBy = Junction(NewsResourceAuthorCrossRef::class)
)
val authors: List<AuthorEntity>,
@Relation(
parentColumn = "news_resource_id",
entityColumn = "topic_id",
associateBy = Junction(NewsResourceTopicCrossRef::class)
)
val topics: List<TopicEntity>
)

@ -0,0 +1,63 @@
/*
* Copyright 2021 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.model
/**
* Type for [NewsResource]
*/
enum class NewsResourceType(
val displayText: String,
// TODO: descriptions should probably be string resources
val description: String
) {
Video(
displayText = "Video 📺",
description = "A video published on YouTube"
),
APIChange(
displayText = "API change",
description = "An addition, deprecation or change to the Android platform APIs."
),
Article(
displayText = "Article 📚",
description = "An article, typically on Medium or the official Android blog"
),
Codelab(
displayText = "Codelab",
description = "A new or updated codelab"
),
Podcast(
displayText = "Podcast 🎙",
description = "A podcast"
),
Docs(
displayText = "Docs 📑",
description = "A new or updated piece of documentation"
),
Event(
displayText = "Event 📆",
description = "Information about a developer event e.g. Android Developer Summit"
),
DAC(
displayText = "DAC",
description = "Android version features - Information about features in an Android"
),
Unknown(
displayText = "Unknown",
description = "Unknown"
)
}

@ -1,5 +1,5 @@
/* /*
* Copyright 2022 The Android Open Source Project * Copyright 2021 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.data.news package com.google.samples.apps.nowinandroid.data.model
import kotlinx.serialization.Serializable /**
* External data layer representation of a NiA Topic
@Serializable */
data class Topic( data class Topic(
val id: Int, val id: Int,
val name: String, val name: String,

@ -0,0 +1,36 @@
/*
* Copyright 2021 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.network
import com.google.samples.apps.nowinandroid.data.local.entities.AuthorEntity
import kotlinx.serialization.Serializable
/**
* Network representation of [AuthorEntity]
*/
@Serializable
data class NetworkAuthor(
val id: Int,
val name: String,
val imageUrl: String,
)
fun NetworkAuthor.asEntity() = AuthorEntity(
id = id,
name = name,
imageUrl = imageUrl
)

@ -0,0 +1,71 @@
/*
* Copyright 2021 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.network
import androidx.room.PrimaryKey
import com.google.samples.apps.nowinandroid.data.local.entities.EpisodeEntity
import com.google.samples.apps.nowinandroid.data.network.utilities.InstantSerializer
import kotlinx.datetime.Instant
import kotlinx.serialization.Serializable
/**
* Network representation of [EpisodeEntity] when fetched from /networkepisodes
*/
@Serializable
data class NetworkEpisode(
@PrimaryKey
val id: Int,
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(),
)
/**
* Network representation of [EpisodeEntity] when fetched from /networkepisodes{id}
*/
@Serializable
data class NetworkEpisodeExpanded(
@PrimaryKey
val id: Int,
val name: String,
@Serializable(InstantSerializer::class)
val publishDate: Instant,
val alternateVideo: String,
val alternateAudio: String,
val newsResources: List<NetworkNewsResource> = listOf(),
val authors: List<NetworkAuthor> = listOf(),
)
fun NetworkEpisode.asEntity() = EpisodeEntity(
id = id,
name = name,
publishDate = publishDate,
alternateVideo = alternateVideo,
alternateAudio = alternateAudio,
)
fun NetworkEpisodeExpanded.asEntity() = EpisodeEntity(
id = id,
name = name,
publishDate = publishDate,
alternateVideo = alternateVideo,
alternateAudio = alternateAudio,
)

@ -0,0 +1,76 @@
/*
* Copyright 2021 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.network
import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceEntity
import com.google.samples.apps.nowinandroid.data.network.utilities.InstantSerializer
import kotlinx.datetime.Instant
import kotlinx.serialization.Serializable
/**
* Network representation of [NewsResourceEntity] when fetched from /networkresources
*/
@Serializable
data class NetworkNewsResource(
val id: Int,
val episodeId: Int,
val title: String,
val content: String,
val url: String,
@Serializable(InstantSerializer::class)
val publishDate: Instant,
val type: String,
val authors: List<Int> = listOf(),
val topics: List<Int> = listOf(),
)
/**
* Network representation of [NewsResourceEntity] when fetched from /networkresources{id}
*/
@Serializable
data class NetworkNewsResourceExpanded(
val id: Int,
val episodeId: Int,
val title: String,
val content: String,
val url: String,
@Serializable(InstantSerializer::class)
val publishDate: Instant,
val type: String,
val authors: List<NetworkAuthor> = listOf(),
val topics: List<NetworkTopic> = listOf(),
)
fun NetworkNewsResource.asEntity() = NewsResourceEntity(
id = id,
episodeId = episodeId,
title = title,
content = content,
url = url,
publishDate = publishDate,
type = type,
)
fun NetworkNewsResourceExpanded.asEntity() = NewsResourceEntity(
id = id,
episodeId = episodeId,
title = title,
content = content,
url = url,
publishDate = publishDate,
type = type,
)

@ -0,0 +1,36 @@
/*
* Copyright 2021 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.network
import com.google.samples.apps.nowinandroid.data.local.entities.TopicEntity
import kotlinx.serialization.Serializable
/**
* Network representation of [TopicEntity]
*/
@Serializable
data class NetworkTopic(
val id: Int,
val name: String = "",
val description: String = "",
)
fun NetworkTopic.asEntity() = TopicEntity(
id = id,
name = name,
description = description
)

@ -0,0 +1,24 @@
/*
* Copyright 2021 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.network
/**
* Interface representing network calls to the NIA backend
*/
interface NiANetwork {
suspend fun getNewsResources(): List<NetworkNewsResource>
}

@ -14,55 +14,24 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.data.news package com.google.samples.apps.nowinandroid.data.network.utilities
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import kotlinx.datetime.toInstant import kotlinx.datetime.toInstant
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName import kotlinx.serialization.descriptors.PrimitiveKind.STRING
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
/** object InstantSerializer : KSerializer<Instant> {
* Item representing a summary of a noteworthy item from a Now In Android episode
*/
@Serializable
data class NewsResource(
val id: Int,
val episodeId: Int,
val title: String,
val content: String,
val url: String,
val authors: List<Int>,
@Serializable(InstantSerializer::class)
val publishDate: Instant,
val type: String,
val topics: List<Int>,
val alternateVideo: VideoInfo?
)
/**
* Data class summarizing video metadata
*/
@Serializable
data class VideoInfo(
@SerialName("URL")
val url: String,
val startTimestamp: Int,
val endTimestamp: Int,
)
private object InstantSerializer : KSerializer<Instant> {
override fun deserialize(decoder: Decoder): Instant = override fun deserialize(decoder: Decoder): Instant =
decoder.decodeString().toInstant() decoder.decodeString().toInstant()
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor( override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(
serialName = "Instant", serialName = "Instant",
kind = PrimitiveKind.STRING kind = STRING
) )
override fun serialize(encoder: Encoder, value: Instant) = override fun serialize(encoder: Encoder, value: Instant) =

@ -14,8 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.data.news package com.google.samples.apps.nowinandroid.data.repository
import com.google.samples.apps.nowinandroid.data.model.NewsResource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
/** /**

@ -1,5 +1,5 @@
/* /*
* Copyright 2022 The Android Open Source Project * Copyright 2021 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,8 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.data.news package com.google.samples.apps.nowinandroid.data.repository
import com.google.samples.apps.nowinandroid.data.model.Topic
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface TopicsRepository { interface TopicsRepository {

@ -22,10 +22,12 @@ import androidx.datastore.core.DataStoreFactory
import androidx.datastore.dataStoreFile import androidx.datastore.dataStoreFile
import com.google.samples.apps.nowinandroid.data.UserPreferences import com.google.samples.apps.nowinandroid.data.UserPreferences
import com.google.samples.apps.nowinandroid.data.UserPreferencesSerializer import com.google.samples.apps.nowinandroid.data.UserPreferencesSerializer
import com.google.samples.apps.nowinandroid.data.news.NewsRepository import com.google.samples.apps.nowinandroid.data.fake.FakeNewsRepository
import com.google.samples.apps.nowinandroid.data.news.TopicsRepository import com.google.samples.apps.nowinandroid.data.fake.FakeNiANetwork
import com.google.samples.apps.nowinandroid.data.news.fake.FakeNewsRepository import com.google.samples.apps.nowinandroid.data.fake.FakeTopicsRepository
import com.google.samples.apps.nowinandroid.data.news.fake.FakeTopicsRepository import com.google.samples.apps.nowinandroid.data.network.NiANetwork
import com.google.samples.apps.nowinandroid.data.repository.NewsRepository
import com.google.samples.apps.nowinandroid.data.repository.TopicsRepository
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -39,6 +41,11 @@ import kotlinx.serialization.json.Json
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
interface AppModule { interface AppModule {
@Binds
fun bindsNiANetwork(
fakeNiANetwork: FakeNiANetwork
): NiANetwork
@Binds @Binds
fun bindsTopicRepository(fakeTopicsRepository: FakeTopicsRepository): TopicsRepository fun bindsTopicRepository(fakeTopicsRepository: FakeTopicsRepository): TopicsRepository

@ -30,11 +30,11 @@ import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.google.samples.apps.nowinandroid.R import com.google.samples.apps.nowinandroid.R
import com.google.samples.apps.nowinandroid.data.news.NewsResource import com.google.samples.apps.nowinandroid.data.model.NewsResource
import com.google.samples.apps.nowinandroid.ui.theme.NiaTheme import com.google.samples.apps.nowinandroid.ui.theme.NiaTheme
/** /**
* [com.google.samples.apps.nowinandroid.data.news.NewsResource] card used on the following screens: * [com.google.samples.apps.nowinandroid.data.model.NewsResource] card used on the following screens:
* For You, Episodes, Saved * For You, Episodes, Saved
*/ */

@ -44,8 +44,8 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.google.samples.apps.nowinandroid.R import com.google.samples.apps.nowinandroid.R
import com.google.samples.apps.nowinandroid.data.news.NewsResource import com.google.samples.apps.nowinandroid.data.model.NewsResource
import com.google.samples.apps.nowinandroid.data.news.Topic import com.google.samples.apps.nowinandroid.data.model.Topic
@Composable @Composable
fun ForYouRoute( fun ForYouRoute(

@ -22,10 +22,10 @@ import androidx.compose.runtime.snapshots.Snapshot.Companion.withMutableSnapshot
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.google.samples.apps.nowinandroid.data.news.NewsRepository import com.google.samples.apps.nowinandroid.data.model.NewsResource
import com.google.samples.apps.nowinandroid.data.news.NewsResource import com.google.samples.apps.nowinandroid.data.model.Topic
import com.google.samples.apps.nowinandroid.data.news.Topic import com.google.samples.apps.nowinandroid.data.repository.NewsRepository
import com.google.samples.apps.nowinandroid.data.news.TopicsRepository import com.google.samples.apps.nowinandroid.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.ui.saveable import com.google.samples.apps.nowinandroid.ui.saveable
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject

@ -0,0 +1,35 @@
/*
* Copyright 2021 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.fake
import com.google.samples.apps.nowinandroid.di.DefaultNiaDispatchers
import kotlinx.serialization.json.Json
import org.junit.Before
class FakeNewsRepositoryTest {
private lateinit var subject: FakeNewsRepository
@Before
fun setup() {
subject = FakeNewsRepository(
// TODO: Create test-specific NiaDispatchers
dispatchers = DefaultNiaDispatchers(),
networkJson = Json { ignoreUnknownKeys = true }
)
}
}

@ -14,23 +14,22 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.data.news.fake package com.google.samples.apps.nowinandroid.data.fake
import com.google.samples.apps.nowinandroid.di.DefaultNiaDispatchers import com.google.samples.apps.nowinandroid.di.DefaultNiaDispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
class FakeNewsRepositoryTest { class FakeNiANetworkTest {
private lateinit var subject: FakeNewsRepository private lateinit var subject: FakeNiANetwork
@Before @Before
fun setup() { fun setUp() {
subject = FakeNewsRepository( subject = FakeNiANetwork(
// TODO: Create test-specific NiaDispatchers // TODO: Create test-specific NiaDispatchers
dispatchers = DefaultNiaDispatchers(), dispatchers = DefaultNiaDispatchers(),
networkJson = Json { ignoreUnknownKeys = true } networkJson = Json { ignoreUnknownKeys = true }
@ -41,7 +40,7 @@ class FakeNewsRepositoryTest {
fun testDeserializationOfNewsResources() = runTest { fun testDeserializationOfNewsResources() = runTest {
assertEquals( assertEquals(
FakeDataSource.sampleResource, FakeDataSource.sampleResource,
subject.getNewsResourcesStream().first().first() subject.getNewsResources().first()
) )
} }
} }

@ -0,0 +1,129 @@
/*
* Copyright 2021 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.network
import com.google.samples.apps.nowinandroid.data.model.NewsResourceType
import kotlinx.datetime.Instant
import org.junit.Assert.assertEquals
import org.junit.Test
class NetworkEntityKtTest {
@Test
fun network_author_can_be_mapped_to_author_entity() {
val networkModel = NetworkAuthor(
id = 0,
name = "Test",
imageUrl = "something"
)
val entity = networkModel.asEntity()
assertEquals(0, entity.id)
assertEquals("Test", entity.name)
assertEquals("something", entity.imageUrl)
}
@Test
fun network_topic_can_be_mapped_to_topic_entity() {
val networkModel = NetworkTopic(
id = 0,
name = "Test",
description = "something"
)
val entity = networkModel.asEntity()
assertEquals(0, entity.id)
assertEquals("Test", entity.name)
assertEquals("something", entity.description)
}
@Test
fun network_news_resource_can_be_mapped_to_news_resource_entity() {
val networkModel = NetworkNewsResource(
id = 0,
episodeId = 2,
title = "title",
content = "content",
url = "url",
publishDate = Instant.fromEpochMilliseconds(1),
type = NewsResourceType.Article.displayText,
)
val entity = networkModel.asEntity()
assertEquals(0, entity.id)
assertEquals(2, entity.episodeId)
assertEquals("title", entity.title)
assertEquals("content", entity.content)
assertEquals("url", entity.url)
assertEquals(Instant.fromEpochMilliseconds(1), entity.publishDate)
assertEquals(NewsResourceType.Article.displayText, entity.type)
val expandedNetworkModel = NetworkNewsResourceExpanded(
id = 0,
episodeId = 2,
title = "title",
content = "content",
url = "url",
publishDate = Instant.fromEpochMilliseconds(1),
type = NewsResourceType.Article.displayText,
)
val entityFromExpanded = expandedNetworkModel.asEntity()
assertEquals(0, entityFromExpanded.id)
assertEquals(2, entityFromExpanded.episodeId)
assertEquals("title", entityFromExpanded.title)
assertEquals("content", entityFromExpanded.content)
assertEquals("url", entityFromExpanded.url)
assertEquals(Instant.fromEpochMilliseconds(1), entityFromExpanded.publishDate)
assertEquals(NewsResourceType.Article.displayText, entityFromExpanded.type)
}
@Test
fun network_episode_can_be_mapped_to_episode_entity() {
val networkModel = NetworkEpisode(
id = 0,
name = "name",
publishDate = Instant.fromEpochMilliseconds(1),
alternateVideo = "alternateVideo",
alternateAudio = "alternateAudio",
)
val entity = networkModel.asEntity()
assertEquals(0, entity.id)
assertEquals("name", entity.name)
assertEquals("alternateVideo", entity.alternateVideo)
assertEquals("alternateAudio", entity.alternateAudio)
assertEquals(Instant.fromEpochMilliseconds(1), entity.publishDate)
val expandedNetworkModel = NetworkEpisodeExpanded(
id = 0,
name = "name",
publishDate = Instant.fromEpochMilliseconds(1),
alternateVideo = "alternateVideo",
alternateAudio = "alternateAudio",
)
val entityFromExpanded = expandedNetworkModel.asEntity()
assertEquals(0, entityFromExpanded.id)
assertEquals("name", entityFromExpanded.name)
assertEquals("alternateVideo", entityFromExpanded.alternateVideo)
assertEquals("alternateAudio", entityFromExpanded.alternateAudio)
assertEquals(Instant.fromEpochMilliseconds(1), entityFromExpanded.publishDate)
}
}

@ -16,8 +16,8 @@
package com.google.samples.apps.nowinandroid.testutil package com.google.samples.apps.nowinandroid.testutil
import com.google.samples.apps.nowinandroid.data.news.NewsRepository import com.google.samples.apps.nowinandroid.data.model.NewsResource
import com.google.samples.apps.nowinandroid.data.news.NewsResource import com.google.samples.apps.nowinandroid.data.repository.NewsRepository
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow

@ -16,8 +16,8 @@
package com.google.samples.apps.nowinandroid.testutil package com.google.samples.apps.nowinandroid.testutil
import com.google.samples.apps.nowinandroid.data.news.Topic import com.google.samples.apps.nowinandroid.data.model.Topic
import com.google.samples.apps.nowinandroid.data.news.TopicsRepository import com.google.samples.apps.nowinandroid.data.repository.TopicsRepository
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow

@ -18,7 +18,7 @@ package com.google.samples.apps.nowinandroid.ui.foryou
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test import app.cash.turbine.test
import com.google.samples.apps.nowinandroid.data.news.Topic import com.google.samples.apps.nowinandroid.data.model.Topic
import com.google.samples.apps.nowinandroid.testutil.TestDispatcherRule import com.google.samples.apps.nowinandroid.testutil.TestDispatcherRule
import com.google.samples.apps.nowinandroid.testutil.TestNewsRepository import com.google.samples.apps.nowinandroid.testutil.TestNewsRepository
import com.google.samples.apps.nowinandroid.testutil.TestTopicsRepository import com.google.samples.apps.nowinandroid.testutil.TestTopicsRepository

@ -49,7 +49,7 @@ subprojects {
target '**/*.kt' target '**/*.kt'
targetExclude("$buildDir/**/*.kt") targetExclude("$buildDir/**/*.kt")
targetExclude('bin/**/*.kt') targetExclude('bin/**/*.kt')
targetExclude("$rootDir/app/src/main/java/com/google/samples/apps/nowinandroid/data/news/fake/FakeData.kt") targetExclude("$rootDir/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeData.kt")
ktlint(libs.versions.ktlint.get()).userData([android: "true"]) ktlint(libs.versions.ktlint.get()).userData([android: "true"])
licenseHeaderFile rootProject.file('spotless/copyright.kt') licenseHeaderFile rootProject.file('spotless/copyright.kt')
} }

@ -22,11 +22,13 @@ kotlinxCoroutines = "1.6.0"
kotlinxCoroutinesTest = "1.6.0" kotlinxCoroutinesTest = "1.6.0"
kotlinxDatetime = "0.3.1" kotlinxDatetime = "0.3.1"
kotlinxSerializationJson = "1.3.1" kotlinxSerializationJson = "1.3.1"
ksp = "1.6.0-1.0.1"
ktlint = "0.43.0" ktlint = "0.43.0"
material3 = "1.5.0-alpha05" material3 = "1.5.0-alpha05"
mockk = "1.12.1" mockk = "1.12.1"
protobuf = "3.19.1" protobuf = "3.19.1"
protobufPlugin = "0.8.18" protobufPlugin = "0.8.18"
room = "2.4.1"
spotless = "6.0.0" spotless = "6.0.0"
turbine = "0.7.0" turbine = "0.7.0"
@ -71,7 +73,10 @@ mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" } protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" }
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-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
[plugins] [plugins]
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" } protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }

@ -23,6 +23,7 @@ dependencyResolutionManagement {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
gradlePluginPortal()
} }
} }
rootProject.name = "nowinandroid" rootProject.name = "nowinandroid"

Loading…
Cancel
Save