Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import io.getstream.feeds.android.client.api.model.FeedMemberData
import io.getstream.feeds.android.client.api.model.FeedSuggestionData
import io.getstream.feeds.android.client.api.model.FollowData
import io.getstream.feeds.android.client.api.model.ModelUpdates
import io.getstream.feeds.android.client.api.model.PaginationData
import io.getstream.feeds.android.client.api.state.query.FeedQuery
import io.getstream.feeds.android.client.api.state.query.FeedsQuery
import io.getstream.feeds.android.client.internal.model.PaginationResult
Expand Down Expand Up @@ -124,14 +125,15 @@ internal interface FeedsRepository {
* @property notificationStatus The notification status for the feed, if available.
*/
internal data class GetOrCreateInfo(
val activities: PaginationResult<ActivityData>,
val pagination: PaginationData,
val activities: List<ActivityData>,
val activitiesQueryConfig: ActivitiesQueryConfig,
val aggregatedActivities: List<AggregatedActivityData>,
val feed: FeedData,
val followers: List<FollowData>,
val following: List<FollowData>,
val followRequests: List<FollowData>,
val members: PaginationResult<FeedMemberData>,
val pinnedActivities: List<ActivityPinData>,
val aggregatedActivities: List<AggregatedActivityData>,
val notificationStatus: NotificationStatusResponse?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,12 @@ internal class FeedsRepositoryImpl(private val api: FeedsApi) : FeedsRepository
)
val rawFollowers = response.followers.map { it.toModel() }
GetOrCreateInfo(
pagination = PaginationData(next = response.next, previous = response.prev),
activities =
PaginationResult(
models =
response.activities.map { it.toModel() }.sortedWith(ActivitiesSort.Default),
pagination = PaginationData(next = response.next, previous = response.prev),
),
response.activities.map { it.toModel() }.sortedWith(ActivitiesSort.Default),
activitiesQueryConfig =
QueryConfiguration(filter = query.activityFilter, sort = ActivitiesSort.Default),
aggregatedActivities = response.aggregatedActivities.map { it.toModel() },
feed = response.feed.toModel(),
followers = rawFollowers.filter { it.isFollowerOf(fid) },
following = response.following.map { it.toModel() }.filter { it.isFollowing(fid) },
Expand All @@ -82,7 +80,6 @@ internal class FeedsRepositoryImpl(private val api: FeedsApi) : FeedsRepository
pagination = response.memberPagination?.toModel() ?: PaginationData.EMPTY,
),
pinnedActivities = response.pinnedActivities.map { it.toModel() },
aggregatedActivities = response.aggregatedActivities.map { it.toModel() },
notificationStatus = response.notificationStatus,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import io.getstream.feeds.android.client.internal.repository.ActivitiesRepositor
import io.getstream.feeds.android.client.internal.repository.BookmarksRepository
import io.getstream.feeds.android.client.internal.repository.CommentsRepository
import io.getstream.feeds.android.client.internal.repository.FeedsRepository
import io.getstream.feeds.android.client.internal.repository.GetOrCreateInfo
import io.getstream.feeds.android.client.internal.repository.PollsRepository
import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent
import io.getstream.feeds.android.client.internal.state.event.handler.FeedEventHandler
Expand Down Expand Up @@ -228,8 +229,15 @@ internal class FeedImpl(
)
return feedsRepository
.getOrCreateFeed(query)
.onSuccess { _state.onQueryMoreActivities(it.activities, it.activitiesQueryConfig) }
.map { it.activities.models }
.onSuccess {
_state.onQueryMoreActivities(
activities = it.activities,
aggregatedActivities = it.aggregatedActivities,
pagination = it.pagination,
queryConfig = it.activitiesQueryConfig,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for my better understanding: Does the aggregatedActivities result also respect the queryConfig (filter + sort)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at it, I'm realizing that activity sorting is something we only apply locally (there is no sorting property in the request) and we only apply it to activities.

For the filtering, it seems that only ID-based filtering is applied to aggregated activities.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also seeing that our public API doesn't allow integrators to pass a sorting, so it seems it's just an internal value 🤔

)
}
.map(GetOrCreateInfo::activities)
}

override suspend fun addBookmark(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import io.getstream.feeds.android.client.api.model.PollVoteData
import io.getstream.feeds.android.client.api.state.FeedState
import io.getstream.feeds.android.client.api.state.query.ActivitiesSort
import io.getstream.feeds.android.client.api.state.query.FeedQuery
import io.getstream.feeds.android.client.internal.model.PaginationResult
import io.getstream.feeds.android.client.internal.model.QueryConfiguration
import io.getstream.feeds.android.client.internal.model.deleteBookmark
import io.getstream.feeds.android.client.internal.model.isFollowRequest
Expand All @@ -53,6 +52,7 @@ import io.getstream.feeds.android.client.internal.state.query.ActivitiesQueryCon
import io.getstream.feeds.android.client.internal.utils.mergeSorted
import io.getstream.feeds.android.client.internal.utils.updateIf
import io.getstream.feeds.android.client.internal.utils.upsert
import io.getstream.feeds.android.client.internal.utils.upsertAll
import io.getstream.feeds.android.client.internal.utils.upsertSorted
import io.getstream.feeds.android.network.models.NotificationStatusResponse
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -130,10 +130,10 @@ internal class FeedStateImpl(
get() = _activitiesPagination

override fun onQueryFeed(result: GetOrCreateInfo) {
_activities.update { result.activities.models }
_activitiesPagination = result.activities.pagination
activitiesQueryConfig = result.activitiesQueryConfig
_activities.update { result.activities }
_aggregatedActivities.update { result.aggregatedActivities }
_activitiesPagination = result.pagination
activitiesQueryConfig = result.activitiesQueryConfig
_feed.update { result.feed }
_followers.update { result.followers }
_following.update { result.following }
Expand All @@ -146,14 +146,19 @@ internal class FeedStateImpl(
}

override fun onQueryMoreActivities(
result: PaginationResult<ActivityData>,
activities: List<ActivityData>,
aggregatedActivities: List<AggregatedActivityData>,
pagination: PaginationData,
queryConfig: ActivitiesQueryConfig,
) {
_activitiesPagination = result.pagination
_activitiesPagination = pagination
activitiesQueryConfig = queryConfig
// Merge the new activities with the existing ones (keeping the sort order)
_activities.update { current ->
current.mergeSorted(result.models, ActivityData::id, activitiesSorting)
current.mergeSorted(activities, ActivityData::id, activitiesSorting)
}
_aggregatedActivities.update { current ->
current.upsertAll(aggregatedActivities, AggregatedActivityData::group)
}
}

Expand Down Expand Up @@ -386,7 +391,9 @@ internal interface FeedStateUpdates {

/** Handles the result of a query for more activities. */
fun onQueryMoreActivities(
result: PaginationResult<ActivityData>,
activities: List<ActivityData>,
aggregatedActivities: List<AggregatedActivityData>,
pagination: PaginationData,
queryConfig: ActivitiesQueryConfig,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,37 @@ internal fun <T, ID> List<T>.upsertSorted(
update: (old: T) -> T = { element },
): List<T> = upsertSorted(element, idSelector, CompositeComparator(sort), update)

/**
* Upserts all elements from another list into this list based on a specified key.
*
* This function updates existing elements in the list that have the same key (as determined by
* [idSelector]) as elements in [that] list. If an element from [that] does not exist in this list,
* it is appended.
*
* @param that The list of elements to upsert into this list.
* @param idSelector A function that extracts a key from an element. This is used to determine
* whether an element already exists in the list.
* @return A new list containing the upserted elements. Existing elements are updated, and new
* elements are appended.
*/
internal fun <T, R> List<T>.upsertAll(that: List<T>, idSelector: (T) -> R): List<T> {
// Using LinkedHashMap instead of e.g. HashMap to preserve order
val toUpsert = that.associateByTo(LinkedHashMap(), idSelector)
val result = ArrayList<T>(this.size + that.size)

// Update what should be updated
forEach { item ->
val key = idSelector(item)
result.add(toUpsert[key] ?: item)
toUpsert.remove(key)
}

// Insert the rest
result.addAll(toUpsert.values)

return result
}

/**
* Merges two sorted arrays while maintaining the sort order and handling duplicates.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,24 +75,21 @@ internal class FeedsRepositoryImplTest {
apiResult = apiResult,
repositoryResult =
GetOrCreateInfo(
activities =
PaginationResult(
models = emptyList(),
pagination = PaginationData(next = "next", previous = "prev"),
),
pagination = PaginationData(next = "next", previous = "prev"),
activities = emptyList(),
activitiesQueryConfig =
QueryConfiguration(
filter = query.activityFilter,
sort = ActivitiesSort.Default,
),
aggregatedActivities = emptyList(),
feed = apiResult.feed.toModel(),
followers = emptyList(),
following = emptyList(),
followRequests = emptyList(),
members =
PaginationResult(models = emptyList(), pagination = PaginationData.EMPTY),
pinnedActivities = emptyList(),
aggregatedActivities = emptyList(),
notificationStatus = null,
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -772,16 +772,17 @@ internal class FeedImplTest {
val paginationData = PaginationData(next = "cursor")

return GetOrCreateInfo(
activities = PaginationResult(models = activities, pagination = paginationData),
pagination = paginationData,
activities = activities,
activitiesQueryConfig =
QueryConfiguration(filter = null, sort = ActivitiesSort.Default),
aggregatedActivities = emptyList(),
feed = testFeedData,
followers = followers,
following = following,
followRequests = followRequests,
members = PaginationResult(models = members, pagination = paginationData),
pinnedActivities = emptyList(),
aggregatedActivities = emptyList(),
notificationStatus = null,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,21 +88,42 @@ internal class FeedStateImplTest {
}

@Test
fun `on queryMoreActivities, then merge activities`() = runTest {
val initialActivities = listOf(activityData())
setupInitialState(initialActivities)
fun `on queryMoreActivities, merge with new activities and aggregated activities`() = runTest {
val initialActivities = listOf(activityData("activity-1"))
val initialAggregated =
listOf(
aggregatedActivityData(
group = "group-1",
activities = listOf(activityData("activity-1")),
activityCount = 1,
)
)
setupInitialState(activities = initialActivities, aggregatedActivities = initialAggregated)

val newActivities = listOf(activityData("activity-2"), activityData("activity-3"))
val newPaginationResult =
PaginationResult(
models = newActivities,
pagination = PaginationData(next = "next-cursor-2", previous = null),
val newAggregated =
listOf(
aggregatedActivityData(
group = "group-2",
activities = listOf(activityData("activity-2")),
),
aggregatedActivityData(
group = "group-3",
activities = listOf(activityData("activity-3")),
),
)
val newPagination = PaginationData(next = "next-cursor-2", previous = null)

feedState.onQueryMoreActivities(newPaginationResult, createQueryConfig())
feedState.onQueryMoreActivities(
activities = newActivities,
aggregatedActivities = newAggregated,
pagination = newPagination,
queryConfig = createQueryConfig(),
)

assertEquals(3, feedState.activities.value.size)
assertEquals("next-cursor-2", feedState.activitiesPagination?.next)
assertEquals(newPagination, feedState.activitiesPagination)
assertEquals(initialActivities + newActivities, feedState.activities.value)
assertEquals((initialAggregated + newAggregated), feedState.aggregatedActivities.value)
}

@Test
Expand Down Expand Up @@ -595,22 +616,19 @@ internal class FeedStateImplTest {
pinnedActivities: List<ActivityPinData> = emptyList(),
aggregatedActivities: List<AggregatedActivityData> = emptyList(),
): GetOrCreateInfo {
val paginationResult =
PaginationResult(
models = activities,
pagination = PaginationData(next = "next-cursor", previous = null),
)
val pagination = PaginationData(next = "next-cursor", previous = null)
val queryConfig = createQueryConfig()

return GetOrCreateInfo(
activities = paginationResult,
pagination = pagination,
activities = activities,
activitiesQueryConfig = queryConfig,
aggregatedActivities = aggregatedActivities,
feed = feed,
followers = followers,
following = following,
followRequests = followRequests,
pinnedActivities = pinnedActivities,
aggregatedActivities = aggregatedActivities,
notificationStatus = null,
members =
PaginationResult(
Expand Down
Loading