Updating UI on a UWP (Raspberry Pi / Windows Iot) - c#

I'm building a C# UWP program (using MvvMLight framework) to run on a Raspberry Pi running Windows 10 Iot.
I have a method which downloads data from a SQL database via a WCF connection, I can see from debugging the WCF service is working, and the data downloads.
I'm puzzled why I can call this method two different ways, one updates the UI, one doesn't. Please can anyone explain why?
public class TagRegStep2ViewModel : ViewModelBase
{
private ObservableCollection<EmployeeName> _Employees;
private string _OnScreenInstructions;
public AsyncRelayCommand StartWizardCommand { get; private set; }
public TagRegStep2ViewModel()
{
// Initialise Messenger
Messenger.Default.Register<InitialiseStepMessage>(this, (msg) => InitialiseStep(msg.Step));
// Initialise Commands
StartWizardCommand = new AsyncRelayCommand(() => GetData(), () => true);
//Set dummy default value
OnScreenInstructions = "Default Value";
}
// I can see data is downloaded from WCF service and the ObservableCollection updates, but this isn't reflected in the UI.
public void InitialiseStep(ViewModelBase vm)
{
if (vm.GetType() == this.GetType())
{
Debug.WriteLine("InitialiseStep() called");
StartWizardCommand.TryExecute();
}
}
// Method that retrieves data from WCF service.
// When called from a relay command, it updates the ObservableCollection and UI.
// When called from a MessengerCommand it updates the ObservableCollection but not the UI. Why???
public async Task GetData()
{
Debug.WriteLine("Getting data...");
OnScreenInstructions = "Loading Employees";
await Task.Delay(25);
var emps = await LoadEmployees(); //not posted this method, but it works
Employees = new ObservableCollection<EmployeeName>(emps);
OnScreenInstructions = "Please select an employee.";
await Task.Delay(25);
Debug.WriteLine("ObservableCollection Count :" + Employees.Count);
}
public ObservableCollection<EmployeeName> Employees
{
get { return _Employees; }
set { Set(() => Employees, ref _Employees, value); }
}
public string OnScreenInstructions
{
get { return _OnScreenInstructions; }
set { Set(() => OnScreenInstructions, ref _OnScreenInstructions, value); }
}
}

Related

Easymodbus gives connection error in Quartz.net class

Friends, I am pulling data from a device with a library called EasyModbus. I want to use the Quartz library to automatically pull this data every hour. The problem is that while I can pull data normally, when I use the same code inside the class I get a connection error.
I don't have any problem pulling data in main form. I only get a connection error when using it within the Quartz class.
public class Gorev : IJob // Quartz.Net
{
string address = "10.100.135.20";
public ModbusClient modbus = new ModbusClient(); // EasyModbus
public bool ModbusConnect() // Modbus Connection
{
if (modbus.Connected == false)
{
modbus.Connect(address, 502);
return modbus.Connected; // Return True
}
else
{
modbus.Disconnect();
return modbus.Connected; // Retunn False
}
}
public Task Execute(IJobExecutionContext context)
{
int[] frekans = modbus.ReadHoldingRegisters(0x009E, 1); //Connected Error
return Task.CompletedTask;
}
}
You did not specify a parameter in the ModbusClient constructor:
public ModbusClient modbus = new ModbusClient(address, 502); // then modbus.Connect() without parameter

Task async Call not returning creates deadlock

I am very new to c# so this could be very easy. I am busy creating a xamarin forms app and need the data for the next page on the UI. Its a tabbeb page and all 3 needs the info that will be returned by the base class.
The problem I am having is that I need to call google api service for company info. Due to this I created a asynchronous call. So now the code returns due to this. Due to the tabbed page I need the data to bind to the screen and I now need to wait for the data.So basically need it to be synchronous.
I have tried everything I could find on this topic. Maybe the way I am doing this is wrong but hopefully my code will show that.
This is the Tabbed Page:
public BusinessTabbedPage(string PlaceId)
{
Children.Add(new BusinessSpecialsPage(PlaceId));
InitializeComponent();
}
This will be one of the pages on the app that calls the viewmodel
public BusinessSpecialsPage(string PlaceId)
{
BindingContext = new BusinessSpecialsPageViewModel(PlaceId);
InitializeComponent();
}
Due the 3 pages needing the same data I created a base class. This will get the data and pass everything back to the UI.
public BusinessBaseViewModel(string placeId)
{
Task<List<CompanyProfileModel>> task = GBDFromGoogle(placeId);
task.Wait();
}
public async Task<List<CompanyProfileModel>> GBDFromGoogle(string PlaceId)
{
var info = await ApiGoogle.GetGoogleCompanySelectedDetails(PlaceId);
var Companyresult = info.result;
CompanyProfileModel CompList = new CompanyProfileModel
{
ContactDetails = Companyresult.formatted_phone_number,
Name = Companyresult.name,
Website = Companyresult.website,
};
ComPF.Add(CompList);
return ComPF;
}
This is the api call which i think is adding a new task and then the process deadlocks?
public static async Task<GoogleSelectedPlaceDetails> GGCSD(string place_id)
{
GoogleSelectedPlaceDetails results = null;
var client = new HttpClient();
var passcall = "https://maps.googleapis.com/maps/api/place/details/json?placeid=" + place_id + "&key=" + Constants.GoogleApiKey;
var json = await client.GetStringAsync(passcall);
//results = await Task.Run(() => JsonConvert.DeserializeObject<GoogleSelectedPlaceDetails>(json)).ConfigureAwait(false);
results = JsonConvert.DeserializeObject<GoogleSelectedPlaceDetails>(json);
return results;
}
I need the process not to deadlock. It needs to wait for the tasks to be done so that I can return the data to the screen.
Task.Wait will block the current thread so you should never use it in Xamarin applications and even less in the constructor, otherwise your application will freeze until you receive some data which might never happen if the service is down or the user loses connection.
Instead of calling the data in your ViewModel's constructor you could initialize it on the Appearing event of your ContentPage view.
To do that, you could create a custom behaviour or even better, you can use the following library which already does this for you:
Using NuGet: Install-Package Behaviors.Forms -Version 1.4.0
Once you have installed the library, you can use the EventHandlerBehavior to wire events to ViewModel commands, for example:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:behaviors="clr-namespace:Behaviors;assembly=Behaviors"
xmlns:viewModels="clr-namespace:YourApp.ViewModels"
Title="Business Page"
x:Class="YourApp.Views.BusinessPage">
<ContentPage.BindingContext>
<viewModels:BusinessViewModel />
</ContentPage.BindingContext>
<ContentPage.Behaviors>
<behaviors:EventHandlerBehavior EventName="Appearing">
<behaviors:InvokeCommandAction Command="{Binding AppearingCommand}" />
</behaviors:EventHandlerBehavior>
</ContentPage.Behaviors>
[...]
The ViewModel:
public BusinessBaseViewModel(string placeId)
{
AppearingCommand = new Command(Appearing);
PlaceId = placeId;
}
public ICommand AppearingCommand { get; private set; }
public string PlaceId { get; private set; }
private ObservableCollection<CompanyProfileModel> _googleGbd;
public ObservableCollection GoogleGbd
{
get { _googleGbd?? (_googleGbd = new ObservableCollection<CompanyProfileModel>()); };
set
{
if (_googleGdb != value)
{
_googleGdb = value;
NotifyPropertyChanged();
}
}
}
private async void Appearing()
{
var companyResult = await ApiGoogle.GetGoogleCompanySelectedDetails(PlaceId);
CompanyProfileModel companyProfile = new CompanyProfileModel
{
ContactDetails = companyResult.formatted_phone_number,
Name = companyResult.name,
Website = companyResult.website,
};
GoogleGbd.Add(companyProfile);
}
If you only want the data to be loaded the first time your View appears, then you can add a bool flag to know that you have already loaded the data.

How to schedule a job using FluentScheduler library with Web Api?

I am unable to get FluentScheduler working in .Net Framework 4.5.2 Web api. Few days ago, I asked a similar question about scheduling through Console application and could get it to work with help but unfortunately facing issues with Web Api now. Below is the code.
[HttpPost]
[Route("Schedule")]
public IHttpActionResult Schedule([FromBody] SchedulerModel schedulerModel)
{
var registry = new Registry();
registry.Schedule<MyJob>().ToRunNow();
JobManager.Initialize(registry);
JobManager.StopAndBlock();
return Json(new { success = true, message = "Scheduled!" });
}
Below is the job I want to schedule which for now is just writing text to a file
public class SampleJob: IJob, IRegisteredObject
{
private readonly object _lock = new object();
private bool _shuttingDown;
public SampleJob()
{
HostingEnvironment.RegisterObject(this);
}
public void Execute()
{
lock (_lock)
{
if (_shuttingDown)
return;
//Schedule writing to a text file
WriteToFile();
}
}
public void WriteToFile()
{
string text = "Random text";
File.WriteAllText(#"C:\Users\Public\TestFolder\WriteText.txt", text);
}
public void Stop(bool immediate)
{
lock (_lock)
{
_shuttingDown = true;
}
HostingEnvironment.UnregisterObject(this);
}
Got this resolved finally. It turns out the issue was with my Registry class. I had to change it as follows.
public class ScheduledJobRegistry: Registry
{
public ScheduledJobRegistry(DateTime appointment)
{
//Removed the following line and replaced with next two lines
//Schedule<SampleJob>().ToRunOnceIn(5).Seconds();
IJob job = new SampleJob();
JobManager.AddJob(job, s => s.ToRunOnceIn(5).Seconds());
}
}
[HttpPost]
[Route("Schedule")]
public IHttpActionResult Schedule([FromBody] SchedulerModel schedulerModel)
{
JobManager.Initialize(new ScheduledJobRegistry());
JobManager.StopAndBlock();
return Json(new { success = true, message = "Scheduled!" });
}
Another point to note: I could get this to work but hosting Api in IIS makes it tricky because we have to deal with App Pool recycles, idle time etc. But this looks like a good start.

Creating a Loading view in .NET Core / Change view without returning c#

I am working on a website created in .NET Core (using the full .NET Framework) that uses background tasks to get a devices list.
I want to display a loading "view" like this while the task is getting data from another PC (using GET requests) and then, when the task is completed I want to display the table with the devices. How can I do that?
Here is a little piece of my code:
public class DeviceController : Controller {
public IActionResult Index() {
if (DataSyncronizer.getDeviceListTask.Status == TaskStatus.Running) {
// TODO Show the loading screen here.
// return this.View("Loading");
}
if (DataSyncronizer.getDeviceListTask.Status == TaskStatus.Faulted) {
ViewData["ErrorTitle"] = "Errore di sincronizzazione";
ViewData["ErrorText"] = "Cannot get devices";
return this.View("Error");
}
if (DataSyncronizer.getDeviceListTask.Status == TaskStatus.Canceled) {
ViewData["ErrorTitle"] = "Errore di sincronizzazione";
ViewData["ErrorText"] = "";
return this.View("Error");
}
return this.View(DataSyncronizer.Devices);
}
And this is the function that gets the device list:
public static class DataSyncronizer {
public static Task<List<Device>> getDeviceListTask { get; private set; }
public static List<Device> Devices = new List<Device>();
public static Task UpdateDevices() {
getDeviceListTask = new Task<List<Device>>(() =>
Device.GetMyDevicesList(meUser));
getDeviceListTask.ContinueWith((result) => {
DataSyncronizer.Devices = result.Result;
}, TaskScheduler.Current);
getDeviceListTask.Start();
return getDeviceListTask;
}
}
You could display the loader right before you call UpdateDevices().
add this to the end of your TASK
.ContinueWith(t => "Function to hide loader");
Example
var webTask = Task.Run(() =>
{
try
{
wcf.UploadMotionDynamicRaw(bytes); //my web service
}
catch (Exception ex)
{
//deal with error
}
}).ContinueWith(t => "Function to hide loader");

Timing issue between async initialisation and results loading in application startup

Seeking some input on a behaviour I'm noticing in my code below. This is my first attempt at async/await using Xamarin Forms and I have perused hundreds of posts, blogs and articles on the subject including the writings from Stephen Cleary on async from constructors and best practices to avoid locking. Although I am using a MVVM framework I assume my issue is more generic than that so I'll ignore it for the moment here.
If I am still missing something or there are ways to improve what I'm trying to do ... happy to listen and learn.
At a high level the logic is as follows:
Application starts and initialises
During initialisation verify database exist and if not - create the SQLite DB. Currently I force this every time to simulate a new application and pre-populate it with some sample data for development purposes
After initialisation completed load results set and display
This works most of the time but I have noticed 2 infrequent occurrences due to the async handling of the database initialisation and pre-populating:
Occasionally not all sample records created are displayed once the app started up - I assume this is because the pre-population phase has not completed when the results are loaded
Occasionally I get an error that one of the tables have not been created - I assume this is because the database initialisation has not completed when the results are loaded
The code - simplified to show the flow during initialisation and startup:
----------- VIEW / PAGE MODEL ----------------
public class MyListItemsPageModel
{
private ObservableRangeCollection<MyListItem> _myListItems;
private Command loadItemsCommand;
public MyListItemsPageModel()
{
_myListItems = new ObservableRangeCollection<MyListItem>();
}
public override void Init(object initData)
{
if (LoadItemsCommand.CanExecute(null))
LoadItemsCommand.Execute(null);
}
public Command LoadItemsCommand
{
get
{
return loadItemsCommand ?? (loadItemsCommand = new Command(async () => await ExecuteLoadItemsAsyncCommand(), () => { return !IsBusy; }));
}
}
public ObservableRangeCollection<MyListItem> MyListItems {
get { return _myListItems ?? (_myListItems = new ObservableRangeCollection<MyListItem>()); }
private set {
_myListItems = value;
}
}
private async Task ExecuteLoadItemsAsyncCommand() {
if (IsBusy)
return;
IsBusy = true;
loadItemsCommand.ChangeCanExecute();
var _results = await MySpecificDBServiceClass.LoadAllItemsAsync;
MyListItems = new ObservableRangeCollection<MyListItem>(_results.OrderBy(x => x.ItemName).ToList());
IsBusy = false;
loadItemsCommand.ChangeCanExecute();
}
}
----------- DB Service Class ----------------
// THERE IS A SPECIFIC SERVICE LAYER BETWEEN THIS CLASS AND THE PAGE VIEW MODEL HANDLING THE CASTING OF TO THE SPECIFIC DATA TYPE
// public class MySpecificDBServiceClass : MyGenericDBServiceClass
public class MyGenericDBServiceClass<T>: IDataAccessService<T> where T : class, IDataModel, new()
{
public SQLiteAsyncConnection _connection = FreshIOC.Container.Resolve<ISQLiteFactory>().CreateConnection();
internal static readonly AsyncLock Mutex = new AsyncLock();
public DataServiceBase()
{
// removed this from the constructor
//if (_connection != null)
//{
// IsInitialized = DatabaseManager.CreateTableAsync(_connection);
//}
}
public Task<bool> IsInitialized { get; private set; }
public virtual async Task<List<T>> LoadAllItemsAsync()
{
// Temporary async/await initialisation code. This will be moved to the start up as per Stephen's suggestion
await DBInitialiser();
var itemList = new List<T>();
using (await Mutex.LockAsync().ConfigureAwait(false))
{
itemList = await _connection.Table<T>().ToListAsync().ConfigureAwait(false);
}
return itemList;
}
}
----------- DB Manager Class ----------------
public class DatabaseManager
{
static double CURRENT_DATABASE_VERSION = 0.0;
static readonly AsyncLock Mutex = new AsyncLock();
private static bool IsDBInitialised = false;
private DatabaseManager() { }
public static async Task<bool> CreateTableAsync(SQLiteAsyncConnection CurrentConnection)
{
if (CurrentConnection == null || IsDBInitialised)
return IsDBInitialised;
await ProcessDBScripts(CurrentConnection);
return IsDBInitialised;
}
private static async Task ProcessDBScripts(SQLiteAsyncConnection CurrentConnection)
{
using (await Mutex.LockAsync().ConfigureAwait(false))
{
var _tasks = new List<Task>();
if (CURRENT_DATABASE_VERSION <= 0.1) // Dev DB - recreate everytime
{
_tasks.Add(CurrentConnection.DropTableAsync<Table1>());
_tasks.Add(CurrentConnection.DropTableAsync<Table2>());
await Task.WhenAll(_tasks).ConfigureAwait(false);
}
_tasks.Clear();
_tasks.Add(CurrentConnection.CreateTableAsync<Table1>());
_tasks.Add(CurrentConnection.CreateTableAsync<Table2>());
await Task.WhenAll(_tasks).ConfigureAwait(false);
_tasks.Clear();
_tasks.Add(UpgradeDBIfRequired(CurrentConnection));
await Task.WhenAll(_tasks).ConfigureAwait(false);
}
IsDBInitialised = true;
}
private static async Task UpgradeDBIfRequired(SQLiteAsyncConnection _connection)
{
await CreateSampleData();
return;
// ... rest of code not relevant at the moment
}
private static async Task CreateSampleData()
{
IDataAccessService<MyListItem> _dataService = FreshIOC.Container.Resolve<IDataAccessService<MyListItem>>();
ObservableRangeCollection<MyListItem> _items = new ObservableRangeCollection<MyListItem>(); ;
_items.Add(new MyListItem() { ItemName = "Test 1", ItemCount = 14 });
_items.Add(new MyListItem() { ItemName = "Test 2", ItemCount = 9 });
_items.Add(new MyListItem() { ItemName = "Test 3", ItemCount = 5 });
await _dataService.SaveAllItemsAsync(_items).ConfigureAwait(false);
_items = null;
_dataService = null;
IDataAccessService<Sample> _dataService2 = FreshIOC.Container.Resolve<IDataAccessService<AnotherSampleTable>>();
ObservableRangeCollection<Sample> _sampleList = new ObservableRangeCollection<Sample>(); ;
_sampleList.Add(new GuestGroup() { SampleName = "ABC" });
_sampleList.Add(new GuestGroup() { SampleName = "DEF" });
await _dataService2.SaveAllItemsAsync(_sampleList).ConfigureAwait(false);
_sampleList = null;
_dataService2 = null;
}
}
In your DataServiceBase constructor, you're calling DatabaseManager.CreateTableAsync() but not awaiting it, so by the time your constructor exits, that method has not yet completed running, and given that it does very little before awaiting, it's probably barely started at that point. As you can't effectively use await in a constructor, you need to remodel things so you do that initialisation at some other point; e.g. perhaps lazily when needed.
Then you also want to not use .Result/.Wait() whenever possible, especially as you're in an async method anyway (e.g. ProcessDBScripts()), so instead of doing
var _test = CurrentConnection.DropTableAsync<MyListItem>().Result;
rather do
var _test = await CurrentConnection.DropTableAsync<MyListItem>();
You also don't need to use Task.Run() for methods that return Task types anyway. So instead of
_tasks.Add(Task.Run(() => CurrentConnection.CreateTableAsync<MyListItem>().ConfigureAwait(false)));
_tasks.Add(Task.Run(() => CurrentConnection.CreateTableAsync<AnotherSampleTable>().ConfigureAwait(false)));
just do
_tasks.Add(CurrentConnection.CreateTableAsync<MyListItem>()));
_tasks.Add(CurrentConnection.CreateTableAsync<AnotherSampleTable>()));
sellotape has correctly diagnosed the code problem: the constructor is starting an asynchronous method but nothing is (a)waiting for it to complete. A simple fix would be to add await IsInitialized; to the beginning of LoadAllItemsAsync.
However, there's also a design problem:
After initialisation completed load results set and display
That's not possible on Xamarin, or any other modern UI platform. You must load your UI immediately and synchronously. What you should do is immediately display a splash/loading page and start the asynchronous initialization work. Then, when the async init is completed, update your VM/UI with your "real" page. If you just have LoadAllItemsAsync await IsInitialized, then your app will sit there for some time showing the user zero data before it "fills in".
You may find my NotifyTask<T> type (available on NuGet) useful here if you want to show a splash/spinner instead of zero data.

Categories