Reputation: 148
I've got an issue when calling an async method in a constructor. It stops data ever showing in an ObservableCollection
even though I can see it in debug when I trace through the code. To work around the issue I have a non async version of the same method but obviously this is not ideal. How can I get my async method to work properly in a constructor?
If I call this.RefreshCarList("")
in the constructor of MainWindowViewModel
then subsequent calls to this.RefreshCarListAsync("")
(not from within the constructor) will work perfectly fine and data shows on the screen. If I only call this.RefreshCarListAsync("")
then data never shows.
public class MainWindowViewModel
{
private ObservableCollection<Car> carList;
public ICollectionView CarList { get; private set; }
private DataReceivedHandler dataReceivedHandler = new DataReceivedHandler();
public MainWindowViewModel()
{
//this.RefreshCarList(""); // Gets data on startup
this.RefreshCarListAsync("") // Doesn't show any data
this.CarList = new QueryableCollectionView(this.carList, typeof(Car));
}
public void RefreshCarList(string carBrand)
{
this.carList = this.dataReceivedHandler.GetCarList(carBrand);
}
public async Task RefreshCarListAsync(string carBrand)
{
this.carList = await this.dataReceivedHandler.GetCarListAsync(carBrand);
}
}
The view model uses a data receiver class which gets data from a service:
public class DataReceivedHandler
{
private CarDataService dataService = new CarDataService();
private List<Car> carList = new List<Car>();
private ObservableCollection<Car> carOC = new ObservableCollection<Car>();
public ObservableCollection<Car> GetCarList(string carBrand)
{
carListFromService = this.dataService.GetCarList(carBrand);
this.carOC.Clear();
foreach (ICar car in carListFromService)
this.carOC.Add(car);
return (this.carOC);
}
public async Task<ObservableCollection<Car>> GetCarListAsync(string carBrand)
{
carListFromService = await Task.Run(() => this.dataService.GetCarList(carBrand)).ConfigureAwait(false);
this.carOC.Clear();
foreach (ICar car in carListFromService)
this.carOC.Add(car);
return (this.carOC);
}
}
The service:
public class CarDataService
{
private List<Car> CarList = new CarList();
public List<Car> GetCarList(string carBrand)
{
return this.CarList;
}
}
Update:
So I've tried this as one of the answers suggested, however it still doesn't work:
public async Task Initialise()
{
try
{
await this.CarListAsync("");
this.CarList = new QueryableCollectionView(this.carList, typeof(Car));
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
this.viewModel.Initialise(); // in a different class
Upvotes: 1
Views: 288
Reputation: 169200
If the property is being set dynamically after the constructor has returned, you should implement INotifyPropertyChanged
and raise the PropertyChanged
event in your MainWindowViewModel
class:
public class MainWindowViewModel : INotifyPropertyChanged
{
private ICollectionView carList;
public ICollectionView CarList
{
get { return carList; }
private set { carList = value; NotifyPropertyChanged(); }
}
private DataReceivedHandler dataReceivedHandler = new DataReceivedHandler();
public async Task Initialise()
{
try
{
await this.CarListAsync("");
this.CarList = new QueryableCollectionView(this.carList, typeof(Car));
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Upvotes: 1
Reputation: 101483
Note that this.carList
is initialized in RefreshCarListAsync
:
this.carList = await this.dataReceivedHandler.GetCarListAsync(carBrand);
But it potentially gets initialized not right away but later, when GetCarListAsync
provides the result. Then in constructor you do not await RefreshCarListAsync
(you can't do that in constructor anyway):
this.RefreshCarListAsync("") // Doesn't show any data
Because you do not await - the next statement
this.CarList = new QueryableCollectionView(this.carList, typeof(Car));
executes (potentially) before this.carList
is initialized. So this.carList
is null when that line executes, most likely leading to your problem.
So, IF you are going to do that from constructor, you need to do something like:
private async Task Initialize() {
try {
await this.RefreshCarListAsync("") // Doesn't show any data
this.CarList = new QueryableCollectionView(this.carList, typeof(Car));
}
catch (Exception ex) {
// do something meaningful, otherwise it will go unnoticed
}
}
And call that from constructor. Or better - make it public and don't call it from constructor but let whatever uses your viewmodel to call it, since there is a chance it might be able to await it.
Upvotes: 0