Binding to a Xamarin Listview via web service - xamarin

I am trying to launch a interface method and bind it to a Xamarin list view but I am having some trouble. My interface is below
readonly string url = "http://myinternaliis/api/";
readonly IHttpService httpService;
public ApiClient(IHttpService httpService)
{
this.httpService = httpService;
}
public Task<List<JobsList>> GetJobs() => httpService.Get<List<JobsList>>($"{url}job");
I am trying to bind it to my list view as such please correct me if this is wrong. Should I be creating a collection of some description
public partial class JobsPage : ContentPage
{
readonly string url = "http://myinternaliis/api/";
public IHttpService httpService;
public IApi FuleApiClient;
public JobsPage ()
{
InitializeComponent ();
FuelApiClient _client = new FuelApiClient(httpService);
this.JobListing.ItemsSource = _client.GetJobs();
}

You need to await your task.
public partial class JobsPage : ContentPage
{
readonly string url = "http://myinternaliis/api/";
public IHttpService httpService;
public IApi FuleApiClient;
public JobsPage ()
{
InitializeComponent ();
FuelApiClient _client = new FuelApiClient(httpService);
SetItemSource();
}
private Task SetItemSource()
. {
. JobListing.ItemsSource = await _client.GetJobs();
}
}

Related

ItemsSource doesn't show/bind when ObservableCollection is filled before loading the page

I have a carouselview, in that view I have an ObservableCollection binded as an itemssource. I am able to bind the collection and it would show when I execute the viewmodel's command in the OnAppearing event.
Code that works:
Second Page
public partial class SecondPage : ContentPage
{
public Coll(bool hard, string subject)
{
InitializeComponent();
var vm = (DataSelectionViewModel)BindingContext;
vm.Hard = hard;
vm.Subject = subject;
/* had to set "hard" and "subject" here again, otherwise data won't load */
}
protected override async void OnAppearing()
{
var vm = (DataSelectionViewModel)BindingContext;
base.OnAppearing();
await vm.LoadData.ExecuteAsync().ConfigureAwait(false);
}
}
The viewmodel for second page
public class DataSelectionViewModel : BaseViewModel
{
private string subject;
public string Subject { get => subject; set => SetProperty(ref subject, value); }
private bool hard;
public bool Hard { get => hard; set => SetProperty(ref hard, value); }
public ObservableCollection<Items> FilteredData { get; set; }
public UserSelectionViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
LoadData= new AsyncAwaitBestPractices.MVVM.AsyncCommand(FilterData);
FilteredData = new ObservableCollection<Items>();
}
public async Task FilterData()
{
FilteredData.Clear();
var filtereddata = await _dataStore.SearchData(Hard, Subject).ConfigureAwait(false);
foreach (var data in filtereddata)
{
FilteredData.Add(data);
}
}
}
First Page where second page gets Hard and Subject values
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (BaseViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
vm.Subject = vm.Subject.ToLower();
await Navigation.PushAsync(new SecondPage(vm.Hard, vm.Subject));
}
So I want to change my code so that if I press the button on the first page, data instantly starts to filter and add to the ObservableCollection and when it's finished, then navigate to the second page. However if I try to load it to the BaseViewModel and then get the data from the second viewmodel it won't show the data.
Code that doesn't work:
Second Page
public partial class SecondPage : ContentPage
{
public SecondPage()
{
InitializeComponent();
}
}
The viewmodel for second page
public class DataSelectionViewModel : BaseViewModel
{
public ObservableCollection<Items> FilteredData { get; set; }
public UserSelectionViewModel()
{
FilteredData = new ObservableCollection<Items>();
}
}
BaseViewModel
public class BaseViewModel : INotifyPropertyChanged
{
private string subject;
public string Subject { get => subject; set => SetProperty(ref subject, value); }
private bool hard;
public bool Hard { get => hard; set => SetProperty(ref hard, value); }
public ObservableCollection<Items> FilteredData { get; set; }
/* BaseViewModel has implementation of SetProperty */
}
First Page where second page gets Hard and Subject values
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (BaseViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
vm.Subject = vm.Subject.ToLower();
}
First Page viewmodel
public class FirstPageViewModel : BaseViewModel
{
public IAsyncCommand MehetButtonClickedCommand { get; }
readonly IPageService pageService;
readonly IFeladatokStore _feladatokStore;
public FeladatValasztoViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
ButtonClickedCommand = new AsyncCommand(ButtonClicked);
pageService = DependencyService.Get<IPageService>();
}
private async Task ButtonClicked()
{
await FilterData();
await pageService.PushAsync(new SecondPage());
}
private async Task FilterData()
{
FilteredData.Clear();
var datas = await _dataStore.SearchData(Subject, Hard).ConfigureAwait(false);
foreach (var data in datas)
{
FilteredData.Add(data);
}
}
So basically this gives a null exception error. I also tried giving the ObservableCollection as an argument for SecondPage(ObservableCollection x) and that did work, but because I had to make another ObservableCollection for it and copy from one to another it stopped being async and froze for a couple of seconds. So my question is how can I make this async?
To avoid delay, build the new collection in a private variable. Then set the property to that variable:
// Constructor with parameter
public SomeClass(IList<Items> data)
{
SetFilteredDataCopy(data);
}
public ObservableCollection<Items> FilteredData { get; set; }
private void SetFilteredDataCopy(IList<Items> src)
{
var copy = new ObservableCollection<Items>();
foreach (var item in src)
copy.Add(item);
FilteredData = copy;
//MAYBE OnPropertyChanged(nameof(FilteredData));
}

How can I pass a bound value from one class to another in Xamarin?

I have a class that looks like this. I would like to create an Info class inside of it and have the value of the text in Info bound to the view model (_vm.A). I know how to pass a string into the class Info, but how can I pass a bound value that will change over time.
public class InfoPage
{
private readonly InfoViewModel _vm;
public InfoPage() : base()
{
BindingContext = _vm = new InfoViewModel();
var info = new Info(_vm.A);
}
public ChangeValueX() {
_vm.A = "XXX";
}
public ChangeValueY() {
_vm.A = "YYY";
}
}
public Info(??? ???)
{
var Label1 = new Label()
.Bind(Label.TextProperty, ???, source: this);
}
This is just a simple example as there is much more to my code.
Can someone tell me how I can pass a bound value to class Info so when the bound value in the ViewModel changes then the text changes. As I don't know how to do it I just used question marks for now.
Note the ViewModel looks like this:
private string _a;
public string A{ get => _a; set => SetProperty(ref _a, value); }
In your case you could use BindableProperty
in Info class
public class Info:BindableObject
{
public static readonly BindableProperty StringValueProperty =
BindableProperty.Create("StringValue", typeof(string), typeof(Info),string.Empty);
public string StringValue
{
get { return (string)GetValue(StringValueProperty); }
set { SetValue(StringValueProperty, value); }
}
public Info()
{
Label label = new Label();
label.SetBinding(Label.TextProperty, new Binding("StringValue", source: this));
}
}
in page
Info info = new Info();
info.SetBinding(Info.StringValueProperty, new Binding("xxx", source: this));

How can I access the value of a field in ViewModelA from another ViewModel that is called by ViewModelA

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;

How can I access a property of a page in a ViewModel?

I have this code that creates a ViewModel for the page. However in the Viewmodel I want to access the property correctButtonPressed but it's not available.
How can I access this?
public partial class PhrasesFrame : Frame
{
public int correctButtonPressed;
public PhrasesFrameViewModel vm = new PhrasesFrameViewModel();
public PhrasesFrame() {
InitializeComponent();
}
private string abc()
{
var a = correctButtonPressed;
}
}
public class PhrasesFrameViewModel : ObservableProperty
{
private ICommand bButtonClickedCommand;
public ICommand BButtonClickedCommand
{
get
{
return bButtonClickedCommand ??
(bButtonClickedCommand = new Command(() =>
{
// Next line gives an error "use expression body for accessors" "use expression body for properties.
correctButtonPressed = 123;
}));
}
}
}
Expose it as a property on the view model and have the view access it.
public partial class PhrasesFrame : Frame {
public PhrasesFrameViewModel vm = new PhrasesFrameViewModel();
public PhrasesFrame() {
InitializeComponent();
}
private string abc() {
//View can access correctButtonPressed via the view model.
var a = vm.correctButtonPressed;
}
}
public class PhrasesFrameViewModel : ObservableProperty {
public int correctButtonPressed;
private ICommand bButtonClickedCommand;
public ICommand BButtonClickedCommand {
get {
return bButtonClickedCommand ??
(bButtonClickedCommand = new Command(() => {
correctButtonPressed = 123;
}));
}
}
}

Xamarin Forms - Update UI element from itself doesn't work

I'm currently working on Map UI update.
I have a public List<string> PolylineAddressPoints which has a setter where a method is called. The method, which is called, updates public List<GeoPosition> PolylineCoordinates.
When the public List<GeoPosition> PolylineCoordinates has been update, then the renderer is updating the UI of my map.
However, it only works if I edit the list from the MainPage.xaml.cs.
I search during a long moment on the web why my map want not update by itself without find any solution. Does the elements needs to be updated from the UI thread?
My main goal is just to update it directly from the list..
My customMap:
public class CustomMap : Map
{
public static readonly BindableProperty PolylineAddressPointsProperty =
BindableProperty.Create(nameof(PolylineAddressPoints), typeof(List<string>), typeof(CustomMap), new List<string>(), BindingMode.TwoWay);
public List<string> PolylineAddressPoints
{
get { return (List<string>)GetValue(PolylineAddressPointsProperty); }
set
{
SetValue(PolylineAddressPointsProperty, value);
generatePolylineCoordinates(value);
}
}
public static readonly BindableProperty PolylineCoordinatesProperty =
BindableProperty.Create(nameof(PolylineCoordinates), typeof(List<GeoPosition>), typeof(CustomMap), new List<GeoPosition>(), BindingMode.TwoWay);
public List<GeoPosition> PolylineCoordinates
{
get { return (List<GeoPosition>)GetValue(PolylineCoordinatesProperty); }
set { SetValue(PolylineCoordinatesProperty, value); }
}
private async void generatePolylineCoordinates(List<string> value)
{
this.PolylineCoordinates = await GeneratePolylineCoordinates(value);
MessagingCenter.Send<App>((App)Application.Current, "PolylineUpdated");
}
}
I tried using the MessagingCenter but it doesn't works.. It look like the thread is cancel, I said that because I don't see the debug as well.. I don't understand what is happening..
There is the MainPage.xaml.cs:
public partial class MapPolylinePage : ContentPage
{
public List<string> AddressPointList { get; set; }
public MapPolylinePage()
{
base.BindingContext = this;
MessagingCenter.Subscribe<App>((App)Application.Current, "PolylineUpdated", (sender) =>
{
Debug.WriteLine("--DEBUG ADDRESS--");
foreach (string item in MapTest.PolylineAddressPoints)
{
Debug.WriteLine("Adress => [{0}]", item);
}
Debug.WriteLine("--DEBUG ADDRESS--");
Debug.WriteLine("--DEBUG POSITION--");
foreach (GeoPosition item in MapTest.PolylineCoordinates)
{
Debug.WriteLine("GeoPosition => [{0}/{1}]", item.Latitude, item.Longitude);
}
Debug.WriteLine("--DEBUG POSITION--");
});
AddressPointList = new List<string>()
{
"Le Mans, 72100, France",
"Arnages, 72230, France",
"Allonnes, 72700, France"
};
InitializeComponent();
}
}
and the MainPage.xaml:
<ContentPage.Content>
<local:CustomMap x:Name="MapTest" PolylineAutoUpdate="True" PolylineAddressPoints="{Binding AddressPointList}" PolylineColor="Aqua" PolylineWidth="5"
VerticalOptions="Fill" HorizontalOptions="Fill"/>
</ContentPage.Content>
Thank in advance !

Resources