Skip to content

Conversation

MritunjayTiwari14
Copy link

Pull Request: [Circular Loading Indicator Implementation](polls: Add UI feedback on vote button while request pending #1808)

Hello Moderators of Zulip Flutter,
This is Mritunjay Tiwari.
This is my first ever contribution to Zulip.


📌 My Solution

Implementation Video and Images

record.mp4

I have researched for many days about the best practices to implement a Circular Loading Indicator.

At the end, I found that it is very hard and not efficient to implement a progress indicator for an unawaited process on which the pull request depends.

So, I came up with an efficient solution:
by converting the unawaited process to an awaited process, we can easily capture the loading state and then implement a circular loading indicator within the function itself.


Thank you for reviewing my contribution!

💻 Code Snippet

void _toggleVote(PollOption option) async {
    final navigator = Navigator.of(context);

    unawaited(showDialog(context: context,
        barrierDismissible: false,
        builder: (_) {
        return Center(child: CircularProgressIndicator());
      }
    ));

    final store = PerAccountStoreWidget.of(context);
    final op = option.voters.contains(store.selfUserId)
      ? PollVoteOp.remove
      : PollVoteOp.add;
    await(sendSubmessage(store.connection, messageId: widget.messageId,
      submessageType: SubmessageType.widget,
      content: PollVoteEventSubmessage(key: option.key, op: op)));

    navigator.pop();
  }

Copy link
Contributor

@apoorvapendse apoorvapendse left a comment

Choose a reason for hiding this comment

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

Thanks for the PR @MritunjayTiwari14,
Let's fix up the commit message by following the commit style guidelines.

Something like:

poll: Display UI feedback on poll delay.

<description>

Fixes: #1808

Might be a better way to write this.
Feel free to add details and helpful info in the commit description.

Copy link
Contributor

@apoorvapendse apoorvapendse left a comment

Choose a reason for hiding this comment

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

Starting a thread in the #mobile-design channel might be useful to gain initial feedback on this.

We replaced the unawaited call to sendSubmessage() with an awaited call.
This ensures that the operation's start and completion can be tracked, which makes it possible to implement a loading indicator. With the previous unawaited behavior, there was no reliable way to measure the duration of the process.

Fixes: zulip#1808
@MritunjayTiwari14
Copy link
Author

Thanks for the PR @MritunjayTiwari14, Let's fix up the commit message by following the commit style guidelines.

Something like:

poll: Display UI feedback on poll delay.

<description>

Fixes: #1808

Might be a better way to write this. Feel free to add details and helpful info in the commit description.

Thank you very much for Reviewing! @apoorvapendse
The commit description has been successfully Updated as per commit style guidelines.

@MritunjayTiwari14
Copy link
Author

Starting a thread in the #mobile-design channel might be useful to gain initial feedback on this.

Okay, I will look forward into this Channel for further feedback.

@apoorvapendse
Copy link
Contributor

Cool, let's line wrap the description to <= 70 characters per line.
See formatting guidelines.

@alya
Copy link
Collaborator

alya commented Sep 2, 2025

If we show a spinner, I'd expect it to appear on the poll, not blocking the user from reading their other messages.

@MritunjayTiwari14
Copy link
Author

Cool, let's line wrap the description to <= 70 characters per line. See formatting guidelines.

I will make sure to use line wrap in the Description from Now, thank you.

@MritunjayTiwari14
Copy link
Author

If we show a spinner, I'd expect it to appear on the poll, not blocking the user from reading their other messages.

Okay, I’ll work on implementing a spinner similar to the Zulip web poll system.

MritunjayTiwari14 and others added 4 commits September 7, 2025 15:05
Improve PollWidget to give immediate feedback on vote actions,
updating the opacity of the specific poll option while the vote
is in progress.

Fixes: zulip#1808
@MritunjayTiwari14
Copy link
Author

Improve Poll Vote Handling: Optimistic Updates with Store Synchronization

Video Testing of Solution

record.mp4

Background

While working on poll vote handling, we wanted the UI to feel responsive but also stay consistent with server-confirmed data.
Initially, the _toggleVote implementation in the widget relied only on server confirmation, which introduced noticeable lag before the UI updated.

The requirement was:

  • Keep the data layer (PerAccountStore) responsible for state updates.
  • Keep the UI layer focused only on rendering.
  • Provide fast feedback to the user without waiting for server round trips.

Difficulties Faced

  1. Event Timing Uncertainty

    • We initially explored detecting the exact time a vote change arrived from the backend using _modelChanged.
    • This was difficult to reason about, fragile to implement, and created unnecessary complexity.
  2. Lag Without Optimistic Updates

    • If we waited only for server events, the user would experience noticeable delays before their vote appeared.
    • This degraded the user experience and made the interface feel unresponsive.
  3. Navigation & State Sync Issues

    • At one point, we experimented with forcing UI rebuilds via navigation as a workaround to sync state.
    • This was brittle and highlighted the need for a cleaner architecture.
  4. Text Lifting Problem

    • When votes were updated, the vote count text would sometimes “jump” or lift visually due to layout changes.
    • This was solved by changing the alignment of the vote count to center, stabilizing its position during updates.

Resolution

After carefully studying the structure of the Poll model, the Model layer, and the API contract, we realized that a clean and reliable solution could be achieved by adopting optimistic updates with store synchronization:

  • When the user votes:

    1. PerAccountStore immediately updates the local poll model (optimistic update).
    2. A spinner or temporary UI state shows that the vote is “in progress.”
    3. The submessage is sent to the server.
  • When the server confirmation (SubmessageEvent) arrives:

    • The store reconciles the local state with the authoritative server state.
    • Listeners are notified again, ensuring the UI reflects the final, confirmed state.

Outcome

  • Fast feedback: Users see their vote reflected instantly, avoiding lag.
  • Cleaner UI: Alignment changes and loading indicators make the vote count stable and visually consistent during updates.
  • Cleaner architecture: Store handles all state and reconciliation, while the widget focuses only on rendering.
  • Technical debt cleared: Earlier issues around timing, _modelChanged misuse, and navigation hacks were fully resolved.
  • Edge case tested: Added testing to handle abrupt or multiple simultaneous vote updates to ensure the UI and store remain consistent.
  • Informed design: The final solution was arrived at only after a thorough study of the Poll structure, model APIs, and event flow in the codebase.

Removed unused import 'dart:async'.

Fixes: zulip#1808
@MritunjayTiwari14
Copy link
Author

MritunjayTiwari14 commented Sep 12, 2025

Extremely sorry for not removing the import that i used in my previous commit but then refactored my code so that it was not required which caused CI error. I have fixed that in the latest commit.

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.

3 participants