I am using shell and I want tabs on all the pages. so I am following standard way of shell navigation but the problem is I don't know how to pass an object along with the navigation.
await Shell.Current.GoToAsync(page.ToString());
doing this for navigation
Routing.RegisterRoute("TesProject.Views.DetailView", typeof(DetailView));
Registering Route like this
I want to pass a complete object from my list view to detail view. How can I do that?
Xamarin.Forms Shell Navigation Hierarchy with parameters
I saw this but I don't think this will work in my case because I can't pass a complete model object like this.
I wrote a small example for your reference.
In the sending class, you can pass parameters like $"AferPage?param={param}".
Here is the sending code:
public partial class BeforePage : ContentPage
{
public BeforePage()
{
InitializeComponent();
}
private async void Button_Clicked(object sender, EventArgs e)
{
string param = myEntry.Text;
await Shell.Current.GoToAsync($"AferPage?param={param}");//like this
}
}
Here is the receiving class code(Implements IQueryAttributable interface):
public partial class AferPage : ContentPage, IQueryAttributable
{
public string param {get;set;}
public void ApplyQueryAttributes(IDictionary<string, string> query)
{
param = HttpUtility.UrlDecode(query["param"]);
receive.Text = param;
}
public AferPage()
{
InitializeComponent();
}
}
Using Newtonsoft.Json you can:
In List View:
var jsonStr = JsonConvert.SerializeObject([Model]);
await Shell.Current.GoToAsync($"{nameof([DetailsViewPage])}?Param={jsonStr }");
In Details View Page:
Add QueryProperty:
[QueryProperty(nameof(Param), nameof(Param))]
Convert to Model again:
var bonusesFilterData = JsonConvert.DeserializeObject<[Model]>(Param);
A solution is shown in this video: https://www.youtube.com/watch?v=YwnEpiJd0I8
You can pass objects through as a Dictionary.
For example if this is the data object you want to send:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Set the query property on the view model of the page:
[QueryProperty(nameof(Person), nameof(Person))]
public partial class DetailViewModel
{
[ObservableProperty]
Person person;
}
Assuming your page constructor looks something like this so it will automatically set the value on your context:
public partial class DetailView : ContentPage
{
public DetailView(DetailViewModel vm)
{
InitializeComponent();
BindingContext = vm;
}
Then when you navigate to the page, pass the object in a dictionary:
Routing.RegisterRoute(nameof(DetailView), typeof(DetailView));
await Shell.Current.GoToAsync(nameof(DetailView), {
new Dictionary<string, object> {
[nameof(Person)] = person
});
Now you can access your object in bindings:
<Label Text="{Binding Person.Name}"/>
<Label Text="{Binding Person.Age}"/>
P.S. Notice the use of nameof to avoid hard coded strings.
You can user stored preferences if your json is complex like:
private async void OnItemSelected(Item item)
{
if (item == null)
return;
var jsonstr = JsonConvert.SerializeObject(item);
//Clear the shared preferences in case there is any
Preferences.Clear();
//Store your complex json on a shared preference
Preferences.Set("Data", jsonstr);
await Shell.Current.GoToAsync(nameof(DetailsPage));
}
On the details page where you retrieve your data you can have the following code:
bool hasKey = Preferences.ContainsKey("Data");
var content = Preferences.Get("Data", string.Empty);
Details details = hasKey ? JsonConvert.DeserializeObject<Model>(content) : null;
Related
I have a new Xamarin Forms 5 app and I'm having trouble with data binding.
First, I display a message that tells the user how many items are in his list. Initially, this is 0. It's displayed by DisplayMessage property of the view model.
Then, the Init() method gets called and once the API call is finished, there are some items in MyList. I put break points to make sure that the API call works and I end up with some data in MyList property.
Because I change the value of message in my Init() method, I was expecting the message to change and display the number of items in the list but it's not changing even though I have some items in MyList.
I created a new ViewModel that looks like this:
public class MyViewModel : BaseViewModel
{
public List<MyItem> MyList { get; set; } = new List<MyItem>();
string message = "You have no items in your list... ";
public string DisplayMessage
{
get => message;
set
{
if(message == value)
return;
message = value;
OnPropertyChanged();
}
}
public async void Init()
{
var data = await _myService.GetData();
if(data.Count > 0)
message = $"You have {data.Count} items in your list!";
MyList = data;
}
}
My MainPage code behind looks like this:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainPage : ContentPage
{
MyViewModel _vm;
MainPage()
{
InitializeComponent();
_vm = new MyViewModel();
this.BindingContext = _vm;
}
protected override void OnAppearing()
{
base.OnAppearing();
_vm.Init();
}
}
I didn't change anyting in the base view model, except I added my service and it looks like this:
public class BaseViewModel : INotifyPropertyChanged
{
public IMyApiService MyApi => DependencyService.Get<IMyApiService>();
bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
string title = string.Empty;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
I'd appreciatae someone telling me where my mistake is. Thanks.
Without seeing the Xaml, I can't 100% answer, but here are a couple of things I see:
You are setting the "message" through the field, not the property. Since you are setting the field directly the OnPropertyChanged event isn't firing so the UI isn't getting notified that the value has changed.
I am guessing you are binding "MyList" to some sort of CollectionView or something? If it's a readonly view, using a List is ok as the collection is never updated. However, if you plan on adding or removing items at runtime, it needs to be an "ObservableCollection" for the same reason as above, the UI isn't notified of new items in a List, but an ObservableCollection will notify the UI of changes to it, so it can update.
Is what Jason mentions above in his comment. The MyList property should be setup like the other properties with the OnPropertyChanged.
I'm trying to understand the pattern to use in Xamarin Forms when a page gets its initial data from a web API.
The page is tied to a ViewModel. Let's use this simple example:
public class DataFeedViewModel : BaseViewModel
{
public DateFeedViewModel()
{
Title = "My Feed";
}
public List<FeedItem> Feed { get; set; }
}
The DataFeedViewModel is bound to the page:
public MainPage()
{
InitializeComponent();
this.BindingContext = new DataFeedViewModel();
}
As I understand it, I use the OnAppearing() method to fetch my initial set of data from the backend API:
protected override async void OnAppearing()
{
base.OnAppearing();
var result = await _myApiService.GetFeed();
// What's next? Do I simply do the following?
// new DataFeedViewModel
// {
// Feed = result
// }
}
Also a second but very important question is whether this pattern is the recommended approach.
As I learn about Xamarin and .NET Maui, I understand, the trend is to go from an event driven model to a more MVVM command driven approach.
I'm a bit confused about how to use a ViewModel to tap into these life cycle methods such as OnAppearing().
create an Init method on your VM
public class DataFeedViewModel : BaseViewModel
{
public DateFeedViewModel()
{
Title = "My Feed";
}
public List<FeedItem> Feed { get; set; }
public async void Init()
{
Feed = await _myApiService.GetFeed();
}
}
and then have your page call it
private DataFeedViewModel VM { get; set; }
public MainPage()
{
InitializeComponent();
this.BindingContext = VM = new DataFeedViewModel();
}
protected override async void OnAppearing()
{
base.OnAppearing();
await VM.Init();
}
I have a page and a popup page.
public partial class PageA
{
public PageAViewModel vm;
public PageA()
{
BindingContext = vm = new PageAViewModel();
InitializeComponent();
}
public partial class PageAViewModel
{
public int Field1;
public int Field2;
public int Field3;
public async Task OpenPopup()
{
PopupA popup = new PopupA();
await PopupNavigation.Instance.PushAsync(popup);
}
public void Method1() { }:
And
public partial class PopupA
{
public PopupViewModel vm;
public PopupA()
{
BindingContext = vm = new PopupViewModel();
InitializeComponent();
}
public partial class PopupViewModel
{
// How can I get the value of Field1, Field2 and Field3 here?
// How can I call Method1 here?
pass a reference to the VM when creating the popup
PopupA popup = new PopupA(this);
then in PopupA
public PopupA(PageAViewModel vma)
{
BindingContext = vm = new PopupViewModel(vma);
InitializeComponent();
}
then in PopupViewModel
public PopupViewModel(PageAViewModel vma)
{
// now this VM has a reference to PageAViewModel
}
note that this is not a great design approach and it is deeply coupling these two classes
You can do this by adding a design pattern in your project . I use MVVM Light and in that I add a ViewModelLocator class to create a singleton pattern.
https://www.c-sharpcorner.com/article/xamarin-forms-mvvm-viewmodel-locator-using-mvvm-light/
Following the link and then you can write
var xyz = App.ViewModelLocator.YourViewModel.YourPublicProperty;
I am working on Xamarin forms app and I have an instance of the ViewModel data of my current page deserialised to its correct type in my Base ViewModel. My Base ViewModel also implements INotifiyPropertyChangedInterface. The view model data that has been serialized contains the properties with the correct data.
I was expecting it to bind automatically since the INotifiyPropertyChangedInterface is implemented. But nothing happens. Do I need to do anything further? If you need any further information to help me, please ask. There was not much code that I could paste in except the deserializing the json.
Your class should look something like this:
public class Person : INotifyPropertyChanged
{
private string nameValue = string.Empty;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get
{
return nameValue;
}
set
{
if (value != nameValue)
{
nameValue = value;
NotifyPropertyChanged();
}
}
}
// This method should be called by the Set accessor of each property.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Notice how it implements the INotifyPropertyChanged interface and calls the right method whenever a value is set. Failing to do so, will cause the UI not to be updated.
Now to bind your value, do this in XAML, somewhere on your page: <Label Text="{Binding Name}" />. Lastly, you have to set the BindingContext property of your page, like this:
public MyPage : ContentPage
{
public MyPage()
{
var person = new Person();
person.Name = "Bert";
BindingContext = person;
person.Name = "Ernie";
}
}
To take out the boilerplate code and call the PropertyChanged method on each property, have a look at the PropertyChanged.Fody plugin.
I have a sample windows phone 7 project where I test some MVVM stuff, however I came across a problem.
My code looks like this:
This is from my View which is a MainPage:
<Grid>
<ListBox x:Name="list" ItemsSource="{Binding _reviews}"/>
</Grid>
This is code behind for the View:
public MainPage()
{
this.Loaded += MainPage_Loaded;
// Line below makes list show what it is supposed to show
// list.ItemsSource = (DataContext as MainPageVM)._reviews;
DataContext = new MainPageVM();
InitializeComponent();
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
// DataContext is set to the right object!
var obj = list.DataContext;
}
Code for ViewModel
class MainPageVM
{
public ObservableCollection<Review> _reviews { get; set; }
public MainPageVM()
{
_reviews = GetReviews();
}
private ObservableCollection<Review> GetReviews()
{
ObservableCollection<Review> reviews = new ObservableCollection<Review>();
reviews.Add(new Review() { User = "Lol", Text = "Cool", Country = "UK"});
reviews.Add(new Review() { User = "misterX", Text = "aWESCOM APP", Country = "USA"});
reviews.Add(new Review() { User = "meYou", Text = "The best", Country = "UK"});
return reviews;
}
And here is my model:
class Review
{
public string Text { get; set; }
public string User { get; set; }
public string Country { get; set; }
}
Could you please point out where is the error and why I am able to set the ItemSource in code behind, but not via binding in XAML
The problem is that your view model class does not implement the INotifyPropertyChanged interface and you are not raising the PropertyChanged event, so the view does not know that the property you are binding to has changed.
If you're not sure about how to implement this interface, take a look at this post on Silverlight Show.
UPDATE: For most properties the above is true, however, in this instance because it's an ObservableCollection it's not necessary. However, because your view model class isn't public the view can't bind to it. Do you see any binding errors in the Output window whilst debugging?