CommunityToolkit / dotnet

.NET Community Toolkit is a collection of helpers and APIs that work for all .NET developers and are agnostic of any specific UI platform. The toolkit is maintained and published by Microsoft, and part of the .NET Foundation.
https://docs.microsoft.com/dotnet/communitytoolkit/?WT.mc_id=dotnet-0000-bramin
Other
2.99k stars 294 forks source link

ObservableProperty with NotifyCanExecuteChangedFor exception occurs #858

Open TSKHot opened 6 months ago

TSKHot commented 6 months ago

Describe the bug

With a very simple piece of code an exception is raised within the toolkit generated CanExecuteChanged method.

Regression

No response

Steps to reproduce

Very simple page (supposed to be an account registration page, but cut right back to the bare bones for illustrating the issue)

 xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             x:Class="WalletApp.Views.RegisterPage"
             x:DataType="viewModel:RegisterViewModel"
             xmlns:viewModel="clr-namespace:WalletApp.ViewModels"
             xmlns:helpers="clr-namespace:WalletApp.Helpers"
             Title="{Binding Title}">

    <VerticalStackLayout Style="{StaticResource BaseLayout}">

        <ActivityIndicator IsRunning="{Binding IsBusy}"/>

        <StackLayout Orientation="Horizontal">
            <Image Source="{AppThemeBinding Light=user_icon.png, Dark=user_icon_dark.png}" Style="{StaticResource EntryIcon}" />
            <Entry x:Name="UserNameEntry" Text="{Binding Username}" Placeholder="User name" IsEnabled="{Binding IsBusy, Converter={helpers:InverseBoolConverter}}" Style="{StaticResource Default}">
                <Entry.Behaviors>
                    <toolkit:UserStoppedTypingBehavior 
                        Command="{Binding UsernameCheckCommand}"
                        StoppedTypingTimeThreshold="500"
                        MinimumLengthThreshold="5"
                        ShouldDismissKeyboardAutomatically="True"/>
                </Entry.Behaviors>
            </Entry>
        </StackLayout>

        <Button x:Name="RegisterButton" Command="{Binding RegisterCommand}" Text="Register" />

    </VerticalStackLayout>
</ContentPage>
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace WalletApp.ViewModels
{
    public partial class RegisterViewModel : BaseViewModel
    {

        [ObservableProperty]
        [NotifyCanExecuteChangedFor(nameof(RegisterCommand))]
        private string _username;

        [NotifyCanExecuteChangedFor(nameof(RegisterCommand))]
        [ObservableProperty]
        private bool _accountExists;

        public RegisterViewModel()
        {

            Title = "Register";

            Username = string.Empty;
            AccountExists = false;
        }

        [RelayCommand]
        private async Task UsernameCheck()
        {
            try
            {
                AccountExists = true;
            }
            catch (Exception ex)
            {
                Console.Write(ex);
            }
        }

        private bool CheckCanRegister()
        {
            return !AccountExists;
        }

        [RelayCommand(CanExecute = nameof(CheckCanRegister))]
        public async Task Register()
        {
            var registered = true;
        }
    }
}
namespace WalletApp.ViewModels
{
    public partial class BaseViewModel : ObservableValidator
    {
        [ObservableProperty]
        private bool _isBusy;

        [ObservableProperty]
        private string _title;
    }
}

Setting the value of AccountExists to true in the UsernameCheck function consistently throws an exception. Note, setting the value to false does not cause the exception.

[DOTNET] Android.Util.AndroidRuntimeException: Only the original thread that created a view hierarchy can touch its views. [DOTNET] at Java.Interop.JniEnvironment.InstanceMethods.CallNonvirtualVoidMethod(JniObjectReference instance, JniObjectReference type, JniMethodInfo method, JniArgumentValue args) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/obj/Release/net7.0/JniEnvironment.g.cs:line 20830 [DOTNET] at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeVirtualVoidMethod(String encodedMember, IJavaPeerable self, JniArgumentValue parameters) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs:line 75 [DOTNET] at Android.Views.View.set_Background(Drawable value) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net8.0/android-34/mcw/Android.Views.View.cs:line 5617 [DOTNET] at Microsoft.Maui.Platform.ButtonExtensions.UpdateBorderDrawable(MaterialButton platformView, IButton button) in D:\a_work\1\s\src\Core\src\Platform\Android\ButtonExtensions.cs:line 80 [DOTNET] at Microsoft.Maui.Platform.ButtonExtensions.UpdateBackground(MaterialButton platformView, IButton button) in D:\a_work\1\s\src\Core\src\Platform\Android\ButtonExtensions.cs:line 11 [DOTNET] at Microsoft.Maui.Handlers.ButtonHandler.MapBackground(IButtonHandler handler, IButton button) in D:\a_work\1\s\src\Core\src\Handlers\Button\ButtonHandler.Android.cs:line 71 [DOTNET] at Microsoft.Maui.PropertyMapper2.<>c__DisplayClass5_0[[Microsoft.Maui.IButton, Microsoft.Maui, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[Microsoft.Maui.Handlers.IButtonHandler, Microsoft.Maui, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].<Add>b__0(IElementHandler h, IElement v) in D:\a\_work\1\s\src\Core\src\PropertyMapper.cs:line 172 [DOTNET] at Microsoft.Maui.PropertyMapper.UpdatePropertyCore(String key, IElementHandler viewHandler, IElement virtualView) in D:\a\_work\1\s\src\Core\src\PropertyMapper.cs:line 47 [DOTNET] at Microsoft.Maui.PropertyMapper.UpdateProperty(IElementHandler viewHandler, IElement virtualView, String property) in D:\a\_work\1\s\src\Core\src\PropertyMapper.cs:line 72 [DOTNET] at Microsoft.Maui.Handlers.ElementHandler.UpdateValue(String property) in D:\a\_work\1\s\src\Core\src\Handlers\Element\ElementHandler.cs:line 87 [DOTNET] at Microsoft.Maui.Controls.VisualElement.MapBackgroundColor(IViewHandler handler, IView view) in D:\a\_work\1\s\src\Controls\src\Core\VisualElement\VisualElement.Mapper.cs:line 42 [DOTNET] at Microsoft.Maui.PropertyMapperExtensions.<>c__DisplayClass2_02[[Microsoft.Maui.IView, Microsoft.Maui, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[Microsoft.Maui.IViewHandler, Microsoft.Maui, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].b0(IViewHandler h, IView v, Action`2 p) in D:\a_work\1\s\src\Core\src\PropertyMapperExtensions.cs:line 66 [DOTNET] at Microsoft.Maui.PropertyMapperExtensions.<>cDisplayClass1_02[[Microsoft.Maui.IView, Microsoft.Maui, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[Microsoft.Maui.IViewHandler, Microsoft.Maui, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].<ModifyMapping>g__newMethod|0(IElementHandler handler, IElement view) in D:\a\_work\1\s\src\Core\src\PropertyMapperExtensions.cs:line 46 [DOTNET] at Microsoft.Maui.PropertyMapper2.<>cDisplayClass5_0[[Microsoft.Maui.IView, Microsoft.Maui, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[Microsoft.Maui.IViewHandler, Microsoft.Maui, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].b__0(IElementHandler h, IElement v) in D:\a_work\1\s\src\Core\src\PropertyMapper.cs:line 172 [DOTNET] at Microsoft.Maui.PropertyMapper.UpdatePropertyCore(String key, IElementHandler viewHandler, IElement virtualView) in D:\a_work\1\s\src\Core\src\PropertyMapper.cs:line 47 [DOTNET] at Microsoft.Maui.PropertyMapper.UpdateProperty(IElementHandler viewHandler, IElement virtualView, String property) in D:\a_work\1\s\src\Core\src\PropertyMapper.cs:line 72 [DOTNET] at Microsoft.Maui.Handlers.ElementHandler.UpdateValue(String property) in D:\a_work\1\s\src\Core\src\Handlers\Element\ElementHandler.cs:line 87 [DOTNET] at Microsoft.Maui.Controls.Element.OnPropertyChanged(String propertyName) in D:\a_work\1\s\src\Controls\src\Core\Element\Element.cs:line 573 [DOTNET] at Microsoft.Maui.Controls.Button.OnPropertyChanged(String propertyName) in D:\a_work\1\s\src\Controls\src\Core\Button\Button.cs:line 472 [DOTNET] at Microsoft.Maui.Controls.BindableObject.SetValueActual(BindableProperty property, BindablePropertyContext context, Object value, Boolean currentlyApplying, SetValueFlags attributes, SetterSpecificity specificity, Boolean silent) in D:\a_work\1\s\src\Controls\src\Core\BindableObject.cs:line 641 [DOTNET] at Microsoft.Maui.Controls.BindableObject.SetValueCore(BindableProperty property, Object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes, SetterSpecificity specificity) in D:\a_work\1\s\src\Controls\src\Core\BindableObject.cs:line 569 [DOTNET] at Microsoft.Maui.Controls.AppThemeBinding.<>cDisplayClass9_0.g_Set|0() in D:\a_work\1\s\src\Controls\src\Core\AppThemeBinding.cs:line 86 [DOTNET] at Microsoft.Maui.Controls.AppThemeBinding.ApplyCore(Boolean dispatch) in D:\a_work\1\s\src\Controls\src\Core\AppThemeBinding.cs:line 72 [DOTNET] at Microsoft.Maui.Controls.AppThemeBinding.Apply(Object context, BindableObject bindObj, BindableProperty targetProperty, Boolean fromBindingContextChanged, SetterSpecificity specificity) in D:\a_work\1\s\src\Controls\src\Core\AppThemeBinding.cs:line 46 [DOTNET] at Microsoft.Maui.Controls.BindableObject.SetBinding(BindableProperty targetProperty, BindingBase binding, SetterSpecificity specificity) in D:\a_work\1\s\src\Controls\src\Core\BindableObject.cs:line 332 [DOTNET] at Microsoft.Maui.Controls.Setter.Apply(BindableObject target, SetterSpecificity specificity) in D:\a_work\1\s\src\Controls\src\Core\Setter.cs:line 75 [DOTNET] at Microsoft.Maui.Controls.VisualStateManager.GoToState(VisualElement visualElement, String name) in D:\a_work\1\s\src\Controls\src\Core\VisualStateManager.cs:line 111 [DOTNET] at Microsoft.Maui.Controls.VisualElement.ChangeVisualState() in D:\a_work\1\s\src\Controls\src\Core\VisualElement\VisualElement.cs:line 1482 [DOTNET] at Microsoft.Maui.Controls.View.ChangeVisualState() in D:\a_work\1\s\src\Controls\src\Core\View\View.cs:line 196 [DOTNET] at Microsoft.Maui.Controls.Button.ChangeVisualState() in D:\a_work\1\s\src\Controls\src\Core\Button\Button.cs:line 370 [DOTNET] at Microsoft.Maui.Controls.VisualElement.OnIsEnabledPropertyChanged(BindableObject bindable, Object oldValue, Object newValue) in D:\a_work\1\s\src\Controls\src\Core\VisualElement\VisualElement.cs:line 1554 [DOTNET] at Microsoft.Maui.Controls.BindableObject.SetValueActual(BindableProperty property, BindablePropertyContext context, Object value, Boolean currentlyApplying, SetValueFlags attributes, SetterSpecificity specificity, Boolean silent) in D:\a_work\1\s\src\Controls\src\Core\BindableObject.cs:line 643 [DOTNET] at Microsoft.Maui.Controls.BindableObject.SetValueCore(BindableProperty property, Object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes, SetterSpecificity specificity) in D:\a_work\1\s\src\Controls\src\Core\BindableObject.cs:line 569 [DOTNET] at Microsoft.Maui.Controls.BindableObject.SetValue(BindableProperty property, Object value, SetterSpecificity specificity) in D:\a_work\1\s\src\Controls\src\Core\BindableObject.cs:line 503 [DOTNET] at Microsoft.Maui.Controls.BindableObjectExtensions.RefreshPropertyValue(BindableObject self, BindableProperty property, Object value) in D:\a_work\1\s\src\Controls\src\Core\BindableObjectExtensions.cs:line 26 [DOTNET] at Microsoft.Maui.Controls.VisualElement.RefreshIsEnabledProperty() in D:\a_work\1\s\src\Controls\src\Core\VisualElement\VisualElement.cs:line 1645 [DOTNET] at Microsoft.Maui.Controls.Button.Microsoft.Maui.Controls.Internals.ICommandElement.CanExecuteChanged(Object sender, EventArgs e) in D:\a_work\1\s\src\Controls\src\Core\Button\Button.cs:line 463 [DOTNET] at CommunityToolkit.Mvvm.Input.AsyncRelayCommand.NotifyCanExecuteChanged() in //src/CommunityToolkit.Mvvm/Input/AsyncRelayCommand.cs:line 267 [DOTNET] at ViewModels.RegisterViewModel.set_AccountExists(Boolean value) in C:\BySapio\Starling\DotNet\RewardsApp\CommunityToolkit.Mvvm.SourceGenerators\CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator\ViewModels.RegisterViewModel.g.cs:line 49 [DOTNET] at ViewModels.RegisterViewModel.UsernameCheck() in C:\BySapio\Starling\DotNet\RewardsApp\ViewModels\RegisterViewModel.cs:line 34 [DOTNET] --- End of managed Android.Util.AndroidRuntimeException stack trace ---



### Expected behavior

No exception should be thrown.

### Screenshots

_No response_

### IDE and version

VS 2022

### IDE version

17.9.5

### Nuget packages

- [ ] CommunityToolkit.Common
- [ ] CommunityToolkit.Diagnostics
- [ ] CommunityToolkit.HighPerformance
- [X] CommunityToolkit.Mvvm (aka MVVM Toolkit)

### Nuget package version(s)

8.2.2

### Additional context

Also using CommunityToolkit.Maui 7.0.1

### Help us help you

No, just wanted to report this