maui icon indicating copy to clipboard operation
maui copied to clipboard

RefreshView IsEnabled Disables All Blazor Hybrid Elements on Android, Works Properly on iOS

Open AuriR opened this issue 5 months ago • 9 comments

Description

In a Blazor Hybrid app, running .NET 9, and the 9.0.61 and 9.0.70 verisons of the Hybrid WebView control, targeting Android and iOS, not Windows. On some pages, I need to have the RefreshView disabled, such as the login screen. While on other screens, I need it enabled, such as those that display data. This is done following the Microsoft documentation to use the IsEnabled field to enable/disable the pull-to-refresh feature:

https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/refreshview?view=net-maui-9.0 (look for Disable a RefreshView)

And here's the code in MainPage.xaml, which contains the Blazor app:

    <RefreshView x:Name ="refreshView" Refreshing="RefreshView_OnRefreshing" IsEnabled="False">
        <BlazorWebView x:Name="blazorWebView" HostPage="wwwroot/index.html">
            <BlazorWebView.RootComponents>
                <RootComponent Selector="#app" ComponentType="{x:Type local:Components.Routes}" />
            </BlazorWebView.RootComponents>
        </BlazorWebView>
    </RefreshView>

And here is the codebehind:

    public partial class MainPage : ContentPage
    {
        private ApplicationStateService _appState = null!;

        public MainPage(ApplicationStateService appState)
        {
            _appState = appState;

            InitializeComponent();

            // If on iOS, make sure we use the safe area to accomodate for the notch.
            On<iOS>().SetUseSafeArea(true);

            // Ensure external URLs open properly.
            blazorWebView.UrlLoading +=
                (sender, urlLoadingEventArgs) =>
                {
                    if (urlLoadingEventArgs.Url.Host.Contains("domain.com", StringComparison.Ordinal))
                    {
                        urlLoadingEventArgs.UrlLoadingStrategy =
                            UrlLoadingStrategy.OpenExternally;
                    }
                };

        }

        private event OnRefreshRequested OnRefreshRequested;

        private void RefreshView_OnRefreshing(object? sender, EventArgs e)
        {
            // Notify the application state service that a refresh has been requested.
            _appState.NotifyRefreshRequested();

            // Notify the UI that the refresh is complete.
            refreshView.IsRefreshing = false;
        }

        public void EnableRefresh()
        {
            refreshView.IsEnabled = true;
        }

        public void DisableRefresh()
        {
            refreshView.IsEnabled = false;
        }

    }

Here's the Home.razor that won't load if I have IsEnabled="false" on Android, but works on iOS:

@page "/"
@inject ILogger<Home> _logger
@inject NotificationService _notificationService
@inject AuthService _authService
@inject ApplicationStateService _appState
@inject NavigationManager _navManager

<div class="login-container justify-content-center align-items-center d-flex flex-column rounded-3">
@if (!IsBusy)
{
    <img src="/images/logo_withwords.svg" alt="Logo" class="mb-5">
    <h2 class="text-black fw-bold fs-1">@StringResources.Phrase_WelcomeBack</h2>
    <p><small>@StringResources.Phrase_EnterUnPwToLogin</small></p>

    <EditForm Model="@_loginRequestModel" OnValidSubmit="OnValidSubmit" OnInvalidSubmit="OnInvalidSubmit">

        <DataAnnotationsValidator/>
        <ValidationSummary/>

        <div class="form-group">
            <InputText 
                type="email" 
                inputtype="email"
                maxlength="180" 
                class="form-control" 
                id="email" 
                @bind-Value="_loginRequestModel.Username" 
                placeholder="@StringResources.Word_Email.ToLower()"/>
        </div>
        <div class="form-group">
            <InputText
                type="password"
                maxlength="32"
                class="form-control"
                id="password"
                @bind-Value="_loginRequestModel.Password"
                placeholder="@StringResources.Word_Password.ToLower()"/>
        </div>

        <div class="form-group">
            <button type="submit" class="btn-primary btn-login w-100">@StringResources.Phrase_LogIn</button>
        </div>

    </EditForm>


    <a href="https://domain.com/help/" class="d-block mt-3">@StringResources.Phrase_NeedHelp</a>
    <br />
    <span class="d-block">@($"{StringResources.Word_AppVersion} {VersionTracking.CurrentVersion} {StringResources.Word_AppBuild} {VersionTracking.CurrentBuild}")</span>
}
else
{
    <WaitIndicator IsBusy="@_appState.IsBusy" />
}
</div>

If I set IsEnabled to false on Android, all inputs and buttons are disabled in the BlazorWebView on Android. It appears to be partially disabling the BlazorWebView. For example, if you change focus, validation will fire. But you can't enter data via keyboard, or press the submit button in an EditForm.

However, on iOS, this works properly, and only pull-to-refresh is disabled.

This is a blocking issue because it's impossible to ship the Android version with all functionality disabled.

Simply setting IsEnabled to true on the Android version re-enables all "embedded" controls. But pull-to-refresh is there, which is a confusing user experience.

Please let me know if I can assist with more details!

Steps to Reproduce

See above. I've included all the code and walk-through.

Link to public reproduction project repository

No response

Version with bug

9.0.70 SR7

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

Android

Affected platform versions

Android API 35

Did you find any workaround?

Not using a RefreshView or always having RefreshView IsEnabled="true"

Relevant log output

None. This does not cause a compilation error or exceptions.

AuriR avatar Jun 10 '25 12:06 AuriR

The provided code has assembly reference errors during compilation. Could you share a reproduction sample, ideally as a GitHub repository that we can clone for further investigation?

TamilarasanSF4853 avatar Jun 10 '25 15:06 TamilarasanSF4853

See the code above and use it on iOS and Android?

I'll try to get a sample project created for you today - but the code above I would imagine makes this very clear on how to repro.

AuriR avatar Jun 10 '25 15:06 AuriR

I've reproduced it for you in this sample project. Enjoy 😄 https://github.com/AuriR/RefreshViewReproApp

AuriR avatar Jun 10 '25 16:06 AuriR

As you will see - iOS with IsEnabled="False" lets you type in the text fields. Android does not.

Expected result: Android should allow typing in the text fields.

AuriR avatar Jun 10 '25 16:06 AuriR

CC @TamilarasanSF4853

AuriR avatar Jun 10 '25 16:06 AuriR

@TamilarasanSF4853 Here is a download link where you can see repro videos. iOS and Android, running from that solution I provided.

https://we.tl/t-wcJpXl5RT3

AuriR avatar Jun 10 '25 16:06 AuriR

This issue has been verified in Visual Studio Code 1.100.3 with .NET MAUI versions 9.0.0 and 9.0.71. It is reproducible on the Android platform.

TamilarasanSF4853 avatar Jun 11 '25 04:06 TamilarasanSF4853

@AuriR thanks for contacting us.

Does this behavior reproduce in a regular webview without Blazor involved?

javiercn avatar Jun 17 '25 13:06 javiercn

@javiercn I have not tried that. Of course, our entire app is MAUI/Blazor Hybrid, so it's not something I tried to troubleshoot. Can you check on your end? I gave you a repro project - maybe you can swap it out and see.

AuriM3 avatar Jun 17 '25 15:06 AuriM3

@javiercn Sorry, sent from my company account. See my answer above :) Thanks!

AuriR avatar Jun 17 '25 15:06 AuriR

@AuriM3 the blazor team usually only looks at issues once we know they are Blazor specific, hence why we ask people to try out without Blazor to make sure that the behavior is not an existing webview behavior (which usually means we can't do anything about it).

javiercn avatar Jun 19 '25 13:06 javiercn

I hear you. Still, since the only way to use the Refresh View with MAUI Blazor Hybrid is by placing the Blazor view inside the MAUI refresh view control. As I've shown, it works properly on iOS, and is broken on Android. It doesn't act according to documentation, which would make me think it's in your Iane to research further. You've repro'd it. I can take a look, but with respect I figured that was more in your lane than mine once I found a bug like this. Are you saying you won't look into this until I repro it another way? I just don't know that I can get to it this week, but if it's blocking you I'll try.

AuriR avatar Jun 19 '25 15:06 AuriR

Maybe you need the MAUI team to look at it instead of the Blazor team, since it's the Blazor WebView control INSIDE OF the MAUI RefreshView control? Not sure how you share tickets between each other... I thought MAUI Blazor Hybrid would be a collab of sorts.

AuriR avatar Jun 19 '25 15:06 AuriR

@javiercn I've modified the repro project to help you sort this out. Take a look at the branch CheckWebViewForSame and you will see the RefreshView also disables the WebView. So it appears to be a bug with the RefreshView and its child controls. You'll see you cannot enter any information into text fields, but button clicks appear to work. I hope that helps!

AuriR avatar Jun 19 '25 15:06 AuriR

@mattleibow Is this something you could take a look at to ensure its Blazor specific? It sounds to me that this is likely an issue with the underlying webview.

javiercn avatar Jun 20 '25 13:06 javiercn

Is there anything going on with the Parent check in the WebView/BlazorWebView on Android if it's a child control of a RefreshView? Trying to look through the codebase and help out... Hard because I'm unfamiliar with all the intricacies. First time I've seen the class AWebView for example :-P

AuriR avatar Jun 21 '25 12:06 AuriR

@mattleibow @javiercn Checking in on this, please. Anything I can assist with? Any ideas for a workaround, please? Much appreciated!

AuriR avatar Jun 25 '25 17:06 AuriR

Team - checking on this please. It's important to me because it's blocking our Android release.

AuriR avatar Jul 07 '25 10:07 AuriR

@mattleibow @javiercn Any updates, please? Did this get moved to a different team? Making sure it's not in limbo with each team waiting on the other :) Thanks in advance!

AuriR avatar Jul 11 '25 23:07 AuriR

@mattleibow @javiercn Maybe this is an issue, though I need to do some testing. See below. IsEnabledCore appears to affect all children if the parent view has IsEnabled set. However, IsEnabled is a bad property name for RefreshView. It's REALLY IsRrefreshEnabled not whether the view itself is enabled. So perhaps the following would fix it? See the added type check:

In VisualElement.cs Line 644.

		/// <summary>
		/// This value represents the cumulative IsEnabled value.
		/// All types that override this property need to also invoke
		/// the RefreshIsEnabledProperty() method if the value will change.
		/// </summary>
		protected virtual bool IsEnabledCore
		{
			get
			{
				if (_isEnabledExplicit == false)
				{
					// If the explicitly set value is false, then nothing else matters
					// And we can save the effort of a Parent check
					return false;
				}

				var parent = Parent as VisualElement;
				if (parent is not null 
				    && !parent.IsEnabled 
				    && parent.GetType() != typeof(RefreshView) /* because RefreshView IsEnabled / IsEnabledCore should only affect Refreshing, not children */)
					return false;

				return _isEnabledExplicit;
			}
		}

AuriR avatar Jul 13 '25 21:07 AuriR

@AuriR apologies for the delay.

I'm no expert in this area, I do work on the core Blazor bits, not on the Maui specific integration. It might take us some time to get to this as we are in the latter stages of the .NET 10.0 release.

If we determine more people is being impacted by this issue, we will raise the priority accordingly.

Ping @mattleibow can you help out here?

javiercn avatar Jul 15 '25 09:07 javiercn

@javiercn Thank you for the follow-up. I'd be surprised if the RefreshView wasn't being used by a lot of apps, given it's the official MAUI view for handling refreshing. Is there a best practice for handling pull-to-refresh in MAUI/Blazor Hybrid apps that doesn't involve it, please? Happy to adjust! And of course thanks for all your hard work - looking forward to 10. Problem is, I can't wait to release my app in November+ so any recommended alternative approach while the bug is being fixed will be greatly appreciated :)

To your point about working on Blazor, yeah, it may not be a Blazor issue from the bits I'm seeing.

AuriR avatar Jul 15 '25 13:07 AuriR

I think this issue is sort of a duplicate of this: https://github.com/dotnet/maui/issues/22699 with some extra problems thrown in.

I have created what I think is a alternate implemntation as a spec and will try see what happens: https://github.com/dotnet/maui/issues/30690

Not quite ready to close this issue as a duplicate, but I think the main issue is that IsEnabled was maybe commandered to switch to disable refreshing instead of the children. All other controls disable the view and all subviews in MAUI. Yet refresh view was different.

Please let me know if the new spec looks to address the issue and I will try get copilot to iterate and see what comes of it. I can't say we are going to fix it for .NET 10, but I will try to see if copilot can get us closer while I work on the other things I need to get to.

Hopefully, this is straight forward and we can get a new feature into .NET 10 soon.

mattleibow avatar Jul 17 '25 20:07 mattleibow

@mattleibow I like it. I'm very curious what Copilot comes up with. And, that's simply very cool to see it attempt addressing it.

Is this a .NET 10 "thing" because it would be a breaking change due to the property changing?

Much appreciated! I'll also check out the workarounds mentioned. Fascinated by the prompting.

AuriR avatar Jul 17 '25 21:07 AuriR

@mattleibow Checking in. Fascinating to see Copilot doing its thing. Are the changes it's proposing acceptable?

AuriR avatar Jul 26 '25 15:07 AuriR

I think so. Are you able to try the artifacts in the PR? https://github.com/dotnet/maui/pull/30692

mattleibow avatar Jul 27 '25 12:07 mattleibow

I think so. Are you able to try the artifacts in the PR? #30692

I'll take a look this week. I imagine I need to update my project to .NET 10 as well? I'll need to find those artifacts - didn't see them at a cursory glance, but I'm usually in ADO, not GitHub :) Adding it to my calendar, and happy to help!

AuriR avatar Jul 27 '25 12:07 AuriR

I think so. Are you able to try the artifacts in the PR? #30692

And let me laugh at myself that the one thing I missed was the most ***king obvious: 😄

Image

AuriR avatar Jul 27 '25 12:07 AuriR

@mattleibow Looks like no nuget packages were built yet, blocked by the merge conflict. Checked maui-public and there's no link to Azure artifacts/pipelines. Also possible I simply don't have access. Please let me know if I'm missing something. Much appreciated!

AuriR avatar Jul 27 '25 12:07 AuriR

@mattleibow Looks like no nuget packages were built yet, blocked by the merge conflict. Checked maui-public and there's no link to Azure artifacts/pipelines. Also possible I simply don't have access. Please let me know if I'm missing something. Much appreciated!

they are built now if you want to test

PureWeen avatar Aug 11 '25 20:08 PureWeen