Introduce SealedX KSP library

pull/222/head
skydoves 3 years ago
parent e0687e9aae
commit fd95532270
No known key found for this signature in database
GPG Key ID: D7662013FA1A5596

@ -20,6 +20,13 @@ plugins {
id("kotlinx-serialization")
id("dagger.hilt.android.plugin")
id("nowinandroid.spotless")
alias(libs.plugins.ksp)
}
kotlin {
sourceSets.configureEach {
kotlin.srcDir("$buildDir/generated/ksp/$name/kotlin/")
}
}
dependencies {
@ -38,4 +45,7 @@ dependencies {
implementation(libs.hilt.android)
kapt(libs.hilt.compiler)
implementation(libs.sealedx.core)
ksp(libs.sealedx.processor)
}

@ -0,0 +1,39 @@
/*
* 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.data.model
import com.google.samples.apps.nowinandroid.core.model.data.FollowableAuthor
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceExtensive
import com.google.samples.apps.nowinandroid.core.model.data.SaveableNewsResourceExtensive
import com.skydoves.sealedx.core.Extensive
import com.skydoves.sealedx.core.annotations.ExtensiveModel
import com.skydoves.sealedx.core.annotations.ExtensiveSealed
@ExtensiveSealed(
models = [
ExtensiveModel(type = FollowableTopic::class, name = "Topic"),
ExtensiveModel(type = FollowableAuthor::class, name = "Author"),
ExtensiveModel(type = SaveableNewsResourceExtensive::class, name = "SaveableNews"),
ExtensiveModel(type = NewsResourceExtensive::class, name = "News"),
]
)
sealed interface UiState {
data class Success(val data: Extensive) : UiState
object Error : UiState
object Loading : UiState
}

@ -41,6 +41,14 @@ data class NewsResource(
val topics: List<Topic>
)
data class NewsResourceExtensive(
val newsResources: List<NewsResource>
)
fun List<NewsResource>.toExtensive(): NewsResourceExtensive {
return NewsResourceExtensive(this)
}
val previewNewsResources = listOf(
NewsResource(
id = "1",

@ -23,3 +23,11 @@ data class SaveableNewsResource(
val newsResource: NewsResource,
val isSaved: Boolean,
)
data class SaveableNewsResourceExtensive(
val newsResources: List<SaveableNewsResource>
)
fun List<SaveableNewsResource>.toExtensive(): SaveableNewsResourceExtensive {
return SaveableNewsResourceExtensive(this)
}

@ -50,6 +50,8 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage
import com.google.samples.apps.nowinandroid.core.data.model.AuthorUiState
import com.google.samples.apps.nowinandroid.core.data.model.SaveableNewsUiState
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaFilterChip
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaLoadingWheel
@ -59,6 +61,7 @@ import com.google.samples.apps.nowinandroid.core.model.data.FollowableAuthor
import com.google.samples.apps.nowinandroid.core.model.data.SaveableNewsResource
import com.google.samples.apps.nowinandroid.core.model.data.previewAuthors
import com.google.samples.apps.nowinandroid.core.model.data.previewNewsResources
import com.google.samples.apps.nowinandroid.core.model.data.toExtensive
import com.google.samples.apps.nowinandroid.core.ui.newsResourceCardItems
@OptIn(ExperimentalLifecycleComposeApi::class)
@ -69,7 +72,7 @@ fun AuthorRoute(
viewModel: AuthorViewModel = hiltViewModel(),
) {
val authorUiState: AuthorUiState by viewModel.authorUiState.collectAsStateWithLifecycle()
val newsUiState: NewsUiState by viewModel.newUiState.collectAsStateWithLifecycle()
val newsUiState: SaveableNewsUiState by viewModel.newUiState.collectAsStateWithLifecycle()
AuthorScreen(
authorUiState = authorUiState,
@ -85,7 +88,7 @@ fun AuthorRoute(
@Composable
internal fun AuthorScreen(
authorUiState: AuthorUiState,
newsUiState: NewsUiState,
newsUiState: SaveableNewsUiState,
onBackClick: () -> Unit,
onFollowClick: (Boolean) -> Unit,
onBookmarkChanged: (String, Boolean) -> Unit,
@ -115,11 +118,11 @@ internal fun AuthorScreen(
AuthorToolbar(
onBackClick = onBackClick,
onFollowClick = onFollowClick,
uiState = authorUiState.followableAuthor,
uiState = authorUiState.data,
)
}
authorBody(
author = authorUiState.followableAuthor.author,
author = authorUiState.data.author,
news = newsUiState,
onBookmarkChanged = onBookmarkChanged,
)
@ -133,7 +136,7 @@ internal fun AuthorScreen(
private fun LazyListScope.authorBody(
author: Author,
news: NewsUiState,
news: SaveableNewsUiState,
onBookmarkChanged: (String, Boolean) -> Unit
) {
item {
@ -170,20 +173,20 @@ private fun AuthorHeader(author: Author) {
}
private fun LazyListScope.authorCards(
news: NewsUiState,
news: SaveableNewsUiState,
onBookmarkChanged: (String, Boolean) -> Unit
) {
when (news) {
is NewsUiState.Success -> {
is SaveableNewsUiState.Success -> {
newsResourceCardItems(
items = news.news,
items = news.data.newsResources,
newsResourceMapper = { it.newsResource },
isBookmarkedMapper = { it.isSaved },
onToggleBookmark = { onBookmarkChanged(it.newsResource.id, !it.isSaved) },
itemModifier = Modifier.padding(24.dp)
)
}
is NewsUiState.Loading -> item {
is SaveableNewsUiState.Loading -> item {
NiaLoadingWheel(contentDesc = "Loading news") // TODO
}
else -> item {
@ -237,13 +240,13 @@ fun AuthorScreenPopulated() {
NiaBackground {
AuthorScreen(
authorUiState = AuthorUiState.Success(FollowableAuthor(previewAuthors[0], false)),
newsUiState = NewsUiState.Success(
newsUiState = SaveableNewsUiState.Success(
previewNewsResources.mapIndexed { index, newsResource ->
SaveableNewsResource(
newsResource = newsResource,
isSaved = index % 2 == 0,
)
}
}.toExtensive()
),
onBackClick = {},
onFollowClick = {},
@ -263,7 +266,7 @@ fun AuthorScreenLoading() {
NiaBackground {
AuthorScreen(
authorUiState = AuthorUiState.Loading,
newsUiState = NewsUiState.Loading,
newsUiState = SaveableNewsUiState.Loading,
onBackClick = {},
onFollowClick = {},
onBookmarkChanged = { _, _ -> },

@ -19,6 +19,8 @@ package com.google.samples.apps.nowinandroid.feature.author
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.samples.apps.nowinandroid.core.data.model.AuthorUiState
import com.google.samples.apps.nowinandroid.core.data.model.SaveableNewsUiState
import com.google.samples.apps.nowinandroid.core.data.repository.AuthorsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.NewsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
@ -26,6 +28,7 @@ import com.google.samples.apps.nowinandroid.core.model.data.Author
import com.google.samples.apps.nowinandroid.core.model.data.FollowableAuthor
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.model.data.SaveableNewsResource
import com.google.samples.apps.nowinandroid.core.model.data.toExtensive
import com.google.samples.apps.nowinandroid.core.result.Result
import com.google.samples.apps.nowinandroid.core.result.asResult
import com.google.samples.apps.nowinandroid.feature.author.navigation.AuthorDestination
@ -62,7 +65,7 @@ class AuthorViewModel @Inject constructor(
initialValue = AuthorUiState.Loading
)
val newUiState: StateFlow<NewsUiState> = newsUiStateStream(
val newUiState: StateFlow<SaveableNewsUiState> = newsUiStateStream(
authorId = authorId,
userDataRepository = userDataRepository,
newsRepository = newsRepository
@ -70,7 +73,7 @@ class AuthorViewModel @Inject constructor(
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = NewsUiState.Loading
initialValue = SaveableNewsUiState.Loading
)
fun followAuthorToggle(followed: Boolean) {
@ -113,7 +116,7 @@ private fun authorUiStateStream(
val (followedAuthors, author) = followedAuthorToAuthorResult.data
val followed = followedAuthors.contains(authorId)
AuthorUiState.Success(
followableAuthor = FollowableAuthor(
data = FollowableAuthor(
author = author,
isFollowed = followed
)
@ -133,7 +136,7 @@ private fun newsUiStateStream(
authorId: String,
newsRepository: NewsRepository,
userDataRepository: UserDataRepository,
): Flow<NewsUiState> {
): Flow<SaveableNewsUiState> {
// Observe news
val newsStream: Flow<List<NewsResource>> = newsRepository.getNewsResourcesStream(
filterAuthorIds = setOf(element = authorId),
@ -154,33 +157,21 @@ private fun newsUiStateStream(
when (newsToBookmarksResult) {
is Result.Success -> {
val (news, bookmarks) = newsToBookmarksResult.data
NewsUiState.Success(
SaveableNewsUiState.Success(
news.map { newsResource ->
SaveableNewsResource(
newsResource,
isSaved = bookmarks.contains(newsResource.id)
)
}
}.toExtensive()
)
}
is Result.Loading -> {
NewsUiState.Loading
SaveableNewsUiState.Loading
}
is Result.Error -> {
NewsUiState.Error
SaveableNewsUiState.Error
}
}
}
}
sealed interface AuthorUiState {
data class Success(val followableAuthor: FollowableAuthor) : AuthorUiState
object Error : AuthorUiState
object Loading : AuthorUiState
}
sealed interface NewsUiState {
data class Success(val news: List<SaveableNewsResource>) : NewsUiState
object Error : NewsUiState
object Loading : NewsUiState
}

@ -47,16 +47,19 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage
import com.google.samples.apps.nowinandroid.core.data.model.NewsUiState
import com.google.samples.apps.nowinandroid.core.data.model.TopicUiState
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaFilterChip
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaLoadingWheel
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceExtensive
import com.google.samples.apps.nowinandroid.core.model.data.previewNewsResources
import com.google.samples.apps.nowinandroid.core.model.data.previewTopics
import com.google.samples.apps.nowinandroid.core.model.data.toExtensive
import com.google.samples.apps.nowinandroid.core.ui.newsResourceCardItems
import com.google.samples.apps.nowinandroid.feature.topic.R.string
import com.google.samples.apps.nowinandroid.feature.topic.TopicUiState.Loading
@OptIn(ExperimentalLifecycleComposeApi::class)
@Composable
@ -93,7 +96,7 @@ internal fun TopicScreen(
Spacer(Modifier.windowInsetsTopHeight(WindowInsets.safeDrawing))
}
when (topicState) {
Loading -> item {
TopicUiState.Loading -> item {
NiaLoadingWheel(
modifier = modifier,
contentDesc = stringResource(id = string.topic_loading),
@ -105,14 +108,14 @@ internal fun TopicScreen(
TopicToolbar(
onBackClick = onBackClick,
onFollowClick = onFollowClick,
uiState = topicState.followableTopic,
uiState = topicState.data,
)
}
TopicBody(
name = topicState.followableTopic.topic.name,
description = topicState.followableTopic.topic.longDescription,
name = topicState.data.topic.name,
description = topicState.data.topic.longDescription,
news = newsState,
imageUrl = topicState.followableTopic.topic.imageUrl
imageUrl = topicState.data.topic.imageUrl
)
}
}
@ -164,7 +167,7 @@ private fun LazyListScope.TopicCards(news: NewsUiState) {
when (news) {
is NewsUiState.Success -> {
newsResourceCardItems(
items = news.news,
items = news.data.newsResources,
newsResourceMapper = { it },
isBookmarkedMapper = { /* TODO */ false },
onToggleBookmark = { /* TODO */ },
@ -187,7 +190,7 @@ private fun TopicBodyPreview() {
LazyColumn {
TopicBody(
"Jetpack Compose", "Lorem ipsum maximum",
NewsUiState.Success(emptyList()), ""
NewsUiState.Success(NewsResourceExtensive(emptyList())), ""
)
}
}
@ -238,7 +241,7 @@ fun TopicScreenPopulated() {
NiaBackground {
TopicScreen(
topicState = TopicUiState.Success(FollowableTopic(previewTopics[0], false)),
newsState = NewsUiState.Success(previewNewsResources),
newsState = NewsUiState.Success(previewNewsResources.toExtensive()),
onBackClick = {},
onFollowClick = {}
)

@ -19,12 +19,15 @@ package com.google.samples.apps.nowinandroid.feature.topic
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.samples.apps.nowinandroid.core.data.model.NewsUiState
import com.google.samples.apps.nowinandroid.core.data.model.TopicUiState
import com.google.samples.apps.nowinandroid.core.data.repository.NewsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.model.data.toExtensive
import com.google.samples.apps.nowinandroid.core.result.Result
import com.google.samples.apps.nowinandroid.core.result.asResult
import com.google.samples.apps.nowinandroid.feature.topic.navigation.TopicDestination
@ -73,7 +76,7 @@ class TopicViewModel @Inject constructor(
if (topicResult is Result.Success && followedTopicsResult is Result.Success) {
val followed = followedTopicsResult.data.contains(topicId)
TopicUiState.Success(
followableTopic = FollowableTopic(
data = FollowableTopic(
topic = topicResult.data,
isFollowed = followed
)
@ -87,7 +90,7 @@ class TopicViewModel @Inject constructor(
}
val news: NewsUiState = when (newsResult) {
is Result.Success -> NewsUiState.Success(newsResult.data)
is Result.Success -> NewsUiState.Success(newsResult.data.toExtensive())
is Result.Loading -> NewsUiState.Loading
is Result.Error -> NewsUiState.Error
}
@ -107,18 +110,6 @@ class TopicViewModel @Inject constructor(
}
}
sealed interface TopicUiState {
data class Success(val followableTopic: FollowableTopic) : TopicUiState
object Error : TopicUiState
object Loading : TopicUiState
}
sealed interface NewsUiState {
data class Success(val news: List<NewsResource>) : NewsUiState
object Error : NewsUiState
object Loading : NewsUiState
}
data class TopicScreenUiState(
val topicState: TopicUiState,
val newsState: NewsUiState

@ -36,6 +36,7 @@ kotlinxCoroutines = "1.6.3"
kotlinxDatetime = "0.3.3"
kotlinxSerializationJson = "1.3.3"
ksp = "1.7.0-1.0.6"
sealedx = "1.0.0"
ktlint = "0.43.0"
lint = "30.2.1"
okhttp = "4.10.0"
@ -120,6 +121,8 @@ room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
secrets-gradlePlugin = { group = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", name = "secrets-gradle-plugin", version.ref = "secrets" }
spotless-gradlePlugin = { group = "com.diffplug.spotless", name = "spotless-plugin-gradle", version.ref = "spotless" }
sealedx-core = { group = "com.github.skydoves", name = "sealedx-core", version.ref = "sealedx" }
sealedx-processor = { group = "com.github.skydoves", name = "sealedx-processor", version.ref = "sealedx" }
[plugins]
protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" }

Loading…
Cancel
Save