how to use parameter in viewmodel ins xamarin.forms - c#

Im trying to pass parameter to viewmodel in Xamarin.Forms.But Also I want to use it while loading the page ,to call a rest service with that parameter.But In load event parameter is null always.(I can see that parameter if I bind it to a label or etc...)
How can I make it work properly and use that parameter inside the load event of viewmodel? Any help is appreciated,thanks
here is the button click event and navigate to Samplepage.
private void OntaptedBildirimItem(string param)
{
this._navigationService.NavigateTo(nameof(Views.SamplePage), param);
}
this is the Sample Page Viewmodel
private string _id;
public string id
{
get
{
return _id;
}
set
{
Set(() => id, ref _id, value);
}
}
public SamplePageViewModel(INavigationService navigationService) : base(navigationService)
{
this._navigationService = navigationService;
this.Title = "Sample Page=" + id; // here the id is always null,but if I use it to bind a label in xaml ,it has a value.
}
This is the SamplePage Code
public SamplePage(string parameter)
{
InitializeComponent();
var viewModel = this.BindingContext as ViewModels.SamplePageViewModel;
if(viewModel != null && parameter != null)
{
viewModel.bildirimid = parameter;
}
}

Thx for this Miguel ,this helps me a lot ,I can do what I want by this approach .You can send parameter while navigating a page in xamarin.forms like this
_navigationService.NavigateToAsync<SamplePageViewModel>("Your_parameter");
and you can get that parameter in that page's viewmodel like this
public override async Task InitializeAsync(object yourparameter)
{
// use yourparameter as you wish
}

We need see NavigationService .
You should add bindingContext in NavigationService not in SamplePage.
In This Sample uses NavigationService with a parameter and a better NavigationService structure. You should see it.
Send feedback please.

Related

Xamarin.Forms and Prism - How to pass data and navigate to another view?

This's my first question here, so hi everybody.
I'm working on the mobile app in Xamarin.Forms with Prism. I've created ListView where shown data from the database.
When the user clicks in the selected row app should navigate to a new view and pass the selected item from ListView.
<ListView x:Name="DefectsBase"
RowHeight="65"
ItemsSource="{Binding Defects}"
ItemSelected="ShowDetailsEvent"
IsPullToRefreshEnabled="true"
RefreshCommand="{Binding Refresh}"
IsRefreshing="{Binding IsRefreshing}">
Code backend:
async void ShowDetailsEvent(object sender, EventArgs e)
{
var myListView = (ListView)sender;
var myItem = myListView.SelectedItem;
var p = new NavigationParameters();
p.Add("selectedDefect", myItem);
await _navigationService.NavigateAsync("DefectDetailsView", p);
}
Unfortunately, the app doesn't respond to pressing the selected row in ListView.
As I can see you are already using Prism and you have a List page with Items and you want to navigate to some details page based on the selected/taped/chosen item which the user taps in the ListView.
The idea is to move as much code and logic as we can to the view model and keep our code-behind. This is pretty easy to solve using Prism and EventToCommand behaviour.
In the example and answer below, I will show you how to solve this with few lines of code, with a nice code approach.
First of all, I recommend you use EventToCommand behaviour, you can include it with prism xmlns, like this: xmlns:prism="http://prismlibrary.com", later on, you can use it with ListView.
Remove ItemSelected event from your ListView and move the markup about it to the <ListView.Behaviors> part. Here is my code sample for the ListView which binds to some ObserverableCollection of the Car models:
<ListView ItemsSource="{Binding Cars}">
<ListView.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Behaviors>
<prism:EventToCommandBehavior EventName="ItemTapped"
Command="{Binding SelectedCarCommand}"
EventArgsParameterPath="Item" />
</ListView.Behaviors>
The main part here is <ListView.Behaviors>, where you can see that I am binding to the SelectedCarCommand which will be invoked when the user taps on some of the items from the list. I am using the ItemTapped event for this and passing the current "taped" item from the list as a parameter.
In order to follow this XAML part in my view model of this page, I have declared the DelegateCommand and method which will be called when the command is invoked. The view model part looks like this:
This is my CarListPageViewModel, take a look at DelegateCommand and SelectedCar method.
public class CarListPageViewModel
{
private readonly INavigationService _navigationService;
public ObservableCollection<Car> Cars { get; set; }
public DelegateCommand<Car> SelectedCarCommand { get; private set; }
public CarListPageViewModel(INavigationService navigationService, IDataProvider dataProvider)
{
_navigationService = navigationService;
// Insert test data into collection of Cars
Cars = new ObservableCollection<Car>(dataProvider.GetData());
SelectedCarCommand = new DelegateCommand<Car>(SelectedCar);
}
private async void SelectedCar(Car selectedCar)
{
NavigationParameters navigationParameters = new NavigationParameters
{
{ "selectedCar", selectedCar }
};
await _navigationService.NavigateAsync(nameof(CarDetailsPage), navigationParameters);
}
}
As you can see we have DelegateCommand defined with the type of parameter which will be passed, in my case, this is the Car class, the same class as our items in the ListView.
In the constructor, I did my initialization and defined the method which will be called, that method has a parameter of the type Car.
When the user taps on one of the items in the ListView, SelectedCar (method) will be called and we can pass the data to the next view using NavigationParameters and NavigationService.
In order to retrieve the passed data we can use INavigationAware in the details view model and with the OnNavigatedTo method, access the data which is being passed.
This is my CarDetailsPageViewModel, take a look at OnNavigatedTo method.
public class CarDetailsPageViewModel : BindableBase, INavigationAware
{
private string carTitle;
public string CarTitle
{
get { return carTitle; }
set { SetProperty(ref carTitle, value); }
}
private string photoUrl;
public string PhotoUrl
{
get { return photoUrl; }
set { SetProperty(ref photoUrl, value); }
}
public CarDetailsPageViewModel() { }
public void OnNavigatedTo(INavigationParameters parameters)
{
if (parameters.ContainsKey("selectedCar"))
{
Car car = parameters.GetValue<Car>("selectedCar");
if (car != null)
{
CarTitle = $"{car.Make} {car.Model}";
PhotoUrl = car.PhotoUrl;
}
}
}
public void OnNavigatedFrom(INavigationParameters parameters) { }
}
From this answer and example, you can see:
How to, use EventToCommand behaviour with ListView
Define and use DelegateCommand with passing parameter
How to navigate to another view and pass navigation parameter and
... finally how to access the passed data.
Code and this sample you can find on my GitHub profile here.
Hope this answer was helpful for you!
Wishing you lots of luck with coding! 👋

Prism navigation breaks page binding after returning to the page

I have two pages . the first page has an Image with the source bound the second page has a label with text bound.
when I navigate using prism navigation navigatedTo to the second page the label binding works but when I hit the back button to the first page the image disapears then when I navigate again to the second page the label text is empty .
Update added more info and some code
Main page has a listview of Merchants when clicked on an item it trigger a command and Merchant obj as paramter, the First page takes this object and do other operations on it one of the features is a scratch game when clicked on a button it takes you to the second page (scratchgame) also bound with command and a parameter of Merchant Obj.
First Page (Merchant Page) view :
<controls:CircleImage x:Name="logoimg"
TranslationY="-25" WidthRequest="100"
VerticalOptions="End"
BorderColor="#800080"
BorderThickness="2"
Source="{Binding Merchant.MerchantLogo}">
</controls:CircleImage>
First Page ViewModel :
internal class MerchantPageViewModel : AppMapViewModelBase, INavigationAware
{
private NojoomAppManager manager;
private readonly INavigationService _navigationService;
private Merchant _merchant;
public Merchant Merchant
{
get { return _merchant; }
set { _merchant = value; RaisePropertyChanged(nameof(Merchant)); }
}
public MerchantPageViewModel(INavigationService navigationService) : base(navigationService)
{
// Azure Mobile SDK
manager = NojoomAppManager.DefaultManager;
_navigationService = navigationService;
}
public new DelegateCommand<object> ScratchGameNavigateCommand =>
_scratchGameNavigateCommand ?? (_scratchGameNavigateCommand = new DelegateCommand<object>(ExecuteScratchNavigateCommand));
private async void ExecuteScratchNavigateCommand(object obj)
{
var p = new NavigationParameters();
p.Add("merchant", obj);
await NavigationService.NavigateAsync("ScratchGame", p);
}
public void OnNavigatedTo(INavigationParameters parameters)
{
Merchant = parameters.GetValue<Merchant>("merchant");
}
}
Second page ( scratch game ) view
<Label x:Name="Credit" FontSize="Large" FontAttributes="Bold" VerticalOptions="Center"></Label>
this label take its value from an API call
second page code :
private Merchant Merchant = new Merchant();
private Wallet Wallet = new Wallet();
public ScratchGame()
{
InitializeComponent();
manager = NojoomAppManager.DefaultManager;
}
private async Task Getwallet()
{
try
{
var wallets = await manager.GetWalletByIdAsync(Merchant.Id, Settings.UserId, false);
Wallet = wallets.First();
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
public void OnNavigatedTo(INavigationParameters parameters)
{
Merchant = parameters.GetValue<Merchant>("merchant");
Task.Run(async () => { await Getwallet(); }).Wait();
Credit.text = Wallet.SilverStars.ToString();
}
Update 2
after further investigation when I hit the back button on the second page , the first page NavigatedTo is triggerd but without paramter which makes the image source null .
How do I handle this and make the parameter passed again or used again when the back button is hit ?
The OnNavigatedTo method is executed when another page navigates to it. This is called after the ViewModel has been pushed on to the stack, this means that you either need to pass the parameter back to it OR check whether the property already has value, because when you navigate back, the ViewModel is already pushed into the stack, and it keeps values setted before.
The solution will depend on your preference, viability or ease of use when developing, is the page gonna be in the middle of a big stack? if so, maybe passing the parameter on every page after that one is not the smartest way.
Here is an example (remember, this might not be suitable to every situation, but for most cases it is):
public void OnNavigatedTo(INavigationParameters parameters)
{
if(Merchant == null)
Merchant = parameters.GetValue<Merchant>("merchant");
}
I found the solution ,simply I add this line of code in the second page .
I need to pass the paramater back again to the first page otherwise it will be null when navigated back to it.
public void OnNavigatedFrom(INavigationParameters parameters)
{
parameters.Add("merchant", Merchant);
}

Extending dynamic dispatch to call functions in the view model?

I'm using MVVM in a Xamarin application, I have an interface to navigate between pages:
public interface INavigate
{
INavigate Next();
INavigate Previous();
string ViewTitle { get; }
}
In the implementing views:
public partial class V2Upload : ContentView, INavigate
{
public string ViewTitle => "Upload photos";
public INavigate Next()
=> new V3AdDetail();
public INavigate Previous()
=> new V1Agreement();
}
and in the view model
I have a property of type INavigate:
public INavigate CurrentAddItemStep
{
get { return _currentAddItemStep; }
set { Set(ref _currentAddItemStep, value); }
}
and the Content property of the parent view is bound to this property:
when next button is clicked I execute this code:
CurrentAddItemStep = CurrentAddItemStep.Next();
ViewTitle = CurrentAddItemStep.ViewTitle;
now a validation method is required before navigating to the next page for all the Content views..
I want to keep the MVVM pattern as clean as possible by not writing business code in the view, for example in the V2Upload view the File1 and File2 properties of the view model shouldn't be null:
private bool ValidateFiles(){
return (File1 ?? File2) != null;
}
but since the navigating is done dynamically in run-time, I can't know which view is the current view.
I'm thinking to use reflection , to know what is the name of the view (but this will break the whole design)
Another option is to provide a function parameter to the Next method, but also how to provide it in the design time from the view model?
This is what I'm doing now:
public INavigate Next()
{
if (((ViewModel.AddItemViewModel)BindingContext).ValidateFiles())
return new V3AdDetail();
else
return this;
}
but again, I'm accessing the view model from the view (and had to change the ValidateFiles method from private to public), which I want to avoid

How to update cuurent view in prism region

I wish to know that is there a way to update my current view after loading in prism region.
My view is updated automatically when loaded and i use lifetime interface to load every time when called.
is there a way where we can update the current view like update folder??
First of all, the view model (and with it the view) should update itself automatically when the model changes, either through INotifyPropertyChanged, dedicated events, usage of the EventAggregator or any other message passing system.
That being said, if you want the view model to update only at a certain point in time (e.g. when the user clicks an update button), you should move the update code out of the NavigatedTo method and call that method from NavigatedTo and the UpdateCommand.
internal class MyViewModel : BindableBase, INavigationAware
{
public MyViewModel( IDataSource theSourceOfData )
{
_theSourceOfData = theSourceOfData;
UpdateCommand = new DelegateCommand( UpdateData );
}
public string MyProperty
{
get
{
return _myProperty;
}
set
{
SetProperty( ref _myProperty, value );
}
}
public DelegateCommand UpdateCommand { get; }
#region INavigationAware
public void OnNavigatedTo( NavigationContext navigationContext )
{
UpdateData();
}
#endregion
#region private
private readonly IDataSource _theSourceOfData;
private string _myProperty;
private void UpdateData()
{
_myProperty = _theSourceOfData.FetchTheData();
}
#endregion
}
Now, if we click the update button, MyViewModel.MyProperty is updated and the change notification pushed out to the view. Same happens if we navigate to the view model.

PropertyChangedEvent and CanExecute issue

I am using MVVM (prism) to develop wpf application.
One of my model class "StandardContact" has its properties directly bound to the view. I use IDataErrorInfo to track and notify whether the model has any error. If there are any errors in Model, I disable the "Save" Command.
As the user enters some data, I use the StandardContact.PropertyChanged handler to see if "Save" command can execute (i.e if the model data entered by user is valid). The problem is that the StandardContact.PropertyChanged handler is called before the IDataErrorInfo's validation code, so CanExecute for "Save" command does not correctly reflect whether the command can be executed or not. What I am looking for is that, before the CanExecute executes, the IDataErrorInfo validation should run so that the CanExecute will query on the latest data in model and decide whether it is enabled or not. Here is the sample code that I am using
Model:
public class StandardContact :EntityBase, IDataErrorInfo
{
public virtual string Name
{
get { return _name; }
set { SetField(ref _name, value, () => Name); }
}
//...
//Validators
public string this[string propertyName]
{
get
{
string error = null;
//....
}
ViewModel
public class SContactEditViewModel : NotificationObject, INavigationAware
{
//....
StandardContact.PropertyChanged +=
new PropertyChangedEventHandler(StandardContact_PropertyChanged);
void StandardContact_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//Requery if command can execute
SaveNewCommand.RaiseCanExecuteChanged();
}
}
I just inspected our priprietary MVVM library. Inside the ViewModels indexer (in your case this is the Models indexer) the requested Property is validated:
public string this[string propertyName]
{
get
{
string result = null;
if (CanDataErrorValidated(propertyName))
{
int errorCount = CurrentValidationAdapter.ErrorCount();
result = ValidateProperty(propertyName, GetValidateValue(propertyName));
// if the error flag has been changed after validation
if (errorCount != CurrentValidationAdapter.ErrorCount())
{
RaisePropertyChanged(PropHasError);
RaisePropertyChanged(PropError);
}
}
else
{
RaisePropertyChanged(PropHasError);
RaisePropertyChanged(PropError);
}
return result;
}
}
So the solution of your problem seems to validate the requested property on the fly.
I don't use prism, but if it exposes some sort of IsValid method or property you can use that to trigger your error checking. And if it doesn't you can write your own.
The basic idea without prism is to have to leverage IDataErrorInfo.Error by doing
bool IsValid{ get{return string.IsNullOrEmpty(Error) } // trigger validation
Then inside your Save.CanExecute method
return IsValid; // trigger validation on demand
HTH,
Berryl

Categories