Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deduplicate posts in the feed #1613

Merged
merged 2 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ dependencies {
implementation("androidx.profileinstaller:profileinstaller")
baselineProfile(project(":benchmarks"))

implementation("it.vercruysse.lemmyapi:lemmy-api:0.3.3-SNAPSHOT")
implementation("it.vercruysse.lemmyapi:lemmy-api:0.3.4-SNAPSHOT")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")

// For custom logging plugin
Expand Down
28 changes: 15 additions & 13 deletions app/src/main/java/com/jerboa/feed/FeedController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ open class FeedController<T> {

val feed: List<T> = items

fun updateAll(
open fun updateAll(
selector: (List<T>) -> List<Int>,
transformer: (T) -> T,
) {
Expand All @@ -17,7 +17,7 @@ open class FeedController<T> {
}
}

fun safeUpdate(
open fun safeUpdate(
index: Int,
transformer: (T) -> T,
) {
Expand All @@ -29,7 +29,7 @@ open class FeedController<T> {
safeUpdate(index, transformer(items[index]))
}

fun safeUpdate(
open fun safeUpdate(
selector: (List<T>) -> Int,
transformer: (T) -> T,
) {
Expand All @@ -44,7 +44,7 @@ open class FeedController<T> {
* Example: a network request to update an item succeeded after the list has changed.
* So, we ignore it
*/
fun safeUpdate(
open fun safeUpdate(
index: Int,
new: T,
) {
Expand All @@ -55,26 +55,28 @@ open class FeedController<T> {
}
}

fun init(newItems: List<T>) {
items.clear()
items.addAll(newItems)
open fun init(newItems: List<T>) {
clear()
addAll(newItems)
}

fun get(index: Int): T? = items.getOrNull(index)
open fun get(index: Int): T? = items.getOrNull(index)

fun add(item: T) = items.add(item)
open fun add(item: T) = items.add(item)

fun remove(item: T) = items.remove(item)
open fun remove(item: T) = items.remove(item)

fun removeAt(index: Int) {
open fun removeAt(index: Int) {
if (isValidIndex(index)) {
items.removeAt(index)
}
}

fun clear() = items.clear()
open fun clear() = items.clear()

fun addAll(newItems: List<T>) = items.addAll(newItems)
open fun addAll(newItems: List<T>) {
items.addAll(newItems)
}

protected inline fun <E> Iterable<E>.indexesOf(predicate: (E) -> Boolean) =
mapIndexedNotNull { index, elem ->
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/jerboa/feed/PostController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import it.vercruysse.lemmyapi.datatypes.HidePost
import it.vercruysse.lemmyapi.datatypes.Person
import it.vercruysse.lemmyapi.datatypes.PostView

open class PostController : FeedController<PostView>() {
open class PostController : UniqueFeedController<PostView>() {
fun findAndUpdatePost(updatedPostView: PostView) {
safeUpdate({ posts ->
posts.indexOfFirst {
Expand Down
33 changes: 33 additions & 0 deletions app/src/main/java/com/jerboa/feed/UniqueFeedController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.jerboa.feed

import it.vercruysse.lemmyapi.Identity

open class UniqueFeedController<T : Identity> : FeedController<T>() {
private val ids = mutableSetOf<Long>()

override fun add(item: T): Boolean {
if (ids.add(item.id)) {
items.add(item)
return true
}
return false
}

override fun addAll(newItems: List<T>) {
newItems.forEach {
if (ids.add(it.id)) {
items.add(it)
}
}
}

override fun clear() {
super.clear()
ids.clear()
}

override fun remove(item: T): Boolean {
ids.remove(item.id)
return super.remove(item)
}
}
82 changes: 82 additions & 0 deletions app/src/test/java/com/jerboa/feed/UniqueFeedControllerTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.jerboa.feed

import it.vercruysse.lemmyapi.Identity
import org.junit.Assert.*
import org.junit.Test

class UniqueFeedControllerTest {
private data class PostView(
override val id: Long,
) : Identity

@Test
fun `Should not add duplicate posts`() {
val controller = UniqueFeedController<PostView>()
controller.add(PostView(1))
assertEquals(1, controller.feed.size)
controller.add(PostView(1))
assertEquals(1, controller.feed.size)
controller.add(PostView(2))
assertEquals(2, controller.feed.size)
}

@Test
fun `Should remove post`() {
val controller = UniqueFeedController<PostView>()
controller.add(PostView(1))
assertEquals(1, controller.feed.size)
controller.remove(PostView(1))
assertEquals(0, controller.feed.size)
}

@Test
fun `Post removal should clear id`() {
val controller = UniqueFeedController<PostView>()
controller.add(PostView(1))
assertEquals(1, controller.feed.size)
controller.remove(PostView(1))
assertTrue(controller.feed.isEmpty())
controller.add(PostView(1))
assertEquals(1, controller.feed.size)
}

@Test
fun `Should clear all posts`() {
val controller = UniqueFeedController<PostView>()
controller.add(PostView(1))
controller.add(PostView(2))
assertEquals(2, controller.feed.size)
controller.clear()
assertTrue(controller.feed.isEmpty())
}

@Test
fun `Clear should clear ids`() {
val controller = UniqueFeedController<PostView>()
controller.add(PostView(1))
controller.add(PostView(2))
assertEquals(2, controller.feed.size)
controller.clear()
assertTrue(controller.feed.isEmpty())
controller.add(PostView(1))
controller.add(PostView(2))
assertEquals(2, controller.feed.size)
}

@Test
fun `Add all should not add duplicates`() {
val controller = UniqueFeedController<PostView>()
controller.addAll(listOf(PostView(1), PostView(2), PostView(1)))
assertEquals(2, controller.feed.size)
}

@Test
fun `Init should clear ids`() {
val controller = UniqueFeedController<PostView>()
controller.add(PostView(1))
controller.add(PostView(2))
assertEquals(2, controller.feed.size)
controller.init(listOf(PostView(1), PostView(2)))
assertEquals(2, controller.feed.size)
}
}