In my project, I followed this guide to build an async database service called animalServ in this context. However, in the guide it specifies how to load data into a CollectionView, only after the press of a button using an async method:
public async void OnGetButtonClicked(object sender, EventArgs args)
{
statusMessage.Text = "";
List<Animal> animals = await App.animalServ.GetAllAnimals();
animalList.ItemsSource = animals;
}
I'm avoiding adding such code in the constructor due to asynchronous problems. What would be a proper way to load such data, so that it immediately is visible when opening the page?
I would also suggest using OnAppearing () which will load data immediately prior to the Page becoming visible. Typically, you want to get the data from database and then assign it to the CollectionView when initializing the page. Here's the code snippet below for your refernce:
protected override async void OnAppearing()
{
base.OnAppearing();
//Get the data from DB
UserDB db = await UserDB.Instance;
List<User> a = await db.GetUserAsync();
//Assign it to the CollectionView
UserCollection = new ObservableCollection<User>(a);
userinfodata.ItemsSource = UserCollection;
}
I know you don't understand the question title. Let me tell you the whole scenario.
I have a class named as Processor. Processor can get notifiable steps or send notification to other application API depending on some condition. Like as below:
Method: SendOrStartProcess(Operation operation)
Method Implementation:
If(operation.steps.any(o=>o.canProcess)){
_service.notify(operations); //This is fine
}
else{
var totalSteps = await _service.getAdjustableSteps(); //It returns the adjustable steps and will invoke event which is subscribed by another class named as GetAdjustableStepsEnd. It can be invoked immediately or after some time.
//Set totalSteps in operationsc.steps and save in database and that's it.
}
Now, I have another class named as "OperationHandler" which subscirbed the GetAdjustableStepsEnd event.
public OperationHandler(IUnityContainer container)
{
_agent.GetAdjustableStepsEnd += GetAdjustableStepsEnd ;
}
public async void GetAdjustableStepsEnd (object sender, GetAdjustableStepsEndEventArgs e)
{
// Here i will call again the above method _processor.SendOrStartProcess(e.Operations);
}
//Now the problem is if event invokes after some time then it is fine because meanwhile i set the status in database. But if it invoked just after the getAdjustableSteps then i call SendOrStartProcess again and it sends getAdjustableSteps again because record is not set in the database. How to overcome this situation. I can not put lock on it because this is used by many clients.
I'm having a problem with my app during the start up. I'm getting at exception that says
A method was called at an unexpected time. Could not create a
new view because the main window has not yet been created
First I display a splash screen so I can get some data from the internet in the background. My splash screen works fine and I implemented it correctly as indicated in the documentation.
In App.xaml.ca I have some standard code for splash screen
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
...
if (e.PreviousExecutionState != ApplicationExecutionState.Running)
{
bool loadState = (e.PreviousExecutionState == ApplicationExecutionState.Terminated);
ExtendedSplash extendedSplash = new ExtendedSplash(e.SplashScreen, loadState);
Window.Current.Content = extendedSplash;
}
...
Window.Current.Activate();
}
Then in my App constructor I have this
public static Notifications notifications;
public App()
{
Microsoft.ApplicationInsights.WindowsAppInitializer.InitializeAsync(
Microsoft.ApplicationInsights.WindowsCollectors.Metadata |
Microsoft.ApplicationInsights.WindowsCollectors.Session);
this.InitializeComponent();
this.Suspending += OnSuspending;
SomeClass.RunTasks(); //acquire data from a REST service
//initializing the object for subscribing to push notification, not sure if this is the best place to put this.
App.notifications = new Notifications("hubname", "myEndpoint");
}
The exception occurs inside my RunTasks() method which looks like this
public class SomeClass
{
GetHTTPResponse _aggregateData = new GetHTTPResponse("http://someRestService");
public async void RunTasks()
{
try
{
HttpResponseMessage aggregateData = await _aggregateData.AcquireResponse();
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
//do a bunch of stuff with the data
//NOTE: I am making updates to my ViewModel here with the data I acquired
//for example App.ViewModel.Time = somevalue
//when finished dismiss the splash screen
ExtendedSplash.Instance.DismissExtendedSplash();
}
);
}
catch (System.Exception ex)
{
}
}
}
Any ideas how I can improve this and eliminate the error?
Could it have something to do with me updating my ViewModel items (which are data bound to UI components)?
EDIT when I remove the creation of my notifications object from App.cs constructor (and move it into the RunTasks() method, the error goes away.
App.notifications = new Notifications("hubname", "myEndpoint");
The reason you get the exception is because Windows.ApplicationModel.Core.CoreApplication.MainView is not valid in your application's constructor as the main view has not been created yet.
You can access it once you have received the Application.OnLaunched/OnActivated event.
Thanks!
Stefan Wick -
Windows Developer Platform
This question is specific to Windows Phone 8.1 (WinRT); it may also be applicable Windows 8.1. I am using Caliburn.Micro 2.0.1
In my ViewModel's OnActivate I check whether an item is a database, if it isn't, I want to navigate back to the previous page.
The simplist solution will be just to call GoBack in the OnActivate method (this works in Windows Phone 8.0):
INavigationService _navigationService;
protected override void OnActivate()
{
_item = GetItemFromDB();
if(_item == null)
{
_navigationService.GoBack()
}
}
To navigate to the view model I call:
_navigationService.NavigateToViewModel<MyViewModel>(_param);
But it does not work, it ignores the GoBack call and stays on the page which I do not want to view.
When stepping through the code you can see that the GoBack code is called inside the NavigateToViewModel method; I expect this is the reason why it does not work (something to do with a queuing issue maybe?).
I have a very "hacky" solution that involves a timer (that works), but I really despise it since it is prone to threading issues and has the possibility of being called during the NavigateToViewModel call (if it takes long to finish), which will then again not work:
protected override void OnActivate()
{
_item = GetItemFromDB();
if(_item == null)
{
DispatcherTimer navigateBackTimer = new DispatcherTimer();
navigateBackTimer.Interval = TimeSpan.FromMilliseconds(300);
navigateBackTimer.Tick += GoBackAfterNavigation;
navigateBackTimer.Start();
}
}
public void GoBackAfterNavigation(object sender, object e)
{
_navigationService.GoBack();
(sender as DispatcherTimer).Stop();
}
Is there a better way to navigate back? Why doesn't the GoBack work in OnActivate? Is there a way to get it to work in OnActivate?
You can use
Execute.OnUIThreadAsync(() => /* navigationCode */);
instead of a timer to queue the action immediately after the processing of the current stack has finished.
I have a ListView in my Windows Phone 8.1 application and I can have something like 1000 or more results, so I need to implement a Load More feature each time the scroll hits bottom, or some other logic and natural way of triggering the adding of more items to the List.
I found that the ListView has support for an ISupportIncrementalLoading, and found this implementation: https://marcominerva.wordpress.com/2013/05/22/implementing-the-isupportincrementalloading-interface-in-a-window-store-app/
This was the better solution I found, since it does not specify a type, i.e., it's generic.
My problem with this solution is that when the ListView is Loaded, the LoadMoreItemsAsync runs all the times needed until it got all the results, meaning that the Load More is not triggered by the user. I'm not sure what make the LoadMoreItemsAsync trigger, but something is not right, because it assumes that happens when I open the page and loads all items on the spot, without me doing anything, or any scrolling. Here's the implementation:
IncrementalLoadingCollection.cs
public interface IIncrementalSource<T> {
Task<IEnumerable<T>> GetPagedItems(int pageIndex, int pageSize);
void SetType(int type);
}
public class IncrementalLoadingCollection<T, I> : ObservableCollection<I>, ISupportIncrementalLoading where T : IIncrementalSource<I>, new() {
private T source;
private int itemsPerPage;
private bool hasMoreItems;
private int currentPage;
public IncrementalLoadingCollection(int type, int itemsPerPage = 10) {
this.source = new T();
this.source.SetType(type);
this.itemsPerPage = itemsPerPage;
this.hasMoreItems = true;
}
public bool HasMoreItems {
get { return hasMoreItems; }
}
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count) {
var dispatcher = Window.Current.Dispatcher;
return Task.Run<LoadMoreItemsResult>(
async () => {
uint resultCount = 0;
var result = await source.GetPagedItems(currentPage++, itemsPerPage);
if(result == null || result.Count() == 0) {
hasMoreItems = false;
}
else {
resultCount = (uint)result.Count();
await dispatcher.RunAsync(
CoreDispatcherPriority.Normal,
() => {
foreach(I item in result)
this.Add(item);
});
}
return new LoadMoreItemsResult() { Count = resultCount };
}).AsAsyncOperation<LoadMoreItemsResult>();
}
}
Here's the PersonModelSource.cs
public class DatabaseNotificationModelSource : IIncrementalSource<DatabaseNotificationModel> {
private ObservableCollection<DatabaseNotificationModel> notifications;
private int _type = "";
public DatabaseNotificationModelSource() {
//
}
public void SetType(int type) {
_type = type;
}
public async Task<IEnumerable<DatabaseNotificationModel>> GetPagedItems(int pageIndex, int pageSize) {
if(notifications == null) {
notifications = new ObservableCollection<DatabaseNotificationModel>();
notifications = await DatabaseService.GetNotifications(_type);
}
return await Task.Run<IEnumerable<DatabaseNotificationModel>>(() => {
var result = (from p in notifications select p).Skip(pageIndex * pageSize).Take(pageSize);
return result;
});
}
}
I changed it a bit, because the call to my Database is Asynchronous and it was the only way I found to make sure I could wait for the query before filling the collection.
And in my DatabaseNotificationViewModel.cs
IncrementalNotificationsList = new IncrementalLoadingCollection<DatabaseNotificationModelSource, DatabaseNotificationModel>(type);
Everything works fine, apart from the not so normal "Load More". What's wrong in my code?
I created a very simplified example of this issue here, and raised this issue on the MSDN forums here. Honestly, I don't know why this weird behavior is happening.
What I observed
The ListView will call LoadMoreItemsAsync first with a count of 1. I assume this is to determine the size of a single item so that it can work out the number of items to request for the next call.
If the ListView is behaving nicely, the second call to LoadMoreItemsAsync should happen immediately after the first call, but with the correct number of items (count > 1), and then no more calls to LoadMoreItemsAsync will occur unless you scroll down. In your example, however, it may incorrectly call LoadMoreItemsAsync with a count of 1 again.
In the worst case, which actually occurs quite frequently in your example, is that the ListView will continue to call LoadMoreItemsAsync with a count of 1 over and over, in order, until HasMoreItems becomes false, in which case it has loaded all of the items one at a time. When this happens, there is a noticeable UI delay while the ListView loads the items. The UI thread isn't blocked, though. The ListView is just hogging the UI thread with sequential calls to LoadMoreItemsAsync.
The ListView won't always exhaust all of the items though. Sometimes it will load 100, or 200, or 500 items. In each case, the pattern is: many calls of LoadMoreItemsAsync(1) followed by a single call to LoadMoreItemsAsync(> 1) if not all of the items have been loaded by the prior calls.
It only seems to occur on page load.
The issue is persistent on Windows Phone 8.1 as well as Windows 8.1.
What causes the problem
The issue seems to be very short lived awaited tasks in the LoadMoreItemsAsync method before you've added the items to the list (awaiting tasks after you've added the items to the list is fine).
The issue doesn't occur if you remove all awaits inside LoadMoreItemsAsync, thus forcing it to execute synchronously. Specifically, if you remove the dispatcher.RunAsync wrapper and await source.GetPagedItems (just mock the items instead), then the ListView will behave nicely.
Having removed all awaits, the issue will reappear even if all you add is a seemingly harmless await Task.Run(() => {}). How bizarre!
How to fix the problem
If most of the time spent in a LoadMoreItemsAsync call is waiting for a HTTP request for the next page of items, as I expect most apps are, then the issue won't occur. So, we can extend the time spent in the method by awaiting a Task.Delay(10), like this maybe:
await Task.WhenAll(Task.Delay(10), dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
foreach (I item in result)
this.Add(item);
}).AsTask());
I've merely provided a (hacky) workaround for your example, but not an explanation why. If anyone knows why this is happening, please let me know.
This is not the only thing that can cause this issue. If your ListView is inside a ScrollViewer, it will continue loading all of the items and ALSO will not virtualize properly, negatively impacting performance. The solution is to give your ListView a specific height.