I would like to make a service call to a REST API to check a value and if true, take the user to a new page. Instead of presenting a view controller, I'd like to just use a segue that I have wired up.
The service call to check the value is async Task, and I am calling it when a segue tries to fire (when the user presses the button)
public override bool ShouldPerformSegue(string segueIdentifier, NSObject sender)
{
.. run check here, and return true or false to fire the segue
}
The problem is that C# wants me to modify this method to be async Task or async void or async Task but that breaks the 'override' since i'm no longer overriding.
What is the correct approach to handle a call with async programming, and then once the call finishes, take the user away?
Thanks so much.
You can try to start your method in a Task and when finished take the user away.
Example:
var someTask = Task.Run(async () =>
{
var EventModal = await Method();
await Navigation.PushAsync(Page(EventModal.Stuff));
});
Related
In the diagram below from this page it shows that when an incomplete task is returned by the call to OnInitializedAsync it will await the task and then render the component.
However it seems that what actual happens when an incomplete task is returned is renders the component immediately, and then renders it again once the incomplete task completes.
An example later in the page seems to confirm this. If the component was not rendered immediately after the call to OnInitializedAsync, and instead only rendered for the first time after the Task returned had been completed you would never see the "Loading..." message.
OnParametersSetAsync behavior appears the same. It renders once immediately when an incomplete task is returned, and then again once that task has completed.
Am I misunderstanding the render lifecycle, or is this an error in the documentation?
Thanks
#page "/fetchdata"
#using BlazorSample.Data
#inject WeatherForecastService ForecastService
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
#if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<!-- forecast data in table element content -->
</table>
}
#code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
}
}
To fully answer your question we need to delve into the ComponentBase code.
Your code is running in the async world where code blocks can yield and give control back to the caller - your "incomplete task is returned".
SetParametersAsync is called by the Renderer when the component first renders and then when any parameters have changed.
public virtual Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
if (!_initialized)
{
_initialized = true;
return RunInitAndSetParametersAsync();
}
else
return CallOnParametersSetAsync();
}
RunInitAndSetParametersAsync is responsible for initialization. I've left the MS coders' comments in which explains the StateHasChanged calls.
private async Task RunInitAndSetParametersAsync()
{
OnInitialized();
var task = OnInitializedAsync();
if (task.Status != TaskStatus.RanToCompletion && task.Status != TaskStatus.Canceled)
{
// Call state has changed here so that we render after the sync part of OnInitAsync has run
// and wait for it to finish before we continue. If no async work has been done yet, we want
// to defer calling StateHasChanged up until the first bit of async code happens or until
// the end. Additionally, we want to avoid calling StateHasChanged if no
// async work is to be performed.
StateHasChanged();
try
{
await task;
}
catch // avoiding exception filters for AOT runtime support
{
if (!task.IsCanceled)
throw;
}
// Don't call StateHasChanged here. CallOnParametersSetAsync should handle that for us.
}
await CallOnParametersSetAsync();
}
CallOnParametersSetAsync is called on every Parameter change.
private Task CallOnParametersSetAsync()
{
OnParametersSet();
var task = OnParametersSetAsync();
// If no async work is to be performed, i.e. the task has already ran to completion
// or was canceled by the time we got to inspect it, avoid going async and re-invoking
// StateHasChanged at the culmination of the async work.
var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
task.Status != TaskStatus.Canceled;
// We always call StateHasChanged here as we want to trigger a rerender after OnParametersSet and
// the synchronous part of OnParametersSetAsync has run.
StateHasChanged();
return shouldAwaitTask ?
CallStateHasChangedOnAsyncCompletion(task) :
Task.CompletedTask;
}
In the diagram substitute "Render" for StateHasChanged in the code above.
The diagram uses the work "Render", which is a bit misleading. It implies that the UI re-renders, when what actually happens is a render fragment (a block of code that builds the UI markup for the component) is queued on the Renderer's render queue. It should say "Request Render" or something similar.
If the component code that triggers a render event, or calls StateHasChanged, is all synchronous code, then the Renderer only gets thread time when the code completes. Code blocks need to "Yield" for the Renderer to get thread time during the process.
It's also important to understand that not all Task based methods yield. Many are just synchronous code in a Task wrapper.
So, if code in OnInitializedAsync or OnParametersSetAsync yields there's a render event on the first yield and then on completion.
A common practice to "yield" in a block of synchronous code is to add this line of code where you want the Renderer to render.
await Task.Delay(1);
You can see ComponentBase here - https://github.com/dotnet/aspnetcore/blob/main/src/Components/Components/src/ComponentBase.cs
Short summary
Blazor adds two 'free' StateHasChanged calls, before and after each lifecycle event and UI event.
StateHasChanged only requests an html update, it does not perform one.
An update request can only be fulfilled after the event
or when the main Thread is released by an await
not every await will release the Thread.
So, when you want to make sure the screen gets updated, use
StateHasChanged();
await Task.Delay(1);
Old answer
when an incomplete task is returned it renders the component immediately, and then renders it again once the incomplete task completes.
Yes, that is a possible sequence.
The flowchart shows the steps for showing a component. What is not so clear from the picture is that the actual rendering is not part of this flow, it happens async on the synchronizationcontext. It can happen when your code awaits something.
So we have this basis non-async sequence:
Oninitialzed[Async]
OnParametersSet[Async]
Render
OnAfterRender[Async]
But when there is something async in this code-path then there can be one extra Render during the await. More Renders are possible when you call StateHasChanged during this flow.
I have a CollectionView in my .NET MAUI app and I placed it inside a RefreshView. When I call my API to populate this CollectionView, I cache the data so that I don't have to keep making API calls everytime the user hits this page.
In order to streamline my code, I created a private method in my view model that calls my API. The view model looks like this:
public partial MyViewModel : BaseViewModel
{
ObservableCollection<MyModel> MyData { get; } = new();
[RelayCommand]
async Task RefreshData()
{
IsBusy = true;
await GetData(true);
IsBusy = false;
}
private async Task GetData(bool shouldGetFreshData)
{
// Makes API call to get data, then assigns it to MyData collection
}
public async void Init()
{
IsBusy = true;
await GetData(false);
IsBusy = false;
}
}
The XAML for the page looks like this:
<RefreshView
IsRefreshing={Binding IsBusy}
Command={Binding RefreshDataCommand}>
<CollectionView>
...
</CollectionView>
</RefreshView>
I also wired the page to use the MyViewModel as its view model AND OnAppearing(), I call the Init() method of the view model.
Here's what I was expecting which is NOT what's happening:
I thought, the Init() would get called first which then calls the GetData() method with false input parameter. This way, I could use the cached data. And whenever, the user refreshes the CollectionView by pulling it down, the RefreshData() method would be called with true as the input parameter which would force the code to make an API call.
Instead of what I was expecting, here's what's happening:
The Init() method gets called first and as a result, the line with IsBusy = true executes.
This then ends up invoking the RefreshData() method
Then the await GetData(false) in Init() method executes
Then the await GetData(true) in RefreshData() method executes
As a result of all this, the GetData() method gets called twice.
I think, what's triggering this is the IsBusy. I thought IsBusy would only serve as an indicator but not necessarily invoke the RefreshData() method which is bound to the Command of my RefreshView.
Is this normal behavior or am I missing something here?
Apparently, this is "normal" behavior because I'm manually setting IsBusy to true. I decided to leave this question here because this may be a pitfall that affects others.
Here's the actual section in documentation that states this:
And here's the documentation: https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/refreshview
So, all I had to do is remove the IsBusy = true in Init() method.
I am having an MvvmCross ViewModel, which calls different async methods of my DataService.
Similar to the following:
public class LoginViewModel : MvxViewModel
{
private readonly IIdentityService _dataService;
private CancellationTokenSource _viewModelCancellationTokenSource;
public IMvxCommand GoLogin { get; set; }
public LoginViewModel(IIdentityService identityService)
{
_dataService = identityService;
_viewModelCancellationTokenSource = new CancellationTokenSource();
GoLogin = new MvxCommand(async () => await ProcessLogin());
}
private async Task ProcessLogin()
{
// calling the dataservice which must stop processing
// (to cancel) in case if the ViewModel is being destroyed
await _dataService.AssureIsLoggedIn(data, _viewModelCancellationTokenSource.Token);
await NavigationService.Navigate<LoginNextStepViewModel>();
}
public override void ViewDestroy(bool viewFinishing = true)
{
base.ViewDestroy(viewFinishing);
// not sure if that is a right (and working) place
_viewModelCancellationTokenSource.Cancel();
}
}
So, MvvmCross is quite unclear about the part with the ViewModel destroying. It describes Construction, Init, Reload and Start, but doesn't say any definite regarding the destroying:
Monitoring other View/ViewModel lifecycle event across multiple
platforms is fairly tricky, especially once developers start
experimenting beyond the ‘basic’ presentation models and start using
tabs, splitviews, popups, flyouts, etc
For most viewmodels, it’s common to not try to monitor other lifecyle
events. This is OK since most viewmodels don’t perform any actions and
don’t consume any resources when the view is not present - so these
can just be left to be garbage collected when the system needs the
memory back.
However, besides the custom platform situations, there are still many cases like navigating back from the view model, or (again) navigation away from current viewmodel with its following closing.
So, what's the best way to handle it then?
From your code:
// calling the dataservice which must stop processing
// (to cancel) in case if the ViewModel is being destroyed
The ViewModel won't be destroyed before the async methods finish executing. I think you are confusing the View with the ViewModel.
In case of a login page, you would usually prevent the user from navigating away from it until your server call goes through.
If for some reason you want to cancel then you need to decide what scenarios you want to handle, there is no single universal place. Your options are the view callbacks:
void ViewDisappearing();
void ViewDisappeared();
void ViewDestroy();
and the navigation events:
event BeforeNavigateEventHandler BeforeNavigate;
event AfterNavigateEventHandler AfterNavigate;
event BeforeCloseEventHandler BeforeClose;
event AfterCloseEventHandler AfterClose;
event BeforeChangePresentationEventHandler BeforeChangePresentation;
event AfterChangePresentationEventHandler AfterChangePresentation;
I want to link async method to a delegate command in prism framework in Xamarin.Forms and my question is how to do it?
Is below solution correct? Is there exist any pitfall? (deadlock, UI slow or freezing, bad practices, ...)
{ // My view model constructor
...
MyCommand = new DelegateCommand(async () => await MyJobAsync());
...
}
private async Task MyJobAsync()
{
... // Some await calls
... // Some UI element changed such as binded Observable collections
}
You can use async void directly. However, a few notes from my experience...
The structure of your code is: start asynchronous operation and then update UI with the results. This implies to me that you would be better served with a NotifyTask<T> kind of approach to asynchronous data binding, not commands. See my async MVVM data binding article for more about the design behind NotifyTask<T> (but note that the latest code has a bugfix and other enhancements).
If you really do need an asynchronous command (which is much more rare), you can use async void directly or build an async command type as I describe in my article on async MVVM commmands. I also have types to support this but the APIs for these are more in flux.
If you do choose to use async void directly:
Consider making your async Task logic public, or at least accessible to your unit tests.
Don't forget to handle exceptions properly. Just like a plain DelegateTask, any exceptions from your delegate must be properly handled.
Just have a look at this link if you're using Prism Library: https://prismlibrary.com/docs/commands/commanding.html#implementing-a-task-based-delegatecommand
In case you want to pass a CommandParameter to DelegateCommand, use in the DelegateCommand variable declaration this syntax
public DelegateCommand<object> MyCommand { get; set; }
In the constructor of the ViewModel initialize it this way:
MyCommand = new DelegateCommand<object>(HandleTap);
where HandleTap is declared as
private async void HandleTap(object param)
Hope it helps.
As has already been mentioned the way to handle async code with delegate command is to use async void. There has been a lot of discussion on this, far beyond just Prism or Xamarin Forms. The bottom line is that ICommand that both the Xamarin Forms Command and Prism DelegateCommand are limited by ICommand's void Execute(object obj). If you'd like to get more information on this I would encourage you to read the blog by Brian Lagunas explaining why DelegateCommand.FromAsync handler is obsolete.
Generally most concerns are handled very easily by updating the code. For example. I often hear complaints about Exceptions as "the reason" why FromAsync was necessary, only to see in their code they never had a try catch. Because async void is fire and forget, another complaint I've heard is that a command could execute twice. That also is easily fixed with DelegateCommands ObservesProperty and ObservesCanExecute.
I think the two main problems when calling an asynchronous method from one that executes synchronously (ICommand.Execute) are 1) denying to execute again while previous call is still running 2) handling of exceptions. Both can be tackled with an implementation like the following (prototype). This would be an async replacement for the DelegateCommand.
public sealed class AsyncDelegateCommand : ICommand
{
private readonly Func<object, Task> func;
private readonly Action<Exception> faultHandlerAction;
private int callRunning = 0;
// Pass in the async delegate (which takes an object parameter and returns a Task)
// and a delegate which handles exceptions
public AsyncDelegateCommand(Func<object, Task> func, Action<Exception> faultHandlerAction)
{
this.func = func;
this.faultHandlerAction = faultHandlerAction;
}
public bool CanExecute(object parameter)
{
return callRunning == 0;
}
public void Execute(object parameter)
{
// Replace value of callRunning with 1 if 0, otherwise return - (if already 1).
// This ensures that there is only one running call at a time.
if (Interlocked.CompareExchange(ref callRunning, 1, 0) == 1)
{
return;
}
OnCanExecuteChanged();
func(parameter).ContinueWith((task, _) => ExecuteFinished(task), null, TaskContinuationOptions.ExecuteSynchronously);
}
private void ExecuteFinished(Task task)
{
// Replace value of callRunning with 0
Interlocked.Exchange(ref callRunning, 0);
// Call error handling if task has faulted
if (task.IsFaulted)
{
faultHandlerAction(task.Exception);
}
OnCanExecuteChanged();
}
public event EventHandler CanExecuteChanged;
private void OnCanExecuteChanged()
{
// Raising this event tells for example a button to display itself as "grayed out" while async operation is still running
var handler = CanExecuteChanged;
if (handler != null) handler(this, EventArgs.Empty);
}
}
async void
I personally would avoid "async void" at all cost. It is impossible to know from the outside when the operation has finished and error handling becomes tricky. In regards to latter, for instance writing an "async Task" method which is called from an "async void" method almost needs to be aware of how its failing Task is propagated:
public async Task SomeLogic()
{
var success = await SomeFurtherLogic();
if (!success)
{
throw new DomainException(..); // Normal thing to do
}
}
And then someone writing on a different day:
public async void CommandHandler()
{
await SomeLogic(); // Calling a method. Normal thing to do but can lead to an unobserved Task exception
}
Is UI thread running DelegateCommand and background threads running await expression?
Yes, the UI thread runs the DelegateCommand. In case of an async one, it runs until the first await statement, and then resumes his regular UI thread work. If the awaiter is configured to capture the synchronization context (that is, you do not use .ConfigureAwait(false)) the UI thread will continue to run the DelegateCommand after the await.
Is UI thread running DelegateCommand and background threads running await expression?
Whether the "await expression" runs on a background thread, foreground thread, a threadpool thread or whatever depends on the api you call. For example, you can push cpu-bound work to the threadpool using Task.Run or you can wait for an i/o-operation without using any thread at all with methods like Stream.ReadAsync
public ICommand MyCommand{get;set;}
//constructor
public ctor()
{
MyCommand = new Xamarin.Forms.Command(CmdDoTheJob);
}
public async void DoTheJob()
{
await TheMethod();
}
public DelegateCommand MyCommand => new DelegateCommand(MyMethod);
private async void MyMethod()
{
}
There are no pitfalls. A void return type in async method was created especially for delegates. If you want to change something, that has reflected on UI, insert relevant code in this block:
Device.BeginOnMainThread(()=>
{
your code;
});
Actually, ICommand and DelegateCommand pretty similar, so an above answer is quite right.
I have an activity which has an async method in it. This async method is long running. After the async method returns, the UI needs to be updated and some of the controls reference the activity.
At the moment, everything works correctly if you do not have a configuration change (like screen rotation) while the async task is running. However, if a configuration change happens while it is running, then the exception Activity is destroyed is thrown and the UI is not updated. From what reading I have done, this seems to be because the async method captures context and then tries to update the old context which is of course destroyed after the configuration change.
My question is: What are the best ways to solve this problem or at worst case scenario work around it?
I personally think you have only three options
You can disable rotation permanently or temporary, but this is a bad practice
To disable it permanently set ConfigurationChanges
[Activity(Label = "...", ConfigurationChanges = Android.Content.PM.ConfigChanges.KeyboardHidden | Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize)]
To disable it temporary while task working you should disable rotation handling,
disable
this.RequestedOrientation = Android.Content.PM.ScreenOrientation.Nosensor;
enable
this.RequestedOrientation = Android.Content.PM.ScreenOrientation.Sensor;
If you are using fragment you can prevent fragment destroy with RetainInstance = true. That might work, but i never tested it.
You can cancel task with CancelationToken and restart it in OnRestoreInstanceState()
Here is example how to cancel task
{
CancellationTokenSource cts;
...
// If a download process is already underway, cancel it.
if (cts != null)
{
cts.Cancel();
}
// Now set cts to cancel the current process if the button is chosen again.
CancellationTokenSource newCTS = new CancellationTokenSource();
cts = newCTS;
try
{
//Send cts.Token to carry the message if there is a cancellation request.
await AccessTheWebAsync(cts.Token);
}
// Catch cancellations separately.
catch (OperationCanceledException)
{
ResultsTextBox.Text += "\r\nDownloads canceled.\r\n";
}
catch (Exception)
{
ResultsTextBox.Text += "\r\nDownloads failed.\r\n";
}
// When the process is complete, signal that another process can proceed.
if (cts == newCTS)
cts = null;
}
And in the task
async Task AccessTheWebAsync(CancellationToken ct)
{
...
// Retrieve the website contents from the HttpResponseMessage.
byte[] urlContents = await response.Content.ReadAsByteArrayAsync();
// Check for cancellations before displaying information about the
// latest site.
ct.ThrowIfCancellationRequested();
...
}
There are plenty of things you could do, but please don't go and disable the phones ability to turn the screen -- that is just going to ignore your users.
At a highlevel you will have to do two things:
Make sure the async task keeps running and is not restarted if the activity dies.
You can solve that by moving the task either into the application class or (cleaner) into a headless fragment with setRetainInstance set to true.
In the onDestroy method in the activity, remove it from the async task, in the onCreate task give the activity to the async task (if it exist).
This is what prevents the async task from calling the old context and can be done with a simple java setter on the async task. Don't forget to cache the result in the task if the activity is currently not connected.
In the end what I ended up doing was encapsulating the async task in another class which held a reference to the current activity, which implemented and interface which defined a method which handles the async response and updates the UI.
The activity held a static variable of the encapsulated async task, and if it was running during a config change, the encapsulated async's task reference to the activity was updated to the new activity.