testing testing - 27 days ago 19
C# Question

Calling async method from ViewModel

I have a

ViewModel
, which makes use of
Commanding
. In this
ViewModel
I want to access the MediaPlugin
. With this plugin I have to call
Initialize()
. Because it uses
async
calls I have some timing problems.

This is my code:

public ICommand CameraCommand
{
get { return _cameraCommand ?? (_cameraCommand = new Command(async () => await ExecuteCameraCommand(), () => CanExecuteCameraCommand())); }
}

public bool CanExecuteCameraCommand()
{
// Check if initialized before calling properties
if (!this.initialized)
InitMedia();

if (!this.initialized || !CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
return false;
}
return true;
}

public async Task ExecuteCameraCommand()
{
// Assure that it is initialized before calling method
var file = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions {});
// ...
}

private void InitMedia()
{
CrossMedia.Current.Initialize();
this.initialized = true;
}


With this code the app crashes with


You must call Initialize() before calling any properties.

at Plugin.Media.MediaImplementation.get_IsCameraAvailable()


When I start the initialization in the constructor like with this code

public MyViewModel()
{
InitData();
}

private async Task InitData()
{
// ...
await InitMedia();
}

private async Task InitMedia()
{
await CrossMedia.Current.Initialize();
this.initialized = true;
}

public bool CanExecuteCameraCommand()
{
// Check if initialized before calling properties
if (!this.initialized)
return false;
// ...
}


CanExecuteCameraCommand()
is called before the initialization has finished. As a consequence
false
is returned and the button in the UI is disabled.

I'm testing this code in a Xamarin.Forms environment on a Windows 10 Mobile device (Windows 10 Universal).

Answer

You have not provide a Minimal, Complete, and Verifiable code example, and in particular you have not shown your implementation of the Command class. So I'm going to assume here that it's the Command class described in the Xamarin article you referenced.

The basic strategy for asynchronous initialization of your view model is this:

  1. Disable any commands that depend on the result of the initialization, clear any properties (or set to appropriate default value) that depend on the result of the initialization.
  2. Begin the asynchronous initialization.
  3. Use some continuation mechanism (i.e. don't block the thread) to wait for the asynchronous initialization to complete.
  4. When the initialization completes, re-enable previously disabled commands and update any relevant properties.

Based on the code you posted, it appears to me that the main thing missing is step #4. I.e. you're not doing anything to re-enable the previous disabled commands, even though you have a convenient place to do so:

private async Task InitMedia()
{
    await CrossMedia.Current.Initialize();
    this.initialized = true;
    _cameraCommand.ChangeCanExecute();
}

The Command.ChangeCanExecute() method raises the CanExecuteChanged event, so that controls bound to the command can be notified when the result of the CanExecute() method will be different. By calling this method when initialization is done, this should address the problem of the button remaining disabled even after initialization has completed.

One additional note: I would not call the InitMedia() method, or do any initialization at all, from the CanExecuteCameraCommand() method. Because initialization is asynchronous, it's not like you'll be able to successfully initialize before returning from that method anyway, and you've already got a call to the initialization from the constructor. The ICommand.CanExecute() implementation should be very simple, and strictly limited to checking the current state of things and returning the bool value required according to those results.

If the above does not address your question, please improve the question by providing a good MCVE, and explain in more precise details what is going wrong, what you've tried to fix it, and what specifically you are having trouble figuring out.

Comments