refactor: improve read posts search performance

This commit is contained in:
Harsh Shandilya 2024-01-29 17:22:23 +05:30
parent 89b821ebf1
commit cbd7f2fca4
6 changed files with 31 additions and 44 deletions

View file

@ -57,8 +57,6 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ContributesViewModel @ContributesViewModel
@ -84,7 +82,7 @@ constructor(
pagingSourceFactory = { pagingSourceFactory.create(api::getHottestPosts) }, pagingSourceFactory = { pagingSourceFactory.create(api::getHottestPosts) },
) )
.flow .flow
.map(::mapUIPost) .map(::mapToUIPost)
.cachedIn(viewModelScope) .cachedIn(viewModelScope)
val newestPosts = val newestPosts =
@ -94,7 +92,7 @@ constructor(
pagingSourceFactory = { pagingSourceFactory.create(api::getNewestPosts) }, pagingSourceFactory = { pagingSourceFactory.create(api::getNewestPosts) },
) )
.flow .flow
.map(::mapUIPost) .map(::mapToUIPost)
.cachedIn(viewModelScope) .cachedIn(viewModelScope)
val searchResults = val searchResults =
Pager( Pager(
@ -103,38 +101,33 @@ constructor(
pagingSourceFactory = { searchPagingSourceFactory.create { searchQuery } }, pagingSourceFactory = { searchPagingSourceFactory.create { searchQuery } },
) )
.flow .flow
.map(::mapUIPost) .map(::mapToUIPost)
val savedPosts val savedPosts =
get() = savedPostsRepository.savedPosts
savedPostsRepository.savedPosts .map { it.map(UIPost.Companion::fromSavedPost) }
.map { it.map(UIPost.Companion::fromSavedPost) } .shareIn(viewModelScope, started = SharingStarted.Lazily, Int.MAX_VALUE)
.shareIn(viewModelScope, started = SharingStarted.Lazily, Int.MAX_VALUE)
val savedPostsByMonth val savedPostsByMonth = savedPosts.map(::groupSavedPosts)
get() = savedPosts.map(::mapSavedPosts)
var searchQuery by mutableStateOf("") var searchQuery by mutableStateOf("")
private val _savedPostsMutex = Mutex() private var _readPosts = emptyList<String>()
private var _savedPosts = emptyList<String>() private var _savedPosts = emptyList<String>()
init { init {
viewModelScope.launch { viewModelScope.launch { savedPosts.collectLatest { _savedPosts = it.map(UIPost::shortId) } }
savedPosts.collectLatest { viewModelScope.launch { readPostsRepository.readPosts.collectLatest { _readPosts = it } }
_savedPostsMutex.withLock { _savedPosts = it.map(UIPost::shortId) }
}
}
} }
private fun mapUIPost(pagingData: PagingData<LobstersPost>): PagingData<UIPost> { private fun mapToUIPost(pagingData: PagingData<LobstersPost>): PagingData<UIPost> {
return pagingData.map { post -> return pagingData.map { post ->
val uiPost = post.toUIPost() val uiPost = post.toUIPost()
uiPost.copy(isSaved = isPostSaved(uiPost), isRead = isPostRead(uiPost)) uiPost.copy(isSaved = isPostSaved(uiPost), isRead = isPostRead(uiPost))
} }
} }
private fun mapSavedPosts(items: List<UIPost>): ImmutableMap<String, List<UIPost>> { private fun groupSavedPosts(items: List<UIPost>): ImmutableMap<String, List<UIPost>> {
val sorted = val sorted =
items.sortedWith { post1, post2 -> items.sortedWith { post1, post2 ->
val post1Date = post1.createdAt.toLocalDateTime() val post1Date = post1.createdAt.toLocalDateTime()
@ -159,7 +152,9 @@ constructor(
return _savedPosts.contains(post.shortId) return _savedPosts.contains(post.shortId)
} }
private fun isPostRead(post: UIPost) = readPostsRepository.isRead(post.shortId) private fun isPostRead(post: UIPost): Boolean {
return _readPosts.contains(post.shortId)
}
fun toggleSave(post: UIPost) { fun toggleSave(post: UIPost) {
viewModelScope.launch(ioDispatcher) { viewModelScope.launch(ioDispatcher) {

View file

@ -6,6 +6,8 @@
*/ */
package dev.msfjarvis.claw.android.viewmodel package dev.msfjarvis.claw.android.viewmodel
import app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToList
import dev.msfjarvis.claw.core.injection.DatabaseDispatcher import dev.msfjarvis.claw.core.injection.DatabaseDispatcher
import dev.msfjarvis.claw.database.local.ReadPostsQueries import dev.msfjarvis.claw.database.local.ReadPostsQueries
import javax.inject.Inject import javax.inject.Inject
@ -18,12 +20,9 @@ constructor(
private val readPostsQueries: ReadPostsQueries, private val readPostsQueries: ReadPostsQueries,
@DatabaseDispatcher private val dbDispatcher: CoroutineDispatcher, @DatabaseDispatcher private val dbDispatcher: CoroutineDispatcher,
) { ) {
val readPosts = readPostsQueries.selectAllPosts().asFlow().mapToList(dbDispatcher)
suspend fun markRead(postId: String) { suspend fun markRead(postId: String) {
withContext(dbDispatcher) { readPostsQueries.markRead(postId) } withContext(dbDispatcher) { readPostsQueries.markRead(postId) }
} }
fun isRead(postId: String): Boolean {
return readPostsQueries.isRead(postId).executeAsOneOrNull() != null
}
} }

View file

@ -2,16 +2,15 @@ CREATE TABLE ReadPosts(
id TEXT NOT NULL PRIMARY KEY id TEXT NOT NULL PRIMARY KEY
); );
selectAllPosts:
SELECT *
FROM ReadPosts;
markRead: markRead:
INSERT OR REPLACE INSERT OR IGNORE
INTO ReadPosts(id) INTO ReadPosts(id)
VALUES (?); VALUES (?);
markUnread: markUnread:
DELETE FROM ReadPosts DELETE FROM ReadPosts
WHERE id = ?; WHERE id = ?;
isRead:
SELECT *
FROM ReadPosts
WHERE id = ?;

View file

@ -36,8 +36,3 @@ deletePost:
DELETE DELETE
FROM SavedPost FROM SavedPost
WHERE shortId = ?; WHERE shortId = ?;
selectPost:
SELECT *
FROM SavedPost
WHERE shortId = ?;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2023 Harsh Shandilya. * Copyright © 2023-2024 Harsh Shandilya.
* Use of this source code is governed by an MIT-style * Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at * license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT. * https://opensource.org/licenses/MIT.
@ -23,8 +23,8 @@ class ReadPostsQueriesTest {
fun `mark post as read`() { fun `mark post as read`() {
val id = UUID.randomUUID().toString() val id = UUID.randomUUID().toString()
postQueries.markRead(id) postQueries.markRead(id)
assertThat(postQueries.isRead(id).executeAsOne()).isNotNull() assertThat(postQueries.selectAllPosts().executeAsList()).contains(id)
postQueries.markUnread(id) postQueries.markUnread(id)
assertThat(postQueries.isRead(id).executeAsOneOrNull()).isNull() assertThat(postQueries.selectAllPosts().executeAsList()).doesNotContain(id)
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2021-2023 Harsh Shandilya. * Copyright © 2021-2024 Harsh Shandilya.
* Use of this source code is governed by an MIT-style * Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at * license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT. * https://opensource.org/licenses/MIT.
@ -32,7 +32,7 @@ class SavedPostQueriesTest {
@Test @Test
fun `update post in database`() { fun `update post in database`() {
// Get 1 post // Get 1 post
val post = createTestData(1)[0] val post = createTestData(1).first()
// Insert post into DB // Insert post into DB
postQueries.insertOrReplacePost(post) postQueries.insertOrReplacePost(post)
@ -46,15 +46,14 @@ class SavedPostQueriesTest {
assertThat(postsCount).isEqualTo(1) assertThat(postsCount).isEqualTo(1)
// Check if post is updated // Check if post is updated
val postFromDb = postQueries.selectPost(post.shortId).executeAsOne() val postFromDb = postQueries.selectAllPosts().executeAsOne()
assertThat(postFromDb.submitterName).isEqualTo("Fake name") assertThat(postFromDb.submitterName).isEqualTo("Fake name")
} }
@Test @Test
fun `get post from db`() { fun `get post from db`() {
// Get 1 post // Get 1 post
val post = createTestData(1)[0] val post = createTestData(1).first()
// Insert post into DB // Insert post into DB
postQueries.insertOrReplacePost(post) postQueries.insertOrReplacePost(post)