I am trying to consume an interface but I am having some difficulty here.
I am trying to set it to a xamrin list view in behind a content page
public class xxxApiClient : IApi
{
readonly string url = "http://localhost:81/ ";
readonly IHttpService httpService;
public xxxApiClient(IHttpService httpService)
{
this.httpService = httpService;
}
public Task<List<JobsList>> GetJobs() => httpService.Get<List<JobsList>>($"{url}JobsLists");
}
How ever I am not to sure how I cosume getjobs correclty I am trying the following
public partial class JobsPage : ContentPage ,xxxWC.Interface.IApi
{
public xxxWC.Interface.IApi api = new ful;
public JobsPage ()
{
InitializeComponent ();
}
private Task SetItemSource()
. {
. JobListing.ItemsSource = FuelAp
}
How do I use the get jobs correctly above in the method setItemSource?.
The bit I am having hard time to understand is here.
How do I call the base GetJobs method I have already created in API Client.
Task<List<JobsList>> IApi.GetJobs()
{
throw new NotImplementedException();
}
private Task SetItemSource()
{
JobListings.ItemsSource =await GetJobs();
}
}
Edit 2
Ok based on suggestions below I updated My Code as such
IHttpService httpService;
xxxApiClient _api = newxxxApiClient(httpService);
public JobsPage ()
{
InitializeComponent ();
}
private Task SetItemSource()
{
JobListings.ItemsSource =await GetJobs();
}
But i get the below error
Severity Code Description Project File Line Suppression State
Error CS0236 A field initializer cannot reference the non-static
field, method, or property
'JobsPage.httpService' xxxCallManagmentAppMobile C:\Work\xxxCallAppDev\XamForms\xxxCallManagmentApp\xxxCallManagmentAppMobile\FuelCallManagmentAppMobile\Views\JobsPage.xaml.cs 17 Active
Can someone explain why
Edit 3
Ok i got a bit further but still having some issues. as the main method is not awaited how do I call set SetItemSource.
xxxApiClient _api ;
public JobsPage ()
{
InitializeComponent ()
SetItemSource();
}
private async Task SetItemSource()
{
JobListings.ItemsSource = await client.GetJobs();
}
Assuming that IApi has been mapped to xxxApiClient implementation
Try resolving the service using the DependencyService so that it is available to be used in the view
public partial class JobsPage : ContentPage {
public readonly IApi client;
public JobsPage () {
InitializeComponent ();
client = DependencyService.Get<IApi>();
}
private async Task SetItemSource() {
JobListing.ItemsSource = await client.GetJobs();
//...
}
}
As for calling the SetItemSource, it is async so should be awaited. That can't be done in the constructor.
Consider creating a event that can be raised and its handler used to await the desired behavior.
private event EventHandler loadingData = delegate { };
private async void onLoadingData(object sender, Eventargs args) {
JobListing.ItemsSource = await client.GetJobs();
}
Full code
public partial class JobsPage : ContentPage {
public readonly IApi client;
public JobsPage () {
InitializeComponent ();
//resolving client
client = DependencyService.Get<IApi>();
//subscribing to event
loadingData += onLoadingData;
//raising event
loadingData(this, EventArgs.Empty);
}
private async Task SetItemSource() {
JobListing.ItemsSource = await client.GetJobs();
//...
}
private event EventHandler loadingData = delegate { };
private async void onLoadingData(object sender, Eventargs args) {
JobListing.ItemsSource = await client.GetJobs();
}
}
Although a custom event was created, you could just as easily used on of the event/eventhandler of the view.
All of that code should actually live inside of a view model and then bound to the view in a binding context.
Related
I am lost in this one, i want my Viewmodel to use a event delegate so i can subscribe to it, open some dialog and wait for the dialog result. Later the ViewModel should do whatever it wants with the dialog result.
Here is how i implemented it (resumed code):
public class MyViewModel()
{
public delegate TributaryDocument SearchDocumentEventHandler();
public event SearchDocumentEventHandler SearchDocument;
//Command for the search button
public CommandRelay SearchDocumentCommand { get; set; }
//Document that i found in the dialog.
public TributaryDocument Document { get; set; }
public MyViewModel()
{
SearchDocumentCommand = new CommandRelay(DoSearchDocument);
}
//The command execution
public void DoSearchDocument()
{
//Event used here !
Document = SearchDocument?.Invoke();
}
}
public class MyUIControl : UserControl
{
public MainWindow MainWindow { get; }
public MyUIControl()
{
MainWindow = Application.Current.Windows[0] as MainWindow;
DataContextChanged += MyUIControl_DataContextChanged;
}
private void MyUIControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var modelView = (MyViewModel)DataContext;
modelView.SearchDocument += MyUIControl_SearchDocument;
}
private TributaryDocument MyUIControl_SearchDocument()
{
//Dont know what to do here... i am lost on this part.
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
}
//The signature for MainWindow.ShowDialog
public async Task<object> ShowDialog(object dialog)
{
return await DialogHost.Show(dialog, "MainDialog");
}
MyDocumentSearcherDialog is just a dialog where i search and return a TributaryDocument object.
The problem to my understanding comes from this part (since i cant compile it):
private TributaryDocument MyUIControl_SearchDocument()
{
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
I cant use await without changing the method signature to async. If i change it to async then i must return a Task<TributaryDocument> and change the event delegate:
public delegate Task<TributaryDocument> SearchDocumentEventHandler();
//On MyUIControl
private Task<TributaryDocument> MyUIControl_SearchDocument()
{
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
//On MyViewModel
public async void DoSearchDocument()
{
//Event used here !
Document = await Task.Run(async () => await SearchDocument?.Invoke());
}
If i do this i get the following exception:
Additional information: The calling thread must be STA, because many
UI components require this.
It seems like all you need to do is to remove the Task.Run (there is no need to Offload to another thread in this situation). The Task.Run will definitely give you a STA Thread Exception if you are doing UI work from within.
However, in short the Async and Await Pattern will create a continuation with the current SynchronisationContext, so there is no need to worry about it.
public async void DoSearchDocument()
{
await SearchDocument?.Invoke();
}
Note : Since this is an event, it's about the only place it's OK to use async void.
I'm developing an application with windows-10-t platform on raspberry-pi3. The application has several pages and listens GPIO ports asyncrhonously in the background. It collects data from GPIO and sends to the WCF-Service, after a bit the UI should be updated by the data coming from the WCFService. I've also tried using Tasks, Dispatcher.Invoke etc. but nothing worked properly. I can collect data coming from GPIO but cannot update UI. What am I doing wrong?
Here is the background GPIO listener class with static variables (I'm listening GPIO in other pages too.):
public sealed class GPIO{
private static MainPage mainpage;
public static event EventHandler ProgressUpdate;
public static void InitGPIO(MainPage sender)
{
mainpage = sender;
DataPin.DebounceTimeout = TimeSpan.FromMilliseconds(50);
DataPin.ValueChanged += DataPin_ValueChanged;
}
public static void DataPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs e)
{
if (e.Edge == GpioPinEdge.FallingEdge)
{
Task.Run(() => AddData(0));
}
}
public static async void AddData(int prm_Data)
{
// WCF-Service Operation
await Service.wsClient.GPIOValueAddition(prm_Data);
GPIO.ProgressUpdateOperation();
}
private static void ProgressUpdateOperation()
{
mainpage.GPIO_ProgressUpdate(typeof(GPIO), new EventArgs());
}
}
And here is the page that contains the UI to be updated:
public sealed partial class MainPage : Page
{
public MainPage()
{
GPIO.InitGPIO(this);
GPIO.ProgressUpdate += GPIO_ProgressUpdate;
}
public void GPIO_ProgressUpdate(object sender, EventArgs e)
{
// WCF-Service Operation
service_data = (int)Service.wsClient.GetDataFromServicetoUpdateUI(parameter).Result;
// UI-update
txtUpdate.Text = service_data.ToString();
}
}
EDIT: I forgot to add the exception. "The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))" exception is thrown at AddData function called in DataPin_Valuechanged.
I found the solution in here : https://stackoverflow.com/a/27698035/1093584
Here is the new update-UI function :
public void GPIO_ProgressUpdate(object sender, EventArgs e)
{
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
service_data = await Service.wsClient.GetDataFromServicetoUpdateUI(parameter);
// UI-update
txtUpdate.Text = service_data.ToString();
});
}
I'm trying to call an async function but when I try and do it it has the red line underneath it. I want the function to be called when the page is opened, thanks in advance.
public partial class Home : ContentPage
{
public class GoogleProfile
{
public string Id { get; set;}
}
public Home()
{
InitializeComponent();
}
protected override async void OnAppearing()
{
await Check(/*What do i put in here*/);
}
public async Task Check(GoogleProfile googleprofile)
{
if (String.IsNullOrEmpty(googleprofile.Id))
{
}
else {
await Navigation.PushAsync(new LoginPage());
}
}
}
how would i call this? Sorry im new to C# and xamarin
You should make your async calls in ContentPage.OnAppearing(). The OnAppearing() event will be called, as the name suggest, right when your page is being displayed. This is the expected behavior by the user. Also note that I changed your Check() method to return Task because, if you are able to edit the method signature, always try to change the return type of async methods from void to Task:
public partial class LoginPage : ContentPage {
public LoginPage() {
InitializeComponent();
}
protected override async void OnAppearing() {
await Check(/* Add code here to get your GoogleProfile object */);
}
public async Task Check(GoogleProfile googleprofile) {
var ID = googleprofile.Id;
if (string.IsNullOrEmpty(ID)) {
return;
} else {
await Navigation.PushAsync(new Home());
}
}
}
Technically, if you are dead set to not use OnAppearing() you could do the Check() before pushing your LoginPage, though without seeing more code, that would seem like it would defeat the purpose of the LoginPage.
In my project I use a manager to control a plugin. The main idea is that this plugin must work only in single thread in multythreads WPF application. There is only one instance of plugin in PluginController.
So when I call Start method: it stops plugin (if running) and start it with new argument. Few times a second plugin notificate caller about it's state, and ViewModel shows it in the WPF window.
When I call method Start some times one after one, i see that the previous instance of ViewModel is not destroyed, but only sleeps after Stop. And it calls Update method as god as a new one instance. So my interface twiches becouse two instances are updating it's state. In log is see alternately lines from first one and second one.
But when I call Start(...) then Stop() and then Start(...) again everything works fine.
So
SomeManager.Start(...);
SomeManager.Start(...);
works with errors. And
SomeManager.Start(...);
SomeManager.Stop();
SomeManager.Start(...);
works fine. Can anybody explain me my mistake?
Down lied simplified code.
public static SomeManager
{
public static void Start(SomeArg arg)
{
Stop(); // forgotten code
var vm = GetMainPageVM();
vm.SomeVM = new SomeViewModel(arg);
vm.SomeVM.StartCommand.Execute(null);
}
public static void Stop()
{
var vm = GetMainPageVM();
if (vm.SomeVM != null)
{
vm.SomeVM.Stop();
vm.SomeVM.Dispose();
vm.SomeVM = null;
}
}
}
public sealed SomeViewModel : ViewModelBase, IDisposable
{
private readonly Guid _guid = Guid.NewGuid();
private IPlugin _plugin;
private SomeArg _arg;
public ICommand StartCommand {get; }
public CancellationTokenSource Source {get; }
public SomeViewModel(SomeArg arg)
{
this._arg = arg;
this._plugin = PluginController.GetPluginByName("SomePlugin");
StartCommand = new RelayCommand(StartAsync);
}
~SomeViewModel()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{ ... }
private async Task StartAsync()
{
var progress = new Progress<ISomeProgress>(Update);
try
{
await StartImplementationAsync(progress).ConfigureAwait(false);
}
catch (Exception e) { ... }
}
private async Task StartImplementationAsync(Progress<ISomeProgress> progress)
{
var result = await this._plugin.startAsync(
this._arg,
progress,
this.Source.Token
).ConfigureAwait(false);
}
public void Stop()
{
this._plugin.Stop();
}
private void Update() {log.Debug($"this._guid" ....); }
}
public sealed SomePlugin: IPlugin
{
public async Task<SomeResult> StartAsync(SomeArg args, IProgress<SomeProgress>, CancellationToken cancellationToken)
{ ... }
public void Stop() { ... }
}
UPDATE: I think the problem in simple words is : how to correctly cancel async operation in IDisposable object in normal case with CancellationTokenSource.Cancel() and in unnormal case when Dispose() or Finalizer is called
I have a viewModel with async Task. I don't now how to test it.
public class MyViewModel : BindableBase
{
public MyViewModel()
{
this.PropertyChanged += MyViewModel_PropertyChanged;
}
private void MyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Action action = async () => await DoSomething();
action();
}
public const string BeforeKey = "before";
public const string AfterKey = "After";
public string Status { get; private set; } = BeforeKey;
public async Task DoSomething()
{
await Task.Delay(3000);
Status = AfterKey;
}
string bindagleProp;
public string BindagleProp
{
get { return bindagleProp; }
set { SetProperty(ref bindagleProp, value); }
}
}
Here is my test:
[TestMethod]
public async Task TestMyViewModel()
{
MyViewModel viewModel = new MyViewModel();
Assert.AreEqual(viewModel.Status, MyViewModel.BeforeKey, "before check");
viewModel.BindagleProp = "abc";
Assert.AreEqual(viewModel.Status, MyViewModel.AfterKey, "after check");
}
The test failed because it's not waiting to completion of the task.
I DON'T want to use Task.Delay in the unit test, because it's not safety. DoSomething method can has unknown duration time.
Thank you for any help.
Edit:
In fact, The issue is not specific for MVVM, but for any async event handler.
For example:
// class with some logic, can be UI or whatever.
public class MyClassA
{
Size size;
public Size Size
{
get { return size; }
set
{
size = value;
SizeChanged?.Invoke(this, EventArgs.Empty);
}
}
public event EventHandler SizeChanged;
}
// this class uses the MyClassA class.
public class MyCunsomerClass
{
readonly MyClassA myClassA = new MyClassA();
public MyCunsomerClass()
{
myClassA.SizeChanged += MyClassA_SizeChanged;
}
public string Status { get; private set; } = "BEFORE";
private async void MyClassA_SizeChanged(object sender, EventArgs e)
{
await LongRunningTaskAsync();
Status = "AFTER";
}
public async Task LongRunningTaskAsync()
{
await Task.Delay(3000);
///await XYZ....;
}
public void SetSize()
{
myClassA.Size = new Size(20, 30);
}
}
Now, I want to test it:
[TestMethod]
public void TestMyClass()
{
var cunsomerClass = new MyCunsomerClass();
cunsomerClass.SetSize();
Assert.AreEqual(cunsomerClass.Status, "AFTER");
}
The test failed.
I asked Stehphen Cleary [The famous professor of asynchronous], and he answered me:
If by "async event handler" you mean an async void event handler,
then no, those aren't testable. However, they are often useful in a UI
application. So what I usually end up doing is having all my async
void methods be exactly one line long. They all look like this:
async void SomeEventHandler(object sender, EventArgsOrWhatever args)
{
await SomeEventHandlerAsync(sender, args);
}
async Task SomeEventHandlerAsync(object sender, EventArgsOrWhatever args)
{
... // Actual handling logic
}
Then the async Task version is unit testable, composable, etc. The
async void handler isn't, but that's acceptable since it no longer
has any real logic at all.
Thanks Stephen! Your idea is excellent!
Ok So first of all, I would move the worker out to an other class and make an interface to it. So that when I run the test I can inject another worker!
public class MyViewModel : BindableBase
{
private IWorker _worker;
private readonly DataHolder _data = new DataHolder(){Test = DataHolder.BeforeKey};
public string Status { get { return _data.Status; } }
public MyViewModel(IWorker worker = null)
{
_worker = worker;
if (_worker == null)
{
_worker = new Worker();
}
this.PropertyChanged += MyViewModel_PropertyChanged;
}
private void MyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Action action = async () => await _worker.DoSomething(_data);
action();
}
string bindagleProp;
public string BindagleProp
{
get { return bindagleProp; }
set { SetProperty(ref bindagleProp, value); }
}
}
public class DataHolder
{
public const string BeforeKey = "before";
public const string AfterKey = "After";
public string Status;
}
public interface IWorker
{
Task DoSomething(DataHolder data);
}
public class Worker : IWorker
{
public async Task DoSomething(DataHolder data)
{
await Task.Delay(3000);
data.Status = DataHolder.AfterKey;
}
}
Now the inject code would look something like:
[TestMethod]
public async Task TestMyViewModel()
{
TestWorker w = new TestWorker();
MyViewModel viewModel = new MyViewModel(w);
Assert.AreEqual(viewModel.Status, DataHolder.BeforeKey, "before check");
viewModel.BindagleProp = "abc";
Assert.AreEqual(viewModel.Status, DataHolder.AfterKey, "after check");
}
public class TestWorker : IWorker
{
public Task DoSomething(DataHolder data)
{
data.Status = DataHolder.BeforeKey;
return null; //you maybe should return something else here...
}
}