Task async Call not returning creates deadlock - c#

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.

Related

Login Screen before Master Detail View in Xamarin

I am trying to write a xamarin app that will display a login page before a master detail page but I am running into issues.
Right now I have my app.xaml calling an appbootstrapper as follows:
public App()
{
this.InitializeComponent();
RxApp.SuspensionHost.CreateNewAppState = () => new AppBootstrapper();
RxApp.SuspensionHost.SetupDefaultSuspendResume();
this.MainPage = RxApp.SuspensionHost
.GetAppState<AppBootstrapper>()
.CreateMainPage();
}
/// <summary>Gets the Router associated with this Screen.</summary>
public RoutingState Router { get; } = new RoutingState();
With the app bootstrapper as follows:
public class AppBootstrapper : ReactiveObject, IScreen
{
public AppBootstrapper(IMutableDependencyResolver dependencyResolver = null)
{
SetupLogging();
this.RegisterParts(dependencyResolver ?? Locator.CurrentMutable);
this.Router.Navigate.Execute(new LoginPageViewModel(this));
}
/// <summary>Gets the Router associated with this Screen.</summary>
public RoutingState Router { get; } = new RoutingState();
public Page CreateMainPage()
{
return new RoutedViewHost();
}
private static void SetupLogging()
{
var logger = new Logger { Level = LogLevel.Debug };
Locator.CurrentMutable.RegisterConstant(logger, typeof(ILogger));
}
private void RegisterParts(IMutableDependencyResolver dependencyResolver)
{
dependencyResolver.RegisterConstant(this, typeof(IScreen));
dependencyResolver.Register(() => new LoginPage(), typeof(IViewFor<LoginPageViewModel>));
dependencyResolver.RegisterConstant(new LoginService(), typeof(ILoginService));
}
}
This gets me to my login screen no problem, and I can perform my login operation. Then, once login is successful, I try to navigate to the master detail page, but this is where I run into issues.
public LoginPageViewModel(IScreen screen)
{
this.loginService = Locator.Current.GetService<ILoginService>();
this.HostScreen = screen ?? Locator.Current.GetService<IScreen>();
this.PrepareObservables();
}
........................................................
private void PrepareObservables()
{
...
this.LoginCommand = ReactiveCommand.CreateFromTask(
async execute =>
{
var loginSuccessful = await this.loginService.Login(this.Username, this.Password);
if (loginSuccessful)
{
this.HostScreen.Router.NavigateBack.Execute().Subscribe();
}
}, canExecuteLogin);
...
You can see that my login command is trying to perform a navigate and reset to go to the Main Page (which is my master detail page). This is not working and is resulting in an unhandled exception stating:
An object implementing IHandleObservableErrors has errored, thereby breaking its observable pipeline. To prevent this, ...>
Does anyone know what to do here? I need a good pattern for handling the use case of Login -> Master Detail Page in Xamarin Forms using ReactiveUI. Thanks.
this.LoginCommand = ReactiveCommand.CreateFromTask(
async execute =>
{
var loginSuccessful = await this.loginService.Login(this.Username, this.Password);
if (loginSuccessful)
{
this.HostScreen.Router.NavigateBack.Execute().Subscribe();
}
}, canExecuteLogin);
The above code is navigating back on successful login. I think you mean to use Router.NavigateAndReset.Execute(new MainPageViewModel()).Subscribe();

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

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); }
}
}

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.

Waiting for Async calls to finish

I'm working with the Philips Hue, and I need to get some information from the hue bridge before I can populate my application. The requests are made via HTTP/JSON. I have no issue when I run all my code async, but when I try to break out my code so that I have a separate method to update the UI upon loading I'm getting a System.NullReferenceException on myLights. I'm assuming that's because my startUpProcedure() isn't finished yet, hence myLights has not been set. I can't seem to figure out how to wait for the startUpProcedure() to finish before I run setupUI().
To further back that up, if I just run startUpProcedure() without setupUI() I get no issues. And then if I run setupUI() from say a button click it runs just fine.
Clearly I'm missing something here. I just can't seem to find the answer.
So to succinctly put the question: How do I wait for these Async calls to finish so that I can use variables that are depended on their return values?
public sealed partial class MainPage : Page
{
public string appKey;
public string myIP;
public IEnumerable<Light> myLights;
public ILocalHueClient myClient;
public MainPage()
{
this.InitializeComponent();
startUpPocedure();
setupUI();
}
public async Task startUpPocedure()
{
await startUp();
await getLights();
}
public async Task startUp()
{
if (await findBridgeIP())
{
Debug.WriteLine("Bridge Found...");
//Do Actions
}
else
{
//Error!!
Debug.WriteLine("No hue found");
}
Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
try {
appKey = localSettings.Values["appKey"].ToString();
Debug.WriteLine("appKey loaded: " + appKey);
//Load up
myClient = new LocalHueClient(myIP);
myClient.Initialize(appKey);
}
catch {
Debug.WriteLine("Need to register app");
}
}
async Task getLights()
{
myLights = await myClient.GetLightsAsync();
Debug.WriteLine("Light Count " + myLights.Count());
IEnumerable<string> myLightNames;
List<string> myTempList = new List<string>();
foreach (Light l in myLights)
{
myTempList.Add(l.Name);
Debug.WriteLine(l.Name);
}
myLightNames = myTempList;
comboBox_LightSelect.ItemsSource = myLightNames;
}
private void setupUI()
{
//Populate the Combo Box
IEnumerable<string> myLightNames;
List<string> myTempList = new List<string>();
foreach (Light l in myLights)
{
myTempList.Add(l.Name);
Debug.WriteLine(l.Name);
}
myLightNames = myTempList;
comboBox_LightSelect.ItemsSource = myLightNames;
}
Your startUpPocedure method in MainPage constructor returns Task almost immediately, and because you're not awaiting it, code execution goes to the next line right after that and setupUI gets called. So when your code starts to enumerate myLights collection, it's still null because startUpPocedure and therefore getLights are still running.
Problem is, you can't await asynchronous methods in a constructor, so in your case solution will be to move both startUpPocedure and setupUI to single async method, await them inside this method and call it from constructor similar to this:
public MainPage()
{
this.InitializeComponent();
Startup();
}
private async void Startup()
{
await startUpPocedure();
setupUI();
}
You should leave only InitializeComponent in the constructor, and move all other logic to Loaded event handler,
https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.frameworkelement.loaded
Then you can mark that handler as async, and use await in it to await on async methods.

Working with an HTTP GET request with MVVM

I'm currently developing a Windows Phone 8 app that requires the use of a web API. When I decided to move towards the MVVM model I moved the necessary code to download data from the web API over to a separate class. After implementing all necessary functionality, I realized that the thread wouldn't wait for the data to finish downloading (or at least wouldn't wait for the necessary code to run following that), the thread would go through the ViewModel constructor and return a blank list to bind to my LongListSelector control. When debugging I realized that the thread would actually go through the DownloadCompleted method within my ViewModel at some point, but it was always after the ItemsSource for my LongListSelector was already set to a blank List. In the end I did get a properly populated list of data, it's just that the LongListSelector would have already been bound to the empty List instead. Is there anyway I can change what I'm doing so that my LongListSelector actually binds to the real data I'm getting from the web instead of binding to an empty List before it's properly populated with data? Whether it's somehow waiting for all the necessary code to run before the thread moves on, or updating the View when my List gets properly populated with data, I'm willing to accept any sorts of suggestions as long as they get my code to finally work.
Thanks in advance!
In MainPage.xaml.cs:
public void Retrieve()
{
MySets.ItemsSource = new MySetsViewModel(CurrentLogin).BindingList;
}
MySetsView Model is defined as the following:
public class MySetsViewModel
{
User CurrentLogin;
List<Sets> SetsList;
public List<Sets> BindingList { get; set; }
public MySetsViewModel(User CurrentLogin)
{
this.CurrentLogin = CurrentLogin;
Retrieve(CurrentLogin);
}
public async void Retrieve(User CurrentLogin)
{
if (IsolatedStorageSettings.ApplicationSettings.Contains("AccessToken"))
{
CurrentLogin.AccessToken = IsolatedStorageSettings.ApplicationSettings["AccessToken"].ToString();
}
if (IsolatedStorageSettings.ApplicationSettings.Contains("UserID"))
{
CurrentLogin.UserId = IsolatedStorageSettings.ApplicationSettings["UserID"].ToString();
}
try
{
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage();
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + CurrentLogin.AccessToken);
client.DefaultRequestHeaders.Add("Host", "api.quizlet.com");
var result = await client.GetAsync(new Uri("https://api.quizlet.com/2.0/users/" + CurrentLogin.UserId + "/sets"), HttpCompletionOption.ResponseContentRead);
string jsonstring = await result.Content.ReadAsStringAsync();
DownloadCompleted(jsonstring);
}
catch
{
}
}
void DownloadCompleted(string response)
{
try
{
//Deserialize JSON into a List called Sets
this.BindingList = Sets;
}
catch
{
}
}
}
All you have to do is implement INotifyPropertyChanged on your view model. Raise the PropertyChanged event inside the setter for "BindingList", and the view will update itself.
public class MySetsViewModel : INotifyPropertyChanged
{
public List<Sets> BindingList
{
get { return _bindingList; }
set
{
_bindingList = value;
RaisePropertyChanged("BindingList");
}
}
private List<Sets> _bindingList;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}

Categories