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

improve local status updates #3480

Merged
merged 9 commits into from
Sep 26, 2023
Merged

improve local status updates #3480

merged 9 commits into from
Sep 26, 2023

Conversation

connyduck
Copy link
Collaborator

@connyduck connyduck commented Mar 22, 2023

The idea here is: Everytime we get hold of a new version of a post, we update everything about that post everywhere.
This makes the distincion between different event types unnecessary, as everythng is just a StatusChangedEvent.
The main benefit is that posts should be up-to-date more often, which is important considering there is now editing and #3413

if (viewData != null) { detailedStatus = viewData }
val result = api.status(id).getOrNull()
if (result != null) {
eventHub.dispatch(StatusChangedEvent(result))
Copy link
Collaborator Author

@connyduck connyduck Mar 22, 2023

Choose a reason for hiding this comment

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

This is not ideal since ViewThreadViewModel is both sending and observing StatusChangedEvent, but I don't have a better idea :/

Copy link
Collaborator

Choose a reason for hiding this comment

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

I wondered a bit why this case here stands out.

And maybe it doesn't: What happens if I refresh a timeline and one of the status there did change?
(A status that might as well be shown on the bookmark timeline for example?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes maybe we should send the event for all statuses 🤔
Still there is a problem that this ViewModel is both sending and receiving

Copy link
Collaborator

Choose a reason for hiding this comment

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

Just a few ideas from thinking about this (and maybe questions). The concept(s) are still blurry in my mind:

If this "UpdateEvent" informs about updates (only) then there should be an actual check if there was an update.
That in turn needs a sort of central instance which stores every status that every activity in the running instance has seen. Only this instance/thing/repository would compare and if needed send a changed event.

Otherwise one would need quite a few locations which would need to send every status they see as "change", because they might have been changed somewhere else?

(Is that actually a wide-spread problem: activities remaining active even when another one is in the foreground and then their ui must be refreshed for every change/data received everywhere else?)

And specifically for the thread view: It receives the event to update its ui (on these status)?
Maybe it should then receive all the data to display as event - esp. the events it sends itself?
(It sounds too complex when it should simply "load things and then display them" but maybe that is because of the discussion above).

Sorry for writing so long but maybe it helps a bit.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is not ideal since ViewThreadViewModel is both sending and observing StatusChangedEvent, but I don't have a better idea :/

https://developer.android.com/topic/architecture/data-layer

Tell the data layer to update the data, and the data layer content should be exposed as a Flow, so that consumers of the content observe changes as they happen.

See e.g., https://developer.android.com/codelabs/android-room-with-a-view-kotlin#6, https://developer.android.com/topic/architecture/data-layer/offline-first#reads, https://developer.android.com/topic/architecture/data-layer/offline-first#writes

That'll be a lot easier when #3436 is complete.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I kinda agree that observing the DB is kind of the best thing but also what's the problem with both sending and receiving?

Copy link
Contributor

Choose a reason for hiding this comment

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

It's a layering violation, and a source of bugs. With this approach every view model has to emit events, and if that's missed, or one of them has the wrong logic, then you have a bug.

By putting it in the data layer you guarantee that it doesn't matter what is updating the data, every update will generate the correct event.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree with this, model and persistence should be the source of truth. I was referring to @connyduck 's comment what's the problem with both sending and receiving the event

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes basically what Nik says.
Problem is, we don't have a data layer for non-cached timelines so I'll fix it for cached ones only for now, still better than before.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's coming in #3436. I'm in the "Cleanup up the TODOs and update the tests" phase in that PR.

@connyduck connyduck requested review from charlag and Tak March 22, 2023 20:32
@connyduck connyduck marked this pull request as ready for review March 22, 2023 21:01
timelineDao.setReblogged(accountId, event.statusId, event.reblog)
is BookmarkEvent ->
timelineDao.setBookmarked(accountId, event.statusId, event.bookmark)
is StatusChangedEvent -> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just an observation: "status changed" has much less (specific) information than for example "fav clicked". I would immediately ask "what changed?".

I see that these events are normally not used in their specific sense. But maybe they could?

Is it possible to have an event hierarchy? BookmarkEvent extends StatusChangedEvent? Or is that bad style?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That is the idea that there is less info. We need to update everything anyway. If the post has been bookmarked, the fav count could have changed as well. Or it was favourited on another device.

if (viewData != null) { detailedStatus = viewData }
val result = api.status(id).getOrNull()
if (result != null) {
eventHub.dispatch(StatusChangedEvent(result))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe one could compare the two if there was actually a change?

if (viewData != null) { detailedStatus = viewData }
val result = api.status(id).getOrNull()
if (result != null) {
eventHub.dispatch(StatusChangedEvent(result))
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wondered a bit why this case here stands out.

And maybe it doesn't: What happens if I refresh a timeline and one of the status there did change?
(A status that might as well be shown on the bookmark timeline for example?)

@connyduck
Copy link
Collaborator Author

How about instead of sending events in ViewThreadFragments, we update the database directly? This way at least cached timelines get updated (which is better than before) and we avoid redundant updates in the ViewThreadFragment itself. Still not ideal but my best idea right now.

@charlag
Copy link
Collaborator

charlag commented Apr 15, 2023

I think once the request is successful it's fairly safe to update the db and listen to db everywhere, like @nikclayton said

@@ -144,8 +136,15 @@ class ViewThreadViewModel @Inject constructor(
// for the status. Ignore errors, the user still has a functioning UI if the fetch
// failed.
if (timelineStatus != null) {
val viewData = api.status(id).getOrNull()?.toViewData(isDetailed = true)
if (viewData != null) { detailedStatus = viewData }
val result = api.status(id).getOrNull()
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
val result = api.status(id).getOrNull()
api.status(id).getOrNull?.let { ... }

?

isExpanded = viewData.isExpanded,
isCollapsed = viewData.isCollapsed,
isDetailed = viewData.isDetailed
)
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you need to update handleStatusComposedEvent() as well?

Specifically, if the status that was composed is a reply, the status it was replying to needs to be updated so that the reply count is correct.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes that should probably be done, but I don't see it in the scope of this PR

# Conflicts:
#	app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt
@Tak Tak merged commit 54e92b2 into develop Sep 26, 2023
@Tak Tak deleted the improve_status_updates branch September 26, 2023 07:08
rimar1337 pushed a commit to rimar1337/tooskie that referenced this pull request Sep 26, 2023
Tak added a commit that referenced this pull request Sep 26, 2023
Ugh, I didn't notice that #3480 was affected by the notification
fragment rollback
@charlag charlag added this to the Tusky 24 milestone Nov 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants