The original article was published on March 19, 2025.
Hello, I'm Yūdai Takanashi, a mobile client developer for the Android version of the LINE messaging app.
This article is the latest installment of our weekly series "Improving code quality". For more information about the Weekly Report, please see the first article.
Value betrayal
Suppose you are implementing a program that displays recent article data in the UI. The following ArticleRepository.getRecentArticles() function converts recent article data received from the data source into the Article model and returns it.
data class Article(
val title: String,
val summary: String,
val category: Category,
val pageNumber: Int,
// ...
)
class ArticleRepository(private val dataSource: DataSource) {
fun getRecentArticles(): List<Article> = dataSource
.getArticles(since = LocalDate.now().minusDays(7))
.mapIndexed { index, articleEntity ->
Article(
title = articleEntity.title,
summary = articleEntity.summary,
category = articleEntity.category,
pageNumber = index + 1
// ...
)
}
}
The ArticleViewModel converts the list of Article received from ArticleRepository into a model class ArticleUiState that is more specialized for UI display, and then passes the data to the UI.
class ArticleViewModel(private val articleRepository: ArticleRepository) {
fun getRecentArticles(): List<ArticleUiState> = articleRepository
.getRecentArticles()
.map { article ->
ArticleUiState(
title = article.title,
summary = article.summary,
pageNumber = article.pageNumber
// ...
)
}
data class ArticleUiState(
val title: String,
val summary: String,
val pageNumber: Int,
// ...
)
}
Are there any points to be aware of when reviewing such code?
Inconsistency due to duplicate data
What happens if code is added to filter articles by Category as shown below?
fun getRecentArticles(targetCategory: Category): List<ArticleUiState> = articleRepository
.getRecentArticles()
.filter { it.category == targetCategory } // Added
.map { article ->
ArticleUiState(
title = article.title,
summary = article.summary,
pageNumber = article.pageNumber // This should be incorrect
// ...
)
}
In the ArticleRepository.getRecentArticles() function, although Article.pageNumber is sequentially numbered starting from 1, filtering causes the values to become discontinuous. In this way, when the caller modifies the article list (filtering, sorting, etc.), the value of pageNumber can easily break.
Is duplicate data evil?
Since the index (pageNumber) is not information about the element but information about the collection class, inconsistencies occur if it is kept outside the element. Therefore, such properties should not be created in the first place.
However, there are cases where such data is necessary for rendering the UI. It makes sense to keep such information only within the UI data (ArticleUiState).
Besides this example, there are cases where it is better to have duplicate data for convenience. Cached data is a typical example. When using such data duplication, pay attention to the following points:
- Clearly define which is the primary data.
- Create such data just before use.
In a nutshell
To avoid the risk of data inconsistency, duplicate data should be created just before use.
Keywords: data model, orthogonal relationship, collection, index