View getting loaded before data is received asynchronously - c#

Following is my ViewModel class:
public partial class DosAdminProductHierarchy : UserControl, INotifyPropertyChanged
{
public DosAdminProductHierarchy()
{
InitializeComponent();
this.GetProductList();
//this.ProductList = new NotifyTaskCompletion<List<Product>>(this.GetProductList());
OnPropertyChanged("DepartmentList");
if(isDataLoaded)
{
treeList.ItemsSource = ProductList;
treeList.Visibility = Visibility.Visible;
}
}
private ObservableCollection<Product> dbProductList;
private bool isDataLoaded = false;
public ObservableCollection<Product> ProductList
{
get
{
return dbProductList;
}
private set
{
dbProductList = value;
isDataLoaded = true;
}
}
private async void GetProductList()
{
try
{
IWebApiDataAdapter _webAPIDataAdapter = new DosAdminDataAdapter();
List<Product> lstProd= new List<Product>();
lstProd = await _webAPIDataAdapter.GetProductHierarchy();
dbProductList = new ObservableCollection<Product>();
foreach (Product prd in lstProd)
{
dbProductList.Add(prd);
}
}
catch (Exception ex)
{
throw (ex);
}
}
}
My problem is I want ProductList to be populated but it is not getting populated. Execution is reaching till the end of constructor even though data has not returned from WebApi, I want somehow to hold the execution or to show user something is busy till ProductList is getting populated. Please help how to achieve that.

You should not be loading data in a constructor. It violates the S principle of SOLID.
You should be using a Command linked to Loaded event or similar to load data.
You should also not be using async void method signatures as it hides Exception thrown by the method.
Your constructor is returning immediately because you are not calling await GetProductsList(). Your code results in the async method being executed after the constructor completes.
To solve your problem with the visibility, rather use a BooleanToVisibilityConverter on a Binding to IsDataLoaded property and make it notify when it changes value.

Related

How do I bind a domain context to a data grid ? Ria Services + Silverlight

These past days I've tried to understand the concept of invoked methods and domain contexts, but some things are still not so clear to me.
In my StudentDomainService I have this method:
[Invoke]
public IEnumerable<Student> GetStudents()
{
return _studentRepository.GetStudents();
}
This returns a list of students that I want to bind to my datagrid using a viewmodel.
In my StudentViewModel I have:
private StudentDomainContext _studentDomainContext;
private InvokeOperation<IEnumerable<Student>> _students;
public StudentViewModel()
{
_studentDomainContext = new StudentDomainContext();
_students = _studentDomainContext.GetStudents(OnInvokeCompleted, null);
}
public void OnInvokeCompleted(InvokeOperation<IEnumerable<Student>> studs)
{
if (studs.HasError)
{
studs.MarkErrorAsHandled();
}
else
{
var result = studs.Value;
}
}
public InvokeOperation<IEnumerable<Student>> Students
{
get { return _students; }
set
{
_students = value;
RaisePropertyChanged("Students");
}
}
And in StudentView I have a datagrid and I'm trying to bind to the Students property like this:
ItemsSource="{Binding Students}"
But when I start the application the data grid appears but it's not displaying anything. Can anyone please tell me what I'm doing wrong?
UPDATE I think the problem is that the OnInvokeCompleted() method is not executing on the UI, but I'm not really sure.

DotVVM - GridViewDataSet not loading any data

I am stuck on problem where my facade method is never called so my returned items are always zero. I dont exactly know why this happens since it should at least load items on initial page load. Also I got almost identical ViewModel where it works fine.
Here is my viewmodel where I want to load FavouriteCommodities into GridViewDataSet
//initialized via constructor
private readonly FavouriteCommodityFacade _favouriteCommodityFacade;
public GridViewDataSet<FavouriteCommodityDTO> GridViewDataSetItems { get; set; }
public int PageSize { get; set; } = 10;
private ISortingOptions DefaultSortOptions => new SortingOptions();
public override Task Init()
{
GridViewDataSetItems = new GridViewDataSet<FavouriteCommodityDTO>()
{
PagingOptions = { PageSize = PageSize },
SortingOptions = DefaultSortOptions
};
return base.Init();
}
public override Task PreRender()
{
if (!Context.IsPostBack || GridViewDataSetItems.IsRefreshRequired)
{
LoadData();
}
return base.PreRender();
}
public void LoadData()
{
FavouriteCommodityGroups = _userAccountFavouriteProductsFacade.GetAllFavouriteProductsToUser();
//this never fires my facade method below
GridViewDataSetItems.OnLoadingData = option => _favouriteCommodityFacade.GetAssignedFavouriteProductsToGroup(option);
}
Here is my method in my facade which is never fired.
public GridViewDataSetLoadedData<FavouriteCommodityDTO>
GetAssignedFavouriteProductsToGroup (IGridViewDataSetLoadOptions gridViewDataSetLoadOption)
{
using (unitOfWorkProvider.Create())
{
var query = _favouriteCommodityByChosenGroupQuery();
FavouriteProductByGroupFilter.FavouriteGroupId = 16;
query.Filter = FavouriteProductByGroupFilter;
var x = GetGridViewDataSetLoadedData(gridViewDataSetLoadOption, query);
return x.Result;
}
}
I am also wondering if there is possibility to load those items on user click event. When I looked into namespace DotVVM.Framework.Controls I didnt find anything useful for that.
So I figure out after a while that Repeater component not triggering method. I had to use only GridView component in view.

Object is not set in ViewModel class and control is directly move from data access class to View

I want to fetch first record from AboutUs table and display as a content label.
I have created 4 classes in MVVM pattern.
First is Model class AboutUs.cs
[Table("tblAboutUs")]
public class AboutUs
{
[PrimaryKey, AutoIncrement, NotNull]
public int IDP { get; set; }
public string Content { get; set; }
}
Second is Data Access class
SQLiteAboutUs.cs
public class SQLiteAboutUs
{
private static readonly AsyncLock Mutex = new AsyncLock();
private SQLiteAsyncConnection dbConn;
public int StatusCode { get; set; }
public SQLiteAboutUs(ISQLitePlatform sqlitePlatform, string dbPath)
{
if (dbConn == null)
{
var connectionFunc = new Func<SQLiteConnectionWithLock>(() =>
new SQLiteConnectionWithLock
(
sqlitePlatform,
new SQLiteConnectionString(dbPath, storeDateTimeAsTicks: false)
));
dbConn = new SQLiteAsyncConnection(connectionFunc);
dbConn.CreateTableAsync<Model.AboutUs>();
}
}
public SQLiteAboutUs()
{
}
public async Task Save(Model.AboutUs content)
{
using (await Mutex.LockAsync().ConfigureAwait(false))
{
StatusCode = 0;
await dbConn.InsertAsync(new Model.AboutUs { Content = content.Content });
StatusCode = 1;
}
//For Get first Row from Table
public async Task<Model.AboutUs> GetAllData()
{
return await dbConn.Table<Model.AboutUs>().Where(x => x.IDP == 1).FirstOrDefaultAsync();
}
}
Third class ViewModel Class
AboutUsViewModel.cs
public class AboutUsViewModel
{
readonly SQLiteAboutUs _db;
public string AboutUsContent { get; set; }
//public string AboutUs;
public AboutUsViewModel()
{
_db = new SQLiteAboutUs();
}
public async void FirstRecord()
{
Model.AboutUs obj = await _db.GetAllData();
this.AboutUsContent = obj.Content;
}
}
Forth one is Code behind file of my xaml pages.
AboutUs.xaml.cs
public partial class AboutUs : ContentPage
{
readonly AboutUsViewModel aboutUsViewModel;
public AboutUs()
{
InitializeComponent();
aboutUsViewModel = new AboutUsViewModel();
aboutUsViewModel.FirstRecord();
lblContent.Text = aboutUsViewModel.AboutUsContent;
}
}
I have debug code but problem is In AboutUsViewModel.cs class in FirstRecord Method object can not be set that's why AboutUsContent string property is also not set.
I can't figure out why my debugger directly jump from GetAllData() method in SQLiteAboutUs.cs to label.text in code behind file of view?
Welcome in the wonderfull world of asynchronicity. I encourage you to read carefully about how await is working: How and When to use `async` and `await`
It is not a blocking call. Thus you create the view AboutUs. It creates the ViewModel AboutUsViewModel. It calls
aboutUsViewModel.FirstRecord();
But does not wait for the call to be complete (dont't forget you marked your FirstRecord function as async...)
So it calls
Model.AboutUs obj = await _db.GetAllData();
And directly return to the caller because of the await operator.
That's why it directly jump to
lblContent.Text = aboutUsViewModel.AboutUsContent;
What you would like is Something like
await aboutUsViewModel.FirstRecord();
To wait the call to be complete before going to the next line. But of course you can't do that, because you are in a constructor and you can't have an async constructor. And calling a database (or actually anything that could likely failed) in a constructor is a bad practice anyway.
I would advise you to let only InitializeComponent() in the constructor, and then use Something like the OnDataLoaded() event of your view to perform your async call with a await.
Hope it helps.

Xamarin IMobileServiceTable Not Filtering

I'm using IMobileServiceTable in an data access layer class and bind it to an listview. Initial loading works fine but filtering doesn't. It always returns the initial loaded data.
public class ItemsManager {
IMobileServiceTable<Item> itemTable;
public ItemsManager (IMobileServiceTable<Item> todoTable)
{
this.itemTable = todoTable;
}
public async Task<List<Item>> GetTasksAsync (string searchString)
{
//following doesn't work
var list = new List<Item> (await itemTable.Where(x => x.ItemID.Contains(searchString)).ToListAsync());
return list;
}
public async Task<List<Item>> GetTasksAsync ()
{
return new List<Item> (await itemTable.OrderBy(a =>a.ItemID).ToListAsync());
}
}
If it matter, following is my page code :
public partial class ItemsListXaml : ContentPage
{
IMobileServiceTable<Item> itemTable;
ItemsManager itemManager;
public ItemsListXaml ()
{
InitializeComponent ();
itemTable = App.client.GetTable<Item>();
itemManager = new ItemsManager(itemTable);
App.SetItemsManager (itemManager);
}
protected async override void OnAppearing ()
{
base.OnAppearing ();
listView.ItemsSource = await itemManager.GetTasksAsync ();
}
async void OnValueChanged (object sender, TextChangedEventArgs e) {
var t = e.NewTextValue;
// perform search on min 3 keypress
if (t.Length>3) {
listView.ItemsSource = await itemManager.GetTasksAsync(SearchFor.Text);
}
}
}
It looks like the problem line is this:
var list = new List (await
itemTable.Where(x => x.ItemID.Contains searchString)).ToListAsync());
Not sure exactly what's going on there, but I did manage to get something similar working. The sample I have uses a proxy object to save on Azure fetches. I fetch once to get the initial list of tasks and save that to a local ObservableCollection object that I can bind to the list. Then, I can filter the collection object that is bound to the list (sample here).
You might have legitimate reasons for fetching a filtered list from Azure. In my mind - and bear with me because I'm no expert on app design - unless there is a significant period of time between the initial fetch of the list and the filter action where there might be new data introduced to the table, seems like just filtering a local object would perform better and be cheaper. The app could always handle push notifications to update the list as needed.
Basically, pull objects from Azure into it as shown here:
public async Task<ObservableCollection<ToDoItem>> GetTasksAsync()
{
try
{
return new ObservableCollection<ToDoItem>(await _todoTable.ReadAsync());
}
catch (MobileServiceInvalidOperationException msioe)
{
Debug.WriteLine(#"INVALID {0}", msioe.Message);
}
catch (Exception e)
{
Debug.WriteLine(#"ERROR {0}", e.Message);
}
return null;
}
Then, bind to list as shown here:
protected async override void OnAppearing()
{
base.OnAppearing();
App.TodoManager.TodoViewModel.TodoItems = await App.TodoManager.GetTasksAsync();
listViewTasks.ItemsSource = App.TodoManager.TodoViewModel.TodoItems;
}
In this example, “App.TodoManager.TodoViewModel.TodoItems” is the fully qualified path to the proxy object which is the ObservableCollection.
Then you can filter the proxy object and rebind it to the list. I haven’t actually implemented that part in the sample, but I did take down a copy of it and then added the code and seems to work fine. This would be the code:
Getting the filtered list:
public ObservableCollection<ToDoItem> GetFilteredList(string searchString)
{
return new ObservableCollection<ToDoItem>
(TodoViewModel.TodoItems.Where(x => x.Name.Contains(searchString)));
}
Calling helper method and binding to listview (Incorporating this into one of your example blocks):
async void OnValueChanged (object sender, TextChangedEventArgs e) {
var t = e.NewTextValue;
// perform search on min 3 keypress
if (t.Length>3) {
App.TodoManager.TodoViewModel.TodoItems = App.TodoManager.GetFilteredList(searchFor.Text);
listViewTasks.ItemsSource = App.TodoManager.TodoViewModel.TodoItems;
}
}

Why my List is null after being assigned in a function call?

This might be a silly question. But when I modified one example from Live SDK example, got a weird problem.
I was thinking the root cause is async function GetAll() was used synchronously.
Below is the code snippet, I put the problem as comments. Thanks in advance!
class SkyeDriveViewModel: INotifyPropertyChanged
{
private List<SkyDriveItem> folderList = null;
public List<SkyDriveItem> FolderList
{
get { return folderList; }
private set
{
if (value != folderList)
{
folderList = value;
NotifyPropertyChanged("FolderList");
}
}
}
private async void GetAll(string desiredPath)
{
FolderList = new List<SkyDriveItem>();
this.liveClient = new LiveConnectClient(SkyDrivePage.Session);
try
{
LiveOperationResult operationResult = await this.liveClient.GetAsync(desiredPath);
dynamic result = operationResult.Result;
dynamic items = result.data;
foreach (dynamic item in items)
{
SkyDriveItem newItem = new SkyDriveItem(item);
if (newItem.IsFolder)
{
FolderList.Add(newItem);
}
}
}
catch (LiveConnectException e)
{
}
//**till here, FolderList was assigned**
}
public void InitList()
{
Debugger.Log();
GetAll(SKYDRIVEINITPATH);
Debugger.LogWhen(eDebugger.LogTiming.Exit);
//**till here, FolderList had zero item**
}
}
In general having an async void function is a warning sign. You should only have such a method for an event handler. The appropriate return type for GetAll is Task, or possibly even Task<List<SkyDriveItem>>.
The issue is that calling GetAll will only execute code until it hits the first await call, at which point it returns to the caller and the remainder of the method will be executed asynchronously.
The problem here is that because the method is void you have no way of knowing when it's done. You have a "fire and forget" method and can never know when your list is actually ready. If GetAll returns a task then you can await that task and be sure that your list has actually been populated.

Categories

Resources