Call method when ObservableProperty changes using CommunityToolkit.Mvvm - c#

I'm implementing auto complete feature in my .NET MAUI app and I'm using CommunityToolkit.Mvvm code generators in my view model to handle observable properties.
I have the following code and I'm trying call GetSuggestions() method when the SearchText changes.
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(GetSuggestions))]
string searchText;
[ObservableProperty]
bool showSuggestions;
ObservableCollection<string> Suggestions { get; } = new();
private async Task GetSuggestions()
{
if(string.IsNullOrEmpty(SearchText) || SearchText.Length < 3)
return;
var data = await _myApiService.GetSuggestions(SearchText.Trim());
if(data != null && data.Count > 0)
{
Suggestions.Clear();
foreach(var item in data)
Suggestions.Add(item);
ShowSuggestions = true;
}
}
This is giving me the following error:
The target(s) of [NotifyCanExecuteChangedFor] must be an accessible
IRelayCommand property, but "GetSuggestions" has no matches in type
MyViewModel.
What am I doing wrong here?

I guess there are two problems here.
Why is this error occurring?
That happens because GetSuggestions is not a Command.
Try adding the [RelayCommand] attribute to your method.
[RelayCommand]
private async Task GetSuggestions()
{
if(string.IsNullOrEmpty(SearchText) || SearchText.Length < 3)
return;
var data = await _myApiService.GetSuggestions(SearchText.Trim());
if(data != null && data.Count > 0)
{
Suggestions.Clear();
foreach(var item in data)
Suggestions.Add(item);
ShowSuggestions = true;
}
}
Then link NotifyCanExecuteChangedFor to the autogenerated command.
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(GetSuggestionsCommand))]
string searchText;
The second one.
You need to
call GetSuggestions() method when the SearchText changes.
The NotifyCanExecuteChangedFor attribute doesn't do that.
In the autogenerated source code you should find an empty partial method called OnSearchTextPropertyChanged. Try implementing it.
partial void OnSearchTextPropertyChanged(string value)
{
GetSuggestions();
}
If this is what you're searching for, GetSuggestions doesn't need to be marked with the RelayCommand attribute.

Only meant as more of an amendment of #RMinato's answer.
As my comment say: "While most of this was helpful, I need to do a few things different including using the [RelayCommand] and calling the method inside my OnPropChanged method to be Task.Run(() => this.MyMethodAsync()).Wait();".
My code looks like:
[QueryProperty(nameof(Course), nameof(Course))]
public partial class CourseDetailViewModel : BaseViewModel
{
private readonly CourseService courseService;
public CourseDetailViewModel(CourseService courseService)
{
this.courseService = courseService;
}
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(GetCourseDetailCommand))]
Course course;
partial void OnCourseChanged(Course value)
{
Task.Run(() => this.GetCourseDetailAsync()).Wait();
}
[RelayCommand]
public async Task GetCourseDetailAsync()
{
if (GetCourseDetailCommand.IsRunning) return;
try
{
IsBusy = true;
course = await courseService.GetCourseDetailAsync(course.Id);
}
catch (Exception ex)
{
Debug.WriteLine($"Failed to get course detail. Error: {ex.Message}");
await Shell.Current.DisplayAlert("Error!",
$"Failed to get course detail: {ex.Message}", "OK");
throw;
}
finally
{
IsBusy = false;
}
}
}

Related

Calling async method from MVVM property

I'm implementing my own version of auto-complete in a view model in my Xamarin Forms 5 app.
I need to call an async look up function from the Keyword MVVM property. Not sure if I'm handling it right. See below:
string keyword { get; set; }
ObservableRangeCollection<string> suggestions = new ObservableRangeCollection<string>();
public string Keyword
{
get => keyword;
set
{
if(keyword == value)
return;
keyword = value;
OnPropertyChanged();
// If keyword has at least 3 characters, get suggestions
if(keyword.length > 2)
GetSuggestions(keyword).Wait();
}
}
ObservableRangeCollection<string> Suggestions
{
get => suggestions;
set
{
if(sugggestions == value)
return;
suggestions = value;
OnPropertyChanged();
}
}
async Task GetSuggestions(string searchKeyword)
{
var result = await _myApiService.GetSuggestions(searchKeyword);
if(result != null)
{
Suggestions = new ObservableRangeCollection(result);
OnPropertyChanged(Suggestions);
}
}
I'd appreciate any corrections or suggestions. Thanks.
You definitely don't want to block on async code here (as explained on my blog). More generally, you don't want to block the UI, especially when your users are interacting with it (i.e., typing).
Instead, use a pattern like async data binding to (synchronously) start the operation, and then update your UI when the results come in.
E.g., using NotifyTask<T> from here:
string keyword { get; set; }
NotifyTask<List<string>> suggestions;
public string Keyword
{
get => keyword;
set
{
if (keyword == value)
return;
keyword = value;
OnPropertyChanged();
// If keyword has at least 3 characters, get suggestions
if (keyword.length > 2)
suggestions = NotifyTask.Create(GetSuggestionsAsync(keyword));
}
}
NotifyTask<List<string>> Suggestions
{
get => suggestions;
}
async Task<List<string>> GetSuggestionsAsync(string searchKeyword)
{
return await _myApiService.GetSuggestions(searchKeyword);
}
Then, instead of data-binding to Suggestions as a collection of string, data-bind to Suggestions.Result instead. You can also data-bind to Suggestions.IsCompleted (or Suggestions.IsNotCompleted) to show a loading/busy indicator, and Suggestions.IsFaulted to show an error message.

How to update UI after async Task in viewmodel

In my Xamarin.Forms pcl project, I have a xaml page with label. I want to update label after some async task. In my ViewModel constructor I set default text for my label. And create a async Task function named SomeTask().
Question 1: Where can I call SomeTask() function. Not able to call async Task function in constructor.
Question 2: How to update Label text after async Task SomeTask() function.
My code:
public class MyPageViewModel : ViewModelBase
{
private String _selectedText;
public String SelectedText
{
get { return _selectedText; }
set {
if (_selectedText != value)
{
_selectedText = value;
}
}
}
public MyPageViewModel ()
{
_selectedText = "Welcome"; //Default text
}
private async Task<string> SomeTask()
{
return await Task.Run(async () =>
{
await Task.Delay(3000); //Dummy task. It will return the status of Task.
return "Thanks"; //Update Text
});
}
}
I recommend that you use my NotifyTask type; it's described in my MSDN article on asynchronous MVVM data binding, and I think it's the easiest approach:
public class MyPageViewModel : ViewModelBase
{
private NotifyTask<string> _selectedText;
public NotifyTask<string> SelectedText => _selectedText;
public MyPageViewModel()
{
_selectedText = NotifyTask.Create(SomeTask(), "Welcome");
}
private async Task<string> SomeTask()
{
await Task.Delay(3000);
return "Thanks";
}
}
Your data binding would then be changed to bind to SelectedText.Result to display "Welcome" followed by "Thanks". There are other NotifyTask<T> properties for data binding, such as IsNotCompleted, IsCompleted, and ErrorMessage, which allows you to handle fault conditions via data binding as well.
If you don't want to use this type, you can do something similar on your own:
public class MyPageViewModel : ViewModelBase
{
private string _selectedText;
public string SelectedText
{
get { return _selectedText; }
set
{
if (_selectedText != value)
{
_selectedText = value;
RaisePropertyNotifyChanged(); // However you're doing this.
}
}
}
public MyPageViewModel()
{
_selectedText = "Welcome";
var _ = RunSomeTask();
}
private async Task RunSomeTask()
{
try
{
SelectedText = await SomeTask();
}
catch (Exception ex)
{
// TODO: Handle the exception.
// It *must* be handled here, or else it will be silently ignored!
}
}
private async Task<string> SomeTask()
{
await Task.Delay(3000);
return "Thanks";
}
}
The constructor starts a RunSomeTask operation and then explicitly ignores its results (note that this means all exceptions will be ignored). The RunSomeTask is responsible for running SomeTask and handling its results (and exceptions). The result is just used to update SelectedText, and exceptions will be handled however you deem appropriate for your app.
How about
public MyPageViewModel()
{
_selectedText = "Welcome"; //Default text
SomeTask().ContinueWith(previousTask => SelectedText = previousTask.Result);
}
You can create an async Factory Method and make you constructor private. Then you call that method to create an instance of MyPageViewModel. Inside that method, you can call string str = await SomeTask
public class MyPageViewModel : ViewModelBase
{
public async MyPageViewModel CreateAsync()
{
var model = new MyPageViewModel();
SelectedText = await SomeTask();
return model;
}
private MyPageViewModel ()
{
_selectedText = "Welcome"; //Default text
}
private Task<string> SomeTask()
{
return Task.Run(async () =>
{
await Task.Delay(3000); //Dummy task. It will return the status of Task.
return "Thanks"; //Update Text
});
}
}
So instead of creating your model like this:
var model = new MyPageViewModel();
You create it like this:
var model = await MyPageViewModel.CreateAsync();
Problem 1:
Use delegate and event.
Create Delegate & associated event:
private delegate void MyDelegate();
private event MyDelegate myEvent;
Subscribe to the event in constructor:
myEvent += async () => await SomeTask();
Execute the event, where ever you need:
myEvent(); //Note: Check the event for null, before executing
Problem 2:
If on a non-UI thread, then:
Use some framework class for executing UI operation: for example - Xamarin provides Device.BeginInvokeOnMainThread
We may always use DataBinding with the Label and only update the value of Binding Path from ViewModel, using event subscription.
string _message;
public string Message
{
get => _message;
set
{
_message = value;
}
}
myEvent += () => Message = "New Value";
<Label Text = "{Binding Message}"/>

ISupportIncrementalLoading retrieving exception

I have implemented a ISupportIncrementalLoading interface to perform the incremental loading of a ListView.
The interface has the following code:
public interface IIncrementalSource<T>
{
Task<IEnumerable<T>> GetPagedItems(int pageIndex, int pageSize);
}
public class IncrementalLoadingCollection<T, I> : ObservableCollection<I>,
ISupportIncrementalLoading where T : IIncrementalSource<I>, new()
{
private T source;
private int itemsPerPage;
private bool hasMoreItems;
private int currentPage;
public IncrementalLoadingCollection(int itemsPerPage = 10)
{
this.source = new T();
this.itemsPerPage = itemsPerPage;
this.hasMoreItems = true;
}
public void UpdateItemsPerPage(int newItemsPerPage)
{
this.itemsPerPage = newItemsPerPage;
}
public bool HasMoreItems
{
get { return hasMoreItems; }
}
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
return Task.Run<LoadMoreItemsResult>(
async () =>
{
uint resultCount = 0;
var dispatcher = Window.Current.Dispatcher;
var result = await source.GetPagedItems(currentPage++, itemsPerPage);
if(result == null || result.Count() == 0)
{
hasMoreItems = false;
} else
{
resultCount = (uint)result.Count();
await Task.WhenAll(Task.Delay(10), dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
foreach (I item in result)
this.Add(item);
}).AsTask());
}
return new LoadMoreItemsResult() { Count = resultCount };
}).AsAsyncOperation<LoadMoreItemsResult>();
}
}
The instance of the interface is this one:
var collection = new IncrementalLoadingCollection<LiveTextCode, LiveText>();
this.LTextLW.ItemsSource = collection;
Where LiveText is a UserForm and LiveTextCode is a class that, among other functionalities, sets the previous UserForm up.
The UserForm is filled by reading XML files located in a server so the code must perform async operations and, for that, the containing scope has to be also. Due to an unknown reason, the instance of the custom interface is called before it's filling so, I'm getting a NullReferenceException (or at least that the hypothesis that makes most sense to me...).
I'm pretty lost and I don't know how to fix it, if anyone could help it would be much appreciated.
Thanks in advance!
Instead of using this.LTextLW.ItemsSource = collection;
Specify an ObservableCollection item say collection. Now bind this to your listview by binding it to your ItemsSource="{Binding collection}".
Since its an ObservableCollection type as soon as your collection value gets updated it will be reflected in your View also.
Else you can also specify a collection with a RaisePropertyChanged Event
private IncrementalLoadingCollection<LiveTextCode, LiveText> _collection;
public IncrementalLoadingCollection<LiveTextCode, LiveText> collection
{
get { return _collection; }
set
{
_collection = value;
RaisePropertyChanged();
}
}
This will handle updation of UI whenever the value changes.

Why my List is null after being assigned in a function call?

This might be a silly question. But when I modified one example from Live SDK example, got a weird problem.
I was thinking the root cause is async function GetAll() was used synchronously.
Below is the code snippet, I put the problem as comments. Thanks in advance!
class SkyeDriveViewModel: INotifyPropertyChanged
{
private List<SkyDriveItem> folderList = null;
public List<SkyDriveItem> FolderList
{
get { return folderList; }
private set
{
if (value != folderList)
{
folderList = value;
NotifyPropertyChanged("FolderList");
}
}
}
private async void GetAll(string desiredPath)
{
FolderList = new List<SkyDriveItem>();
this.liveClient = new LiveConnectClient(SkyDrivePage.Session);
try
{
LiveOperationResult operationResult = await this.liveClient.GetAsync(desiredPath);
dynamic result = operationResult.Result;
dynamic items = result.data;
foreach (dynamic item in items)
{
SkyDriveItem newItem = new SkyDriveItem(item);
if (newItem.IsFolder)
{
FolderList.Add(newItem);
}
}
}
catch (LiveConnectException e)
{
}
//**till here, FolderList was assigned**
}
public void InitList()
{
Debugger.Log();
GetAll(SKYDRIVEINITPATH);
Debugger.LogWhen(eDebugger.LogTiming.Exit);
//**till here, FolderList had zero item**
}
}
In general having an async void function is a warning sign. You should only have such a method for an event handler. The appropriate return type for GetAll is Task, or possibly even Task<List<SkyDriveItem>>.
The issue is that calling GetAll will only execute code until it hits the first await call, at which point it returns to the caller and the remainder of the method will be executed asynchronously.
The problem here is that because the method is void you have no way of knowing when it's done. You have a "fire and forget" method and can never know when your list is actually ready. If GetAll returns a task then you can await that task and be sure that your list has actually been populated.

How to call an async method from a getter or setter?

What'd be the most elegant way to call an async method from a getter or setter in C#?
Here's some pseudo-code to help explain myself.
async Task<IEnumerable> MyAsyncMethod()
{
return await DoSomethingAsync();
}
public IEnumerable MyList
{
get
{
//call MyAsyncMethod() here
}
}
There is no technical reason that async properties are not allowed in C#. It was a purposeful design decision, because "asynchronous properties" is an oxymoron.
Properties should return current values; they should not be kicking off background operations.
Usually, when someone wants an "asynchronous property", what they really want is one of these:
An asynchronous method that returns a value. In this case, change the property to an async method.
A value that can be used in data-binding but must be calculated/retrieved asynchronously. In this case, either use an async factory method for the containing object or use an async InitAsync() method. The data-bound value will be default(T) until the value is calculated/retrieved.
A value that is expensive to create, but should be cached for future use. In this case, use AsyncLazy from my blog or AsyncEx library. This will give you an awaitable property.
Update: I cover asynchronous properties in one of my recent "async OOP" blog posts.
You can't call it asynchronously, since there is no asynchronous property support, only async methods. As such, there are two options, both taking advantage of the fact that asynchronous methods in the CTP are really just a method that returns Task<T> or Task:
// Make the property return a Task<T>
public Task<IEnumerable> MyList
{
get
{
// Just call the method
return MyAsyncMethod();
}
}
Or:
// Make the property blocking
public IEnumerable MyList
{
get
{
// Block via .Result
return MyAsyncMethod().Result;
}
}
I really needed the call to originate from the get method, due to my decoupled architecture. So I came up with the following implementation.
Usage: Title is in a ViewModel or an object you could statically declare as a page resource. Bind to it and the value will get populated without blocking the UI, when getTitle() returns.
string _Title;
public string Title
{
get
{
if (_Title == null)
{
Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
}
return _Title;
}
set
{
if (value != _Title)
{
_Title = value;
RaisePropertyChanged("Title");
}
}
}
You can use Task like this :
public int SelectedTab
{
get => selected_tab;
set
{
selected_tab = value;
new Task(async () =>
{
await newTab.ScaleTo(0.8);
}).Start();
}
}
I think that we can await for the value just returning first null and then get the real value, so in the case of Pure MVVM (PCL project for instance) I think the following is the most elegant solution:
private IEnumerable myList;
public IEnumerable MyList
{
get
{
if(myList == null)
InitializeMyList();
return myList;
}
set
{
myList = value;
NotifyPropertyChanged();
}
}
private async void InitializeMyList()
{
MyList = await AzureService.GetMyList();
}
I thought .GetAwaiter().GetResult() was exactly the solution to this problem, no?
eg:
string _Title;
public string Title
{
get
{
if (_Title == null)
{
_Title = getTitle().GetAwaiter().GetResult();
}
return _Title;
}
set
{
if (value != _Title)
{
_Title = value;
RaisePropertyChanged("Title");
}
}
}
Since your "async property" is in a viewmodel, you could use AsyncMVVM:
class MyViewModel : AsyncBindableBase
{
public string Title
{
get
{
return Property.Get(GetTitleAsync);
}
}
private async Task<string> GetTitleAsync()
{
//...
}
}
It will take care of the synchronization context and property change notification for you.
You can create an event and invoke an event when the property is changed.
Something like this:
private event EventHandler<string> AddressChanged;
public YourClassConstructor(){
AddressChanged += GoogleAddressesViewModel_AddressChanged;
}
private async void GoogleAddressesViewModel_AddressChanged(object sender, string e){
... make your async call
}
private string _addressToSearch;
public string AddressToSearch
{
get { return _addressToSearch; }
set
{
_addressToSearch = value;
AddressChanged.Invoke(this, AddressToSearch);
}
}
When I ran into this problem, trying to run an async method synchronicity from either a setter or a constructor got me into a deadlock on the UI thread, and using an event handler required too many changes in the general design.
The solution was, as often is, to just write explicitly what I wanted to happen implicitly, which was to have another thread handle the operation and to get the main thread to wait for it to finish:
string someValue=null;
var t = new Thread(() =>someValue = SomeAsyncMethod().Result);
t.Start();
t.Join();
You could argue that I abuse the framework, but it works.
Necromancing.
In .NET Core/NetStandard2, you can use Nito.AsyncEx.AsyncContext.Run instead of System.Windows.Threading.Dispatcher.InvokeAsync:
class AsyncPropertyTest
{
private static async System.Threading.Tasks.Task<int> GetInt(string text)
{
await System.Threading.Tasks.Task.Delay(2000);
System.Threading.Thread.Sleep(2000);
return int.Parse(text);
}
public static int MyProperty
{
get
{
int x = 0;
// https://stackoverflow.com/questions/6602244/how-to-call-an-async-method-from-a-getter-or-setter
// https://stackoverflow.com/questions/41748335/net-dispatcher-for-net-core
// https://github.com/StephenCleary/AsyncEx
Nito.AsyncEx.AsyncContext.Run(async delegate ()
{
x = await GetInt("123");
});
return x;
}
}
public static void Test()
{
System.Console.WriteLine(System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff"));
System.Console.WriteLine(MyProperty);
System.Console.WriteLine(System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff"));
}
}
If you simply chose System.Threading.Tasks.Task.Run or System.Threading.Tasks.Task<int>.Run, then it wouldn't work.
I think my example below may follow #Stephen-Cleary 's approach but I wanted to give a coded example. This is for use in a data binding context for example Xamarin.
The constructor of the class - or indeed the setter of another property on which it is dependent - may call an async void that will populate the property on completion of the task without the need for an await or block. When it finally gets a value it will update your UI via the NotifyPropertyChanged mechanism.
I'm not certain about any side effects of calling a aysnc void from a constructor. Perhaps a commenter will elaborate on error handling etc.
class MainPageViewModel : INotifyPropertyChanged
{
IEnumerable myList;
public event PropertyChangedEventHandler PropertyChanged;
public MainPageViewModel()
{
MyAsyncMethod()
}
public IEnumerable MyList
{
set
{
if (myList != value)
{
myList = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("MyList"));
}
}
}
get
{
return myList;
}
}
async void MyAsyncMethod()
{
MyList = await DoSomethingAsync();
}
}
I review all answer but all have a performance issue.
for example in :
string _Title;
public string Title
{
get
{
if (_Title == null)
{
Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
}
return _Title;
}
set
{
if (value != _Title)
{
_Title = value;
RaisePropertyChanged("Title");
}
}
}
Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
use dispatcher which is not a good answer.
but there is a simple solution, just do it:
string _Title;
public string Title
{
get
{
if (_Title == null)
{
Task.Run(()=>
{
_Title = getTitle();
RaisePropertyChanged("Title");
});
return;
}
return _Title;
}
set
{
if (value != _Title)
{
_Title = value;
RaisePropertyChanged("Title");
}
}
}
You can change the proerty to Task<IEnumerable>
and do something like:
get
{
Task<IEnumerable>.Run(async()=>{
return await getMyList();
});
}
and use it like
await MyList;

Categories

Resources