I separated my data service in another project and trying to use using dependency IOC. But when I do so I am getting an error that I can't set the Binding context if I have parameterized constructor. Unfortunately I couldn't find any other similar suitable answer on stack overflow to get rid of my confusion.
Error Thrown : Severity Code Description Project File Line Suppression State Error XFC0004 Missing default constructor for "CoffeeMocha.ViewModels.CoffeeDbViewModel". CoffeeMocha C:\xxxx\CoffeeMocha\CoffeeMocha\CoffeeMocha\Views\CoffeeDbPage.xaml 12
My question is that how can I use the implementation of CoffeeService(which is a different project) instance using DI in my ViewModel. And how to set Binding Context in such cases. Kindly note I am not using any MVVM provider like Prism, lite or cross. This is just a Vanilla implementations sample which I implemented using a tutorial.
My App.xaml.cs
namespace CoffeeMocha
{
public partial class App : Application
{
public App()
{
InitializeComponent();
DependencyService.Register<MockDataStore>();
DependencyService.Register<ICoffeeRequestProvider, CoffeeRequestProvider>();
DependencyService.Register<ICoffeeService, CoffeeService>();
MainPage = new AppShell();
}
protected override void OnStart()
{
}
protected override void OnSleep()
{
}
protected override void OnResume()
{
}
}
}
My View.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CoffeeMocha.Views.CoffeeDbPage"
xmlns:vm="clr-namespace:CoffeeMocha.ViewModels"
x:Name="CoffeeDataPage">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding AddCommand}"/>
</ContentPage.ToolbarItems>
<ContentPage.BindingContext>
<vm:CoffeeDbViewModel>
</vm:CoffeeDbViewModel>
</ContentPage.BindingContext>
<ContentPage.Content>
<ListView
Style="{StaticResource CoffeListStyle}"
ItemsSource="{Binding CoffeeList}"
IsPullToRefreshEnabled="True"
SelectedItem="{Binding SelectedCoffee, Mode=TwoWay}"
CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate >
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Delete"
CommandParameter="{Binding .}"
Command="{Binding Source={x:Reference CoffeeDataPage}, Path=BindingContext.DeleteCommand }"/>
</ViewCell.ContextActions>
<Frame Style="{StaticResource CoffeeFrameStyleBlue}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height ="2*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2.5*"/>
</Grid.ColumnDefinitions>
<Image Source="{Binding Image}"
Grid.RowSpan="2"
Grid.Column="0"/>
<Label Grid.Column="1"
Grid.Row="0"
FontSize="Large"
FontFamily="Bold"
TextColor="White"
Text="{Binding Name}"
Margin="0,5,0,5"/>
<Label
Grid.Column="1"
Grid.Row="1"
TextColor="White"
Margin="0,5,0,5"
Text="{Binding Description}"/>
</Grid>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
ViewModel
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
using MvvmHelpers;
using MvvmHelpers.Commands;
using System.Threading.Tasks;
using CoffeeMocha.Views;
using System.Collections.ObjectModel;
using CoffeeMocha.Models;
using CoffeeMocha.Services;
using CoffeMocha.DataService.CoffeeService;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace CoffeeMocha.ViewModels
{
public class CoffeeDbViewModel
{
ObservableRangeCollection<Coffe> _coffeeList;
public ObservableRangeCollection<Coffe> CoffeeList
{
get { return _coffeeList; }
set
{
if (_coffeeList != value)
_coffeeList = value;
OnPropertyChanged("randomList");
}
}
private Coffe _selectedCoffee;
public Coffe SelectedCoffee
{
get { return _selectedCoffee; }
set
{
if (_selectedCoffee != value)
{
_selectedCoffee = value;
Application.Current.MainPage.DisplayAlert("Selected", value.Name, "OK");
}
OnPropertyChanged("SelectedCoffee");
}
}
public AsyncCommand AddCommand { get; set; }
public AsyncCommand<Coffe> DeleteCommand { get; set; }
ICoffeeDbService coffeeDbService;
ICoffeeService coffeeService;
public CoffeeDbViewModel(ICoffeeService coffeeService)
{
AddCommand = new AsyncCommand(AddCoffeeItem);
DeleteCommand = new AsyncCommand<Coffe>(DeleteCoffeeItem);
// coffeeDbService works as expected but is in the same PCL project
CoffeeList = new ObservableRangeCollection<Coffe>();
coffeeDbService = DependencyService.Get<ICoffeeDbService>();
try
{
// Below throws error and expects Coffee service to have parmeterless constructor
// this.coffeeService = DependencyService.Get<ICoffeeService>();
this.coffeeService = coffeeService;
}catch(Exception ex)
{
var exc = ex.Message;
}
}
private async Task AddCoffeeItem()
{
await Application.Current.MainPage.Navigation.
PushAsync(new AddCoffeeItemPage(), false);
}
private async Task DeleteCoffeeItem(Coffe coffe)
{
await coffeeDbService.RemoveCoffeeAsync(coffe.Id);
}
public async Task RefreshItemsFromDB()
{
bool IsFromDB = false;
if (CoffeeList != null)
CoffeeList.Clear();
if (IsFromDB)
{
var coffeeDBList = await coffeeDbService.GetCoffee();
if (coffeeDBList != null)
CoffeeList.AddRange(coffeeDBList);
}
else
{
// From Web Service
var coffeeList = coffeeService.GetCoffee();
var coffeListResult = coffeeList?.Result;
if(coffeListResult != null)
{
//CoffeeList.AddRange(coffeListResult);
}
}
}
#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
}
}
CoffeeService in different project Added this reference in PCL
using CoffeeMocha.Commons;
using CoffeeMocha.Data.Models.Coffee;
using CoffeMocha.DataService.RequestProviders;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace CoffeMocha.DataService.CoffeeService
{
public class CoffeeService : ICoffeeService
{
ICoffeeRequestProvider requestProvider;
public CoffeeService(ICoffeeRequestProvider coffeeRequestProvider)
{
requestProvider = coffeeRequestProvider;
}
public async Task<IEnumerable<Coffee>> GetCoffee()
{
// Expecting a list of Coffee as a Response
try
{
var endPoint = BackendConstants.COFEE;
var response = await this.requestProvider.GetAsync<IEnumerable<Coffee>>(endPoint);
return response;
}
catch(Exception)
{
throw;
}
}
}
}
CoffeeRequestProvider
using CoffeeMocha.Commons;
using CoffeMocha.DataService.RequestProviders;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace CoffeMocha.DataService.RequestProviders
{
public class CoffeeRequestProvider : ICoffeeRequestProvider
{
private readonly JsonSerializerSettings jsonSerializerSettings;
public CoffeeRequestProvider()
{
jsonSerializerSettings = new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver(),
NullValueHandling = NullValueHandling.Ignore,
};
}
public async Task<TResult> GetAsync<TResult>(string uri, string token = "")
{
var baseUrl = BackendConstants.Base_Coffee_Url;
HttpResponseMessage responseMessage;
HttpClient httpClient = CreateHttpClient(baseUrl);
responseMessage = await httpClient.GetAsync(uri);
string serialized = await responseMessage.Content.ReadAsStringAsync();
TResult result = default;
result = JsonConvert.DeserializeObject<TResult>(serialized, jsonSerializerSettings);
return result;
}
private HttpClient CreateHttpClient(string baseUrl)
{
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(baseUrl);
// for real world projects Headers, versions, tokens can be added
return httpClient;
}
}
}
CoffeeDbPage Some sources suggests me to have empty constructor in code behind which I already have.
namespace CoffeeMocha.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CoffeeDbPage : ContentPage
{
CoffeeDbViewModel vm;
public CoffeeDbPage()
{
InitializeComponent();
vm = this.BindingContext as CoffeeDbViewModel;
}
protected override async void OnAppearing()
{
base.OnAppearing();
await vm.RefreshItemsFromDB();
}
}
}