I am trying to pass an asynchronous method to a command in xamarin forms. In microsoft docs, the sample codes are provided with lambda expressions. As I am pretty new at c#, I want to see the explicit form of it to understand the concept clearly:
The code with lambda:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
NavigateCommand = new Command<Type>(
async (Type pageType) =>
{
Page page = (Page)Activator.CreateInstance(pageType);
await Navigation.PushAsync(page);
});
BindingContext = this;
}
public ICommand NavigateCommand { private set; get; }
}
So, my question is how to retype NavigationCommand without lambda. I think It would be more beneficial to the beginners.
Thanks a lot for any respond.
You could check the following code
NavigateCommand = new Command<Type>((pageType) => TestCommand(pageType));
async void TestCommand(Type pageType)
{
Page page = (Page)Activator.CreateInstance(pageType);
await Navigation.PushAsync(page);
}
If your method has no argument, you could init it like
NavigateCommand = new Command(TestCommand);
async void TestCommand()
{
//...
}
Related
I have a Blazor Server .Net 6 app. It has a Synfusion grid which has an ImageViewer componen that I have built. When the grid is loaded it passes a DocumentID to the ImageViewer for each row. The ImageViwer takes the DocumenID and loads an image via a web API service from a database. Teh problem I have is that the image does not fully load, it works if I use the OnInitializedAsync method but thsi does not work if I filter the data. Any ideads the best method to load such images
<SfGrid>
<MyImageViewer AuthCookieValue="#AuthCookieValue" DocumentID="#data.DocumentID" />
<SfGrid>
//THIS IS MY IMAGE CONTROL
#inject HttpClient httpClient
#if (DocumentFileData != null)
{
<img src="data:image;base64,#(Convert.ToBase64String(DocumentFileData))" />
}
#code {
public int _DocumentID { get; set; }
[Parameter] public string AuthCookieValue { get; set; }
[Parameter] public int DocumentID
{
get { return _DocumentID; }
set
{
_DocumentID = value;
//I know this is naughty to call a method via a property and does not work but thought I would try to trigger the change of the image when I refresh the grid
Task.Run(() => GetDocument());
}
}
private byte[] DocumentFileData { get; set; }
protected async override Task OnInitializedAsync()
{
//THIS METHOD WORKS BUT DOES NOT WORK WHEN I CHANGE THE GRID
if (DocumentID != 0)
{
await GetDocument();
}
}
private async Task GetDocument()
{
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + AuthCookieValue);
MyServices.DocumentService documentService;documentService = new(httpClient);
documentService = new(httpClient);
DocumentModel doc = await documentService.GetDocumentAsync(_DocumentID);
DocumentFileData = doc.FileData;
}
}
Many thanks in advance
Make two small changes:
// //I know this is naughty to call a method via a property and does not work but thought I would try to trigger the change of the image when I refresh the grid
// Task.Run(() => GetDocument());
and
//protected async override Task OnInitializedAsync()
protected async override Task OnParametersSetAsync()
{
See the Lifecycle events documentation.
OnInitializedAsync() is only called once in the lifetime of a component. OnParametersSetAsync() is called each time DocumentID changes, and the side benefit is that you don't need that Task.Run() anymore.
The fact that Task.Run() was not awaited here made your UI fall out of sync and not show the image. It was being loaded but not rendered.
I have some property which I am using from Page2 in HomePageViewModel, When I navigate to Page2 I have changed that property and on coming back by doing NavigiationPop.
HomePage is not re-loaded/ refreshed at all as I have set the BindingContext in the constructor of HomePage, which is loaded only once.
The solution which is working for me is
Setting the BindingContext to the ViewModel on "onAppearing()", usually its not consider the best practice. But I also need to refresh the values which I have changed on Page2.
Looking forward for your valuable inputs and suggestions.
Also Pros/Cons of Setting the BindingContext in OnAppearing.
Thanks,
Hemant
It depends on you, they all can achieve the effect.
However if you want to reduce the CPU consumption of the program, then don't use the onAppearing method too much to refresh the page data.
Another method is that you can change the data of the model through Delegate/Event/MessageCenter.
You can refer to this discussion to know how to use Delegate/Event/MessageCenter.
Here I will give the sample by using MessageCenter to achieve that.
For example, the TestViewModel as follows:
public class TestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string pagetitle;
public string PageTitle
{
get
{
return pagetitle;
}
set
{
if (pagetitle != value)
{
pagetitle = value;
NotifyPropertyChanged();
}
}
}
}
Then bind it in FirstPage as follows, and Subscribe MessageCenter inside it:
TestViewModel testViewModel = new TestViewModel();
BindingContext = testViewModel;
MessagingCenter.Subscribe<object>(this, "Hi", (sender) =>
{
// Do something whenever the "Hi" message is received
testViewModel.PageTitle = "Modified Title";
});
Then in the SecondPage, when changing the data by sending message. When back to FirstPage, the View Will be updated.
private void Button_Clicked(object sender, EventArgs e)
{
MessagingCenter.Send<object>(this, "Hi");
}
I'm looking to improve my mobile application developed in Xamarin.Forms.
My functionality is as follows: onResume of the application I want to reload the page on which the user was.
Currently I use the MessagingCenter to operate with the code below.
Unfortunately my application is starting to have a lot of pages and it's not very readable anymore.
I am therefore looking to pass my type (viewModel) as a parameter of my navigation service - my research directs me towards the concept of reflection but I don't know if my problem is achievable.
// App.xaml.cs
protected override void OnResume()
{
// Handle when your app resumes
Page currPage = ((NavigationPage)((MasterDetailPage)Application.Current.MainPage).Detail).CurrentPage;
MessagingCenter.Send<App, Page>(this, "Hi", currPage);
}
Then in my BaseViewModel :
// BaseViewModel.cs
public ViewModelBase()
{
DialogService = ViewModelLocator.Instance.Resolve<IDialogService>();
NavigationService = ViewModelLocator.Instance.Resolve<INavigationService>();
AuthenticationService = ViewModelLocator.Instance.Resolve<IAuthenticationService>();
MessagingCenter.Subscribe<App, Page>(this, "Hi", async (sender, arg) =>
{
// Do something whenever the "Hi" message is received
Type viewModel = NavigationService.GetViewModelTypeForPage(arg.GetType());
if(viewModel == typeof(AboutViewModel))
{
Debug.WriteLine("AboutViewModel");
await NavigationService.NavigateToAsync<AboutViewModel>();
return;
}
if (viewModel == typeof(CardViewModel))
{
Debug.WriteLine("CardViewModel");
await NavigationService.NavigateToAsync<CardViewModel>();
return;
}
...
});
}
I would give you some ideas on how to make your code readable when using MessagingCenter.
First, you can have a BasePage which implemented the MessagingCenter.Subscribe and a method which called loadData:
public partial class BasePage : ContentPage
{
public BasePage()
{
MessagingCenter.Subscribe<App, string>(this, "Hi", (sender, arg) =>
{
// Do something whenever the "Hi" message is received
loadData();
});
}
public virtual void loadData()
{
}
}
Then, when you create a new page which need to refresh when the application is resumed, you can make the page inherits from the BasePage type:
public partial class MainPage : BasePage
{
public MainPage()
{
InitializeComponent();
loadData();
}
public override void loadData()
{
base.loadData();
Console.WriteLine("loadData");
}
}
And the xaml:
<?xml version="1.0" encoding="utf-8" ?>
<bases:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:bases="clr-namespace:App52"
mc:Ignorable="d"
x:Class="App52.MainPage">
</bases:BasePage>
So you don't have to implement MessagingCenter.Subscribe in each Page, those can be managed in BasePage.
I'm not familiar with reflection so maybe can't help you on achieving that by reflection. Hope this helps.
I'm developing my Xamarin app with the MVVM pattern. I want to display an alert to the user when the user presses a button.
I declare my ViewModel with
class MainPageViewModel : BindableBase {
Unfortunately, I'm not able to access a Page object from within the ViewModel directly. How do I best go about displaying my alert?
Late to the party but as Nick Turner has mentioned in a number of comments, the solutions given so far require the view model to reference the view which is an antipattern/infringement of MVVM. Additionally, you'll get errors in your view model unit tests such as: You MUST call Xamarin.Forms.Init(); prior to using it.
Instead you can create an interface that contains the code for your alert boxes and then use in your view model as follows:
Interface:
public interface IDialogService
{
Task ShowAlertAsync(string message, string title, string buttonLabel);
}
Implementation (Using ACR.UserDialogs NuGet package):
public class DialogService : IDialogService
{
public async Task ShowAlertAsync(string message, string title, string buttonLabel)
{
if (App.IsActive)
await UserDialogs.Instance.AlertAsync(message, title, buttonLabel);
else
{
MessagingCenter.Instance.Subscribe<object>(this, MessageKeys.AppIsActive, async (obj) =>
{
await UserDialogs.Instance.AlertAsync(message, title, buttonLabel);
MessagingCenter.Instance.Unsubscribe<object>(this, MessageKeys.AppIsActive);
});
}
}
}
ViewModel:
public class TestViewModel
{
private readonly IDialogService _dialogService;
public TestViewModel(IDialogService dialogService)
{
//IoC handles _dialogService implementation
_dialogService = dialogService ?? throw new ArgumentNullException(nameof(dialogService));
}
public ICommand TestCommand => new Command(async () => await TestAsync());
private async Task TestAsync()
{
await _dialogService.ShowAlertAsync("The message alert will show", "The title of the alert", "The label of the button");
}
}
TestCommand can then be bound to the button in the your xaml:
<Button x:Name="testButton" Command="{Binding TestCommand}">
To display Alert write below code in your ViewModel class
public class MainViewModel
{
public ICommand ShowAlertCommand { get; set; }
public MainViewModel()
{
ShowAlertCommand = new Command(get => MakeAlter());
}
void MakeAlter()
{
Application.Current.MainPage.DisplayAlert("Alert", "Hello", "Cancel", "ok");
}
}
Set your Command to Button in xaml
<StackLayout>
<Button Text="Click for alert" Command="{Binding ShowAlertCommand}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
Set BindingContext in code behind of your xaml file. If you xaml file MainPage.xaml
public MainPage()
{
InitializeComponent();
BindingContext = new MainViewModel();
}
You can call the below code within the view model, if you are using normal MVVM pattern.
App.current.MainPage.DisplayAlert("","","");
You can use Prism's PageDialogService which keeps your ViewModels very clean and testable.
For Shell application I was able to achieve like this
await Shell.Current.DisplayAlert("Title", "Message", "Cancel");
Would it be a good idea to create popups like this? (called from my ViewModel)
private void OpenPopUp()
{
Application.Current.MainPage.Navigation.ShowPopup(new CustomPopUp());
}
You can find a guide on how to create custom pupups here:
https://www.youtube.com/watch?v=DkQbTarAE18
Works pretty good for me, but I am very new to Xamarin.
I have this code and I am wanting to move it into a view model:
resetButton.Clicked += async (sender, e) =>
{
if (App.totalPhrasePoints < 100 || await App.phrasesPage.DisplayAlert(
"Reset score",
"You have " + App.totalPhrasePoints.ToString() + " points. Reset to 0 ? ", "Yes", "No"))
App.DB.ResetPointsForSelectedPhrase(App.cfs);
};
I realize I will need to set up something like this:
In my XAML code;
<Button x:Name="resetButton" Text="Reset All Points to Zero" Command="{Binding ResetButtonClickedCommand}"/>
And in my C# code:
private ICommand resetButtonClickedCommand;
public ICommand ResetButtonClickedCommand
{
get
{
return resetButtonClickedCommand ??
(resetButtonClickedCommand = new Command(() =>
{
}));
}
But how can I fit the async action into a command?
You can try something like this:
(resetButtonClickedCommand = new Command(async () => await SomeMethod()));
async Task SomeMethod()
{
// do stuff
}
And to expand on already provided answer, if you need to pass a parameter to the command, you can use something like
(resetButtonClickedCommand = new Command<object>(async (o) => await SomeMethod(o)));
async Task SomeMethod(object o)
{
// do stuff with received object
}
You could replace object above by anything you want as well.
On further testing, this class may be overkill for most uses.
Despite the downvotes, chawala's answer works fine in my tests.
Importantly, the presence of async on the method declaration is sufficient to avoid blocking UI thread. Therefore, chawala's answer is "not broken"; does not merit those downvotes, imho.
To be clear: the explicit async => await answers are of course perfectly fine, without any issue. Use them if that gives you more confidence.
My answer was intended to make the call site cleaner. HOWEVER, maxc's first comment is correct: what I have done is no longer "identical" to the explicit async => await. So far, I haven't found any situation where that matters. Whether with or without async/await inside new Command, if you click a button several times quickly, all the clicks get queued. I've even tested with SomeMethod switching to a new page. I have yet to find any difference whatsoever to the explicit async/await. All answers on this page have identical results, in my tests.
async void works just as well as async Task, if you aren't using the Task result anyway, and you aren't adding any code to do something useful with any exceptions that occur during this method.
In this class code, see my comment "TBD: Consider adding exception-handling logic here.".
Or to put it another way: most devs are writing code where it makes no difference. If that's a problem, then its going to equally be a problem in their new Command(await () => async SomeMethod()); version.
Below is a convenience class. Using it simplifies combining commands with async.
If you have an async method like this (copied from accepted answer):
async Task SomeMethod()
{
// do stuff
}
Without this class, using that async method in a Command looks like this (copied from accepted answer):
resetButtonClickedCommand = new Command(async () => await SomeMethod());
With the class, usage is streamlined:
resetButtonClickedCommand = new AsyncCommand(SomeMethod);
The result is equivalent to the slightly longer code line shown without using this class. Not a huge benefit, but its nice to have code that hides clutter, and gives a name to an often-used concept.
The benefit becomes more noticeable given a method that takes a parameter:
async Task SomeMethod(object param)
{
// do stuff
}
Without class:
yourCommand = new Command(async (param) => await SomeMethod(param));
With class (same as the no-parameter case; compiler calls the appropriate constructor):
yourCommand = new AsyncCommand(SomeMethod);
Definition of class AsyncCommand:
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;
namespace MyUtilities
{
/// <summary>
/// Simplifies using an "async" method as the implementor of a Command.
/// Given "async Task SomeMethod() { ... }", replaces "yourCommand = new Command(async () => await SomeMethod());"
/// with "yourCommand = new AsyncCommand(SomeMethod);".
/// Also works for methods that take a parameter: Given "async Task SomeMethod(object param) { ... }",
/// Usage: "yourCommand = new Command(async (param) => await SomeMethod(param));" again becomes "yourCommand = new AsyncCommand(SomeMethod);".
/// </summary>
public class AsyncCommand : ICommand
{
Func<object, Task> _execute;
Func<object, bool> _canExecute;
/// <summary>
/// Use this constructor for commands that have a command parameter.
/// </summary>
/// <param name="execute"></param>
/// <param name="canExecute"></param>
/// <param name="notificationSource"></param>
public AsyncCommand(Func<object,Task> execute, Func<object, bool> canExecute = null, INotifyPropertyChanged notificationSource = null)
{
_execute = execute;
_canExecute = canExecute ?? (_ => true);
if (notificationSource != null)
{
notificationSource.PropertyChanged += (s, e) => RaiseCanExecuteChanged();
}
}
/// <summary>
/// Use this constructor for commands that don't have a command parameter.
/// </summary>
public AsyncCommand(Func<Task> execute, Func<bool> canExecute = null, INotifyPropertyChanged notificationSource = null)
:this(_ => execute.Invoke(), _ => (canExecute ?? (() => true)).Invoke(), notificationSource)
{
}
public bool CanExecute(object param = null) => _canExecute.Invoke(param);
public Task ExecuteAsync(object param = null) => _execute.Invoke(param);
public async void Execute(object param = null)
{
// TBD: Consider adding exception-handling logic here.
// Without such logic, quoting https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
// "With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started."
await ExecuteAsync(param);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}
Re comments below about async void Execute. Both class Command and interface ICommand have method void Execute. Being compatible with those, means having the same method signature - so the usually recommended async Task MethodName() is not an option here. See links in my comments for discussion of the implications of having void here.
To instance AsyncCommand with a parameter, is correct this approach:
this.SaveCommand = new AsyncCommand((o) => SaveCommandHandlerAsync (o));
or is need
you can write this also:-
(resetButtonClickedCommand = new Command(DoSomething));
async void DoSomething()
{
// do something
}
Note :- It shows warning at SomeMethod.