Skip to content

Commit 0a748bc

Browse files
committed
Merge branch 'main' of github.com:takagimeow/nowinandroid into fix/clean-up-unused-update-topics
2 parents 86c021a + abf2b6a commit 0a748bc

File tree

14 files changed

+227
-75
lines changed

14 files changed

+227
-75
lines changed

core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/NewsRepository.kt

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,30 @@ import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
2121
import kotlinx.coroutines.flow.Flow
2222

2323
/**
24-
* Data layer implementation for [NewsResource]
24+
* Encapsulation class for query parameters for [NewsResource]
2525
*/
26-
interface NewsRepository : Syncable {
26+
data class NewsResourceQuery(
27+
/**
28+
* Topic ids to filter for. Null means any topic id will match.
29+
*/
30+
val filterTopicIds: Set<String>? = null,
2731
/**
28-
* Returns available news resources as a stream.
32+
* News ids to filter for. Null means any news id will match.
2933
*/
30-
fun getNewsResources(): Flow<List<NewsResource>>
34+
val filterNewsIds: Set<String>? = null,
35+
)
3136

37+
/**
38+
* Data layer implementation for [NewsResource]
39+
*/
40+
interface NewsRepository : Syncable {
3241
/**
33-
* Returns available news resources as a stream filtered by topics.
42+
* Returns available news resources that match the specified [query].
3443
*/
3544
fun getNewsResources(
36-
filterTopicIds: Set<String> = emptySet(),
45+
query: NewsResourceQuery = NewsResourceQuery(
46+
filterTopicIds = null,
47+
filterNewsIds = null,
48+
),
3749
): Flow<List<NewsResource>>
3850
}

core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstNewsRepository.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,13 @@ class OfflineFirstNewsRepository @Inject constructor(
4444
private val network: NiaNetworkDataSource,
4545
) : NewsRepository {
4646

47-
override fun getNewsResources(): Flow<List<NewsResource>> =
48-
newsResourceDao.getNewsResources()
49-
.map { it.map(PopulatedNewsResource::asExternalModel) }
50-
5147
override fun getNewsResources(
52-
filterTopicIds: Set<String>,
48+
query: NewsResourceQuery,
5349
): Flow<List<NewsResource>> = newsResourceDao.getNewsResources(
54-
filterTopicIds = filterTopicIds,
50+
useFilterTopicIds = query.filterTopicIds != null,
51+
filterTopicIds = query.filterTopicIds ?: emptySet(),
52+
useFilterNewsIds = query.filterNewsIds != null,
53+
filterNewsIds = query.filterNewsIds ?: emptySet(),
5554
)
5655
.map { it.map(PopulatedNewsResource::asExternalModel) }
5756

core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeNewsRepository.kt

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package com.google.samples.apps.nowinandroid.core.data.repository.fake
1919
import com.google.samples.apps.nowinandroid.core.data.Synchronizer
2020
import com.google.samples.apps.nowinandroid.core.data.model.asEntity
2121
import com.google.samples.apps.nowinandroid.core.data.repository.NewsRepository
22+
import com.google.samples.apps.nowinandroid.core.data.repository.NewsResourceQuery
2223
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceEntity
2324
import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel
2425
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
@@ -43,26 +44,28 @@ class FakeNewsRepository @Inject constructor(
4344
private val datasource: FakeNiaNetworkDataSource,
4445
) : NewsRepository {
4546

46-
override fun getNewsResources(): Flow<List<NewsResource>> =
47-
flow {
48-
emit(
49-
datasource.getNewsResources()
50-
.map(NetworkNewsResource::asEntity)
51-
.map(NewsResourceEntity::asExternalModel),
52-
)
53-
}.flowOn(ioDispatcher)
54-
5547
override fun getNewsResources(
56-
filterTopicIds: Set<String>,
48+
query: NewsResourceQuery,
5749
): Flow<List<NewsResource>> =
5850
flow {
5951
emit(
6052
datasource
6153
.getNewsResources()
62-
.filter { it.topics.intersect(filterTopicIds).isNotEmpty() }
54+
.filter { networkNewsResource ->
55+
// Filter out any news resources which don't match the current query.
56+
// If no query parameters (filterTopicIds or filterNewsIds) are specified
57+
// then the news resource is returned.
58+
listOfNotNull(
59+
true,
60+
query.filterNewsIds?.contains(networkNewsResource.id),
61+
query.filterTopicIds?.let { filterTopicIds ->
62+
networkNewsResource.topics.intersect(filterTopicIds).isNotEmpty()
63+
},
64+
)
65+
.all(true::equals)
66+
}
6367
.map(NetworkNewsResource::asEntity)
6468
.map(NewsResourceEntity::asExternalModel),
65-
6669
)
6770
}.flowOn(ioDispatcher)
6871

core/data/src/test/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstNewsRepositoryTest.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,21 +92,26 @@ class OfflineFirstNewsRepositoryTest {
9292
fun offlineFirstNewsRepository_news_resources_for_topic_is_backed_by_news_resource_dao() =
9393
runTest {
9494
assertEquals(
95-
newsResourceDao.getNewsResources(
95+
expected = newsResourceDao.getNewsResources(
9696
filterTopicIds = filteredInterestsIds,
97+
useFilterTopicIds = true,
9798
)
9899
.first()
99100
.map(PopulatedNewsResource::asExternalModel),
100-
subject.getNewsResources(
101-
filterTopicIds = filteredInterestsIds,
101+
actual = subject.getNewsResources(
102+
query = NewsResourceQuery(
103+
filterTopicIds = filteredInterestsIds,
104+
),
102105
)
103106
.first(),
104107
)
105108

106109
assertEquals(
107110
emptyList(),
108111
subject.getNewsResources(
109-
filterTopicIds = nonPresentInterestsIds,
112+
query = NewsResourceQuery(
113+
filterTopicIds = nonPresentInterestsIds,
114+
),
110115
)
111116
.first(),
112117
)

core/data/src/test/java/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNewsResourceDao.kt

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,27 @@ class TestNewsResourceDao : NewsResourceDao {
5252

5353
internal var topicCrossReferences: List<NewsResourceTopicCrossRef> = listOf()
5454

55-
override fun getNewsResources(): Flow<List<PopulatedNewsResource>> =
56-
entitiesStateFlow.map {
57-
it.map(NewsResourceEntity::asPopulatedNewsResource)
58-
}
59-
6055
override fun getNewsResources(
56+
useFilterTopicIds: Boolean,
6157
filterTopicIds: Set<String>,
58+
useFilterNewsIds: Boolean,
59+
filterNewsIds: Set<String>,
6260
): Flow<List<PopulatedNewsResource>> =
63-
getNewsResources()
61+
entitiesStateFlow
62+
.map { it.map(NewsResourceEntity::asPopulatedNewsResource) }
6463
.map { resources ->
65-
resources.filter { resource ->
66-
resource.topics.any { it.id in filterTopicIds }
64+
var result = resources
65+
if (useFilterTopicIds) {
66+
result = result.filter { resource ->
67+
resource.topics.any { it.id in filterTopicIds }
68+
}
69+
}
70+
if (useFilterNewsIds) {
71+
result = result.filter { resource ->
72+
resource.entity.id in filterNewsIds
73+
}
6774
}
75+
result
6876
}
6977

7078
override suspend fun insertOrIgnoreNewsResources(

core/database/src/androidTest/java/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceDaoTest.kt

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,44 @@ class NewsResourceDaoTest {
8484
)
8585
}
8686

87+
@Test
88+
fun newsResourceDao_filters_items_by_news_ids_by_descending_publish_date() = runTest {
89+
val newsResourceEntities = listOf(
90+
testNewsResource(
91+
id = "0",
92+
millisSinceEpoch = 0,
93+
),
94+
testNewsResource(
95+
id = "1",
96+
millisSinceEpoch = 3,
97+
),
98+
testNewsResource(
99+
id = "2",
100+
millisSinceEpoch = 1,
101+
),
102+
testNewsResource(
103+
id = "3",
104+
millisSinceEpoch = 2,
105+
),
106+
)
107+
newsResourceDao.upsertNewsResources(
108+
newsResourceEntities,
109+
)
110+
111+
val savedNewsResourceEntities = newsResourceDao.getNewsResources(
112+
useFilterNewsIds = true,
113+
filterNewsIds = setOf("3", "0"),
114+
)
115+
.first()
116+
117+
assertEquals(
118+
listOf("3", "0"),
119+
savedNewsResourceEntities.map {
120+
it.entity.id
121+
},
122+
)
123+
}
124+
87125
@Test
88126
fun newsResourceDao_filters_items_by_topic_ids_by_descending_publish_date() = runTest {
89127
val topicEntities = listOf(
@@ -132,6 +170,7 @@ class NewsResourceDaoTest {
132170
)
133171

134172
val filteredNewsResources = newsResourceDao.getNewsResources(
173+
useFilterTopicIds = true,
135174
filterTopicIds = topicEntities
136175
.map(TopicEntity::id)
137176
.toSet(),
@@ -143,6 +182,68 @@ class NewsResourceDaoTest {
143182
)
144183
}
145184

185+
@Test
186+
fun newsResourceDao_filters_items_by_news_and_topic_ids_by_descending_publish_date() = runTest {
187+
val topicEntities = listOf(
188+
testTopicEntity(
189+
id = "1",
190+
name = "1",
191+
),
192+
testTopicEntity(
193+
id = "2",
194+
name = "2",
195+
),
196+
)
197+
val newsResourceEntities = listOf(
198+
testNewsResource(
199+
id = "0",
200+
millisSinceEpoch = 0,
201+
),
202+
testNewsResource(
203+
id = "1",
204+
millisSinceEpoch = 3,
205+
),
206+
testNewsResource(
207+
id = "2",
208+
millisSinceEpoch = 1,
209+
),
210+
testNewsResource(
211+
id = "3",
212+
millisSinceEpoch = 2,
213+
),
214+
)
215+
val newsResourceTopicCrossRefEntities = topicEntities.mapIndexed { index, topicEntity ->
216+
NewsResourceTopicCrossRef(
217+
newsResourceId = index.toString(),
218+
topicId = topicEntity.id,
219+
)
220+
}
221+
222+
topicDao.insertOrIgnoreTopics(
223+
topicEntities = topicEntities,
224+
)
225+
newsResourceDao.upsertNewsResources(
226+
newsResourceEntities,
227+
)
228+
newsResourceDao.insertOrIgnoreTopicCrossRefEntities(
229+
newsResourceTopicCrossRefEntities,
230+
)
231+
232+
val filteredNewsResources = newsResourceDao.getNewsResources(
233+
useFilterTopicIds = true,
234+
filterTopicIds = topicEntities
235+
.map(TopicEntity::id)
236+
.toSet(),
237+
useFilterNewsIds = true,
238+
filterNewsIds = setOf("1"),
239+
).first()
240+
241+
assertEquals(
242+
listOf("1"),
243+
filteredNewsResources.map { it.entity.id },
244+
)
245+
}
246+
146247
@Test
147248
fun newsResourceDao_deletes_items_by_ids() =
148249
runTest {

core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceDao.kt

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,36 @@ import kotlinx.coroutines.flow.Flow
3333
*/
3434
@Dao
3535
interface NewsResourceDao {
36-
@Transaction
37-
@Query(
38-
value = """
39-
SELECT * FROM news_resources
40-
ORDER BY publish_date DESC
41-
""",
42-
)
43-
fun getNewsResources(): Flow<List<PopulatedNewsResource>>
4436

37+
/**
38+
* Fetches news resources that match the query parameters
39+
*/
4540
@Transaction
4641
@Query(
4742
value = """
4843
SELECT * FROM news_resources
49-
WHERE id in
50-
(
51-
SELECT news_resource_id FROM news_resources_topics
52-
WHERE topic_id IN (:filterTopicIds)
53-
)
44+
WHERE
45+
CASE WHEN :useFilterNewsIds
46+
THEN id IN (:filterNewsIds)
47+
ELSE 1
48+
END
49+
AND
50+
CASE WHEN :useFilterTopicIds
51+
THEN id IN
52+
(
53+
SELECT news_resource_id FROM news_resources_topics
54+
WHERE topic_id IN (:filterTopicIds)
55+
)
56+
ELSE 1
57+
END
5458
ORDER BY publish_date DESC
5559
""",
5660
)
5761
fun getNewsResources(
62+
useFilterTopicIds: Boolean = false,
5863
filterTopicIds: Set<String> = emptySet(),
64+
useFilterNewsIds: Boolean = false,
65+
filterNewsIds: Set<String> = emptySet(),
5966
): Flow<List<PopulatedNewsResource>>
6067

6168
/**

core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.samples.apps.nowinandroid.core.domain
1818

1919
import com.google.samples.apps.nowinandroid.core.data.repository.NewsRepository
20+
import com.google.samples.apps.nowinandroid.core.data.repository.NewsResourceQuery
2021
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
2122
import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource
2223
import com.google.samples.apps.nowinandroid.core.domain.model.mapToUserNewsResources
@@ -38,17 +39,14 @@ class GetUserNewsResourcesUseCase @Inject constructor(
3839
/**
3940
* Returns a list of UserNewsResources which match the supplied set of topic ids.
4041
*
41-
* @param filterTopicIds - A set of topic ids used to filter the list of news resources. If
42-
* this is empty the list of news resources will not be filtered.
42+
* @param query - Summary of query parameters for news resources.
4343
*/
4444
operator fun invoke(
45-
filterTopicIds: Set<String> = emptySet(),
45+
query: NewsResourceQuery = NewsResourceQuery(),
4646
): Flow<List<UserNewsResource>> =
47-
if (filterTopicIds.isEmpty()) {
48-
newsRepository.getNewsResources()
49-
} else {
50-
newsRepository.getNewsResources(filterTopicIds = filterTopicIds)
51-
}.mapToUserNewsResources(userDataRepository.userData)
47+
newsRepository.getNewsResources(
48+
query = query,
49+
).mapToUserNewsResources(userDataRepository.userData)
5250
}
5351

5452
private fun Flow<List<NewsResource>>.mapToUserNewsResources(

core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCaseTest.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.samples.apps.nowinandroid.core.domain
1818

19+
import com.google.samples.apps.nowinandroid.core.data.repository.NewsResourceQuery
1920
import com.google.samples.apps.nowinandroid.core.domain.model.mapToUserNewsResources
2021
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
2122
import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Video
@@ -67,7 +68,11 @@ class GetUserNewsResourcesUseCaseTest {
6768
@Test
6869
fun whenFilteredByTopicId_matchingNewsResourcesAreReturned() = runTest {
6970
// Obtain a stream of user news resources for the given topic id.
70-
val userNewsResources = useCase(filterTopicIds = setOf(sampleTopic1.id))
71+
val userNewsResources = useCase(
72+
NewsResourceQuery(
73+
filterTopicIds = setOf(sampleTopic1.id),
74+
),
75+
)
7176

7277
// Send test data into the repositories.
7378
newsRepository.sendNewsResources(sampleNewsResources)

0 commit comments

Comments
 (0)