Skip to content

Snackbar: Allow to attach custom task on close button click#8589

Merged
henon merged 13 commits into
MudBlazor:devfrom
BieleckiLtd:feature/snackbar-close-button-task
Nov 23, 2024
Merged

Snackbar: Allow to attach custom task on close button click#8589
henon merged 13 commits into
MudBlazor:devfrom
BieleckiLtd:feature/snackbar-close-button-task

Conversation

@BieleckiLtd

Copy link
Copy Markdown
Contributor

Allows the attachment of a custom task when a user dismisses the message by clicking the close button.

Description

Closes #7317

How Has This Been Tested?

unit | visually

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist:

  • The PR is submitted to the correct branch (dev).
  • My code follows the code style of this project.
  • I've added relevant tests.

@github-actions github-actions Bot added enhancement Adds a new feature or enhances existing functionality (not fixing a defect) in the main library PR: needs review labels Apr 6, 2024
@codecov

codecov Bot commented Apr 6, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 71.42857% with 2 lines in your changes missing coverage. Please review.

Project coverage is 91.50%. Comparing base (aa723c2) to head (8be3e18).
Report is 17 commits behind head on dev.

Files with missing lines Patch % Lines
src/MudBlazor/Components/Snackbar/Snackbar.cs 60.00% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #8589      +/-   ##
==========================================
- Coverage   91.51%   91.50%   -0.01%     
==========================================
  Files         414      414              
  Lines       12988    12989       +1     
  Branches     2452     2454       +2     
==========================================
  Hits        11886    11886              
  Misses        555      555              
- Partials      547      548       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.


🚨 Try these New Features:

@danielchalmers danielchalmers left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like the refactors! Interested in seeing how others feel about CodeInline; Makes sense to me.

Comment thread src/MudBlazor/Components/Snackbar/MudSnackbarElement.razor.cs Outdated
Comment thread src/MudBlazor/Components/Snackbar/MudSnackbarElement.razor.cs
Comment thread src/MudBlazor/Components/Snackbar/Snackbar.cs Outdated
Comment thread src/MudBlazor/Components/Snackbar/Snackbar.cs Outdated
/// <summary>
/// The asynchronous delegate that is invoked when the Snackbar is clicked.
/// </summary>
public Func<Snackbar, Task> OnClick { get; set; }

@danielchalmers danielchalmers Apr 7, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Love the rename. Needs to be added to the Migration Guide if accepted

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OnClickAsync?

Comment thread src/MudBlazor.Docs/Pages/Components/Snackbar/SnackbarPage.razor
@danielchalmers danielchalmers added the breaking change This change will require consumer code updates (ex: removes/changes an API) label Apr 7, 2024
// Click action is executed only if it's not from the close icon
await State.Options.OnClick.Invoke(this);
//await State.Options.OnClick.Invoke(this);
State.Options.OnClick.Invoke(this);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hmm, shouldn't Clicked still be async and this call be awaited? I might be totally wrong

ActionClickedAsync and CloseIconClickedAsync could be non-async because they were directly calling another Task

@BieleckiLtd BieleckiLtd Apr 9, 2024

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Maybe someone could have a fresh pair of eyes look at it, but with the code as is, my async actions OnClick and OnCloseButtonClick are being awaited correctly in the use case like this:

private void Show()
{
    Snackbar.Add("Close me using the close button.", Severity.Normal, config =>
    {
        config.RequireInteraction = true;
        config.OnCloseButtonClick = SayGoodbyeAsync;
        config.Action = "Adiós";
        config.OnClick = SayGoodbyeAsync;
    });
}

public async Task SayGoodbyeAsync(Snackbar snackbar)
{
    await Task.Delay(5000); // this is being awaited ok
    Snackbar.Add("Sorry to see you go!");
}

The snackbar will be dismissed immediately upon clicking the close button, with any attached task invoked and awaited in the background. I think that would work alright from a UX perspective. On the other hand, if in the Clicked method I await my custom task to complete before closing the snackbar, this could give the impression that the click did not register or the app has hung.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@henon Thoughts on this behavior? Makes sense to me.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Exceptions in the OnClick method would be swallowed though. So the best thing to do would be this:

_ = InvokeAndLogException(State.Options.OnClick); where InvokeAndLogException is:

[Inject]
private ILoggerFactory LoggerFactory { get; set; } = null!;
private ILogger? _logger;
protected ILogger Logger => _logger ??= LoggerFactory.CreateLogger(GetType());


private async Task InvokeAndLogException(Func<Snackbar, Task> func) {
  try {
    await func(this)
  }
  catch(Exception e) {
    Logger.LogErrpr("An exception happened in Snackbar.OnClick: {e.Message}\n");
  }
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

or maybe @ScarletKuro has a better idea

@Yomodo Yomodo Apr 25, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Logger.LogError has an overload that accepts Exception? that might provide stack as well.
Edit: Hm, perhaps not always wanted.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why it's simply not

await State.Options.OnClick.Invoke(this);

?
It's an Func<Snackbar, Task> and if re are renaming, it probably makes sense to name it OnClickAsync
Otherwise I don't get the Clicked change from void to Task
Well, I mean I understand you did it for the ActionClickedAsync / CloseIconClickedAsync, but it makes sense to use await now when it's a Task.

@danielchalmers danielchalmers removed the request for review from ScarletKuro April 24, 2024 05:36
@henon henon requested a review from ScarletKuro April 24, 2024 18:09
@BieleckiLtd BieleckiLtd marked this pull request as draft April 27, 2024 12:11
@henon

henon commented May 2, 2024

Copy link
Copy Markdown
Contributor

This is almost done, isn't it?

if (fromCloseIcon)
{
// Invoke user-defined task when close button is clicked
State.Options.OnCloseButtonClick?.Invoke(this);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Shouldn't this be

await State.Options.OnCloseButtonClick.Invoke(this); //with null check

?
since OnCloseButtonClick is Func<Snackbar, Task>. It would probably make sense to add Async suffix.

// Click action is executed only if it's not from the close icon
await State.Options.OnClick.Invoke(this);
//await State.Options.OnClick.Invoke(this);
State.Options.OnClick.Invoke(this);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why it's simply not

await State.Options.OnClick.Invoke(this);

?
It's an Func<Snackbar, Task> and if re are renaming, it probably makes sense to name it OnClickAsync
Otherwise I don't get the Clicked change from void to Task
Well, I mean I understand you did it for the ActionClickedAsync / CloseIconClickedAsync, but it makes sense to use await now when it's a Task.

/// <summary>
/// The asynchronous delegate that is invoked when the close button of the Snackbar is clicked.
/// </summary>
public Func<Snackbar, Task> OnCloseButtonClick { get; set; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OnCloseButtonClickAsync?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I disagree, it should be CloseButtonClickFunc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I renamed the property to CloseButtonClickFunc as suggested. Since I'm also correcting Onclick, should it be suffixed with Func too? OnClickFunc, ClickFunc, or just OnClick?

/// <summary>
/// The asynchronous delegate that is invoked when the Snackbar is clicked.
/// </summary>
public Func<Snackbar, Task> OnClick { get; set; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OnClickAsync?

internal void Init() => TransitionTo(SnackbarState.Showing);

internal void Clicked(bool fromCloseIcon)
internal Task Clicked(bool fromCloseIcon)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ClickedAsync.

@ScarletKuro

Copy link
Copy Markdown
Member

This is almost done, isn't it?

I don't think so, the build is failing.

@ScarletKuro

Copy link
Copy Markdown
Member

Is there no more interest? We are working on v8, and it's good timing to push any breaking change.
But this PR needs to solve conflicts, fix build and address all the comments.

@BieleckiLtd

Copy link
Copy Markdown
Contributor Author

I don't want to push something I don't fully understand. I appreciate the suggested renaming and updating the pull request to align with the current dev state, but I have reservations about two aspects: awaiting and exception handling. My understanding is this:

  1. The snackbar's closing behaviour should not depend on the completion of the user's function. By invoking State.Options.OnClickAsync?.Invoke(this) and State.Options.CloseButtonClickFunc?.Invoke(this) (without awaiting), the snackbar's closing process can continue, animation will start doing its thing etc.. Awaiting longer operations however can make the app seem unresponsive, which might lead users to click the close button multiple times and then cause all sorts of problems.

  2. I don't think the snackbar should manage exceptions caused by the caller. Not wrapping the invocation in a try-catch block means that any exceptions thrown by the custom function will correctly propagate up the call stack to be handled by the caller. Why the snackbar should be logging anything if it's not it throwing?

…chronous execution was unnecessary. Added examples for clearer understanding of use cases.
@ScarletKuro

Copy link
Copy Markdown
Member

I don't remember the code or what it was doing, but if the intention here is not to wait for the user function to finish, because, as you mentioned, "Awaiting longer operations can make the app seem unresponsive" then that’s a valid point.

But in that case, you need to indicate this intention, either by using the discard _ = for the task or by leaving a comment in the code. We always do this when something shouldn’t be awaited, as we have similar cases in our codebase.
Otherwise, someone might scroll through the code, see an analyzer or ReSharper suggestion, and add an await, without taking the time to understand what’s really going on, especially with so many people working on the code.

@alexdennis2001

Copy link
Copy Markdown

Will this be added soon to mudblazor I need a OnClose function for the Snackbar

@sonarqubecloud

Copy link
Copy Markdown

@BieleckiLtd BieleckiLtd marked this pull request as ready for review November 20, 2024 17:20
@BieleckiLtd

Copy link
Copy Markdown
Contributor Author

I’m happy with this proposal as it is, but I don’t mind if someone else wants to add anything further.

@henon

henon commented Nov 20, 2024

Copy link
Copy Markdown
Contributor

2. I don't think the snackbar should manage exceptions caused by the caller. Not wrapping the invocation in a try-catch block means that any exceptions thrown by the custom function will correctly propagate up the call stack to be handled by the caller. Why the snackbar should be logging anything if it's not it throwing?

It will mean that an exception in user code will crash the application, which would be fine. We had instances where exceptions would be swallowed completely because we didn't observe tasks and users complained that it made their code hard to debug. Maybe those discards were done on background threads? I don't know. Anyway, if you are sure that here it will result in a crash then it is ok.

@BieleckiLtd

Copy link
Copy Markdown
Contributor Author

It will mean that an exception in user code will crash the application, which would be fine. We had instances where exceptions would be swallowed completely because we didn't observe tasks and users complained that it made their code hard to debug. Maybe those discards were done on background threads? I don't know. Anyway, if you are sure that here it will result in a crash then it is ok.

Yes, the tests to confirm this are in MudBlazor.UnitTests.Viewer

@henon henon merged commit e7cea92 into MudBlazor:dev Nov 23, 2024
@henon

henon commented Nov 23, 2024

Copy link
Copy Markdown
Contributor

Thanks a lot @BieleckiLtd !

@henon

henon commented Nov 23, 2024

Copy link
Copy Markdown
Contributor

@BieleckiLtd What should we write in the v8 migration guide?

@BieleckiLtd

Copy link
Copy Markdown
Contributor Author

@BieleckiLtd What should we write in the v8 migration guide?

**SnackbarOptions**: The delegate invoked on Snackbar click has been renamed from `Onclick` to `OnClick`. #8589

@henon henon mentioned this pull request Nov 23, 2024
@henon

henon commented Nov 23, 2024

Copy link
Copy Markdown
Contributor

Added to v8.0.0 Migration Guide

@BieleckiLtd BieleckiLtd deleted the feature/snackbar-close-button-task branch November 23, 2024 14:26
LLauter pushed a commit to cannellamedia/MudBlazor that referenced this pull request Dec 16, 2024
…r#8589)

Co-authored-by: Pawel Bielecki <Pawel.Bielecki2@leoni.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change This change will require consumer code updates (ex: removes/changes an API) enhancement Adds a new feature or enhances existing functionality (not fixing a defect) in the main library

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow custom actions for close button in MudSnackbar

6 participants