FreshMVVM and resetting VM before popping Modal - c#

FreshMVVM 3.0.0
Xamarin Forms 4.2
A number of our input pages are loaded modally and when the user presses Save we execute a Command like this
var newTemperature = new Temperature()
{
Date = DateTime.Now,
Value = this.TemperatureValue,
CaptureType = CaptureType.Manual,
IsModified = true,
};
await this.Services.DataService.SaveAsync(newTemperature);
// Save completed, now close modal.
await this.CoreMethods.PopPageModel(data, modal, animate);
If you look at the CoreMethods.PopPageModel call in GitHub you can see that it deals with two processes
Raising the PageWasPopped signal
Calling to the Navigation Service to pop the page off of the navigation stack
The FreshMVVM code that handles the page being popped is in FreshPageModel. Among other things the code unhooks from the Appearing and Disappearing events and sets the BindingContext to null. As you can see from the above order that means the BindingContext on the View is set to null before it is popped off the stack.
The problem with this is that for a short period of between 0.5 and 1.5 seconds the user sees a View that looks like the data has all been reset. This could be quite disconcerting if they have just pressed Save.
If I reverse the order of the logic in PopPageModel and pop from the navigation stack before calling RaisePageWasPopped this issue goes away.
Has nobody else seen this problem before?
Any users of FreshMVVM who want to point out the error of my suggested approach?

Just note that the service is being awaited, holding the UI until service completes,
have you tried removing await and popping the page immediately or displaying a loader while the service is busy?
this.InsertReports(metadata.Reports).ConfigureAwait(false);

Our solution to this issue was to implement our own PopPageModel method which essentially switches the order around so that PopPage is called on the Navigation Stack before a call to RaisePageWasPopped
This is what we call when we want to dismiss the Page
public Task DismissAsync(bool modal = true, bool animate = true)
{
return this.DispatcherService.RunOnUiThreadAsync(
async () =>
{
string navServiceName = this.CurrentNavigationServiceName;
if (this.IsModalFirstChild)
{
await this.CoreMethods.PopModalNavigationService(true);
}
else
{
IFreshNavigationService rootNavigation = FreshIOC.Container.Resolve<IFreshNavigationService>(navServiceName);
await rootNavigation.PopPage(modal, animate);
if (modal)
{
this.RaisePageWasPopped();
}
}
});
}

Related

ASP.NET Background Thread blocking page access

I have a (very old but still maintained) website built on .NET WebForms.
Recently I had the need to add a task/operation that, upon click on a button, initiates a background thread that updates a bunch of different stuff:
1 - Integrate with an outside API to fetch information and write it into the database
2 - Update a search index built on Lucene
The code, on what we call the Settings page (Setting.aspx) looks something like this:
protected void btnForcePartialSync_Click(object sender, EventArgs e)
{
Button button = sender as Button;
string logLocation = HostingEnvironment.MapPath(VirtualPathUtility.AppendTrailingSlash(ConfigurationManager.AppSettings["SyncLogsLocation"]) + ConfigurationManager.AppSettings["PartialSyncLogName"]);
if (File.Exists(logLocation))
Renamefile(logLocation);
StreamWriter sw = new StreamWriter(logLocation);
DateTime end = DateTime.UtcNow;
DateTime start;
if (button.Equals(btnDoPartialSyncMonth))
start = end.AddMonths(-1);
else
start = end.AddHours(-24);
HttpContext ctx = HttpContext.Current;
ThreadStart starter = () => { PopulateCv.ForcePartialSync(sw, start, end); };
starter += () => {
HttpContext.Current = ctx;
CacheService.ClearCache(CacheService.WORKS_COUNT_CACHE_KEY);
int CvCount = DataAccess.Cv.Cv.GetActiveCvs().Count;
CacheService.Add(CacheService.WORKS_COUNT_CACHE_KEY, CurriculumCount);
CVSearchIndex.BuildIndex();
FillContentsIndexInfo();
};
Thread t = new Thread(starter) { IsBackground = true };
t.Start();
}
This process, as expected, takes some time to complete since the CVSearchIndex.BuildIndex(); operation is very long (over 10 minutes). Everything is fine up until here.
It seems to work nicely, and I can still use the site and navigate to a different page, e.g. Homepage while the operation completes.
However once I try to navigate back into the Settings page, it simply will not load until the thread ends (when it's done building the search index).
This is not the behaviour I was expecting. How can I solve this without substantial changes to the current code?
Can I make the navigation to this page not block while the search index is being built?
Best Regards.
This is probably blocking due to Session mutex. Try adding the following to the page (aspx) <%# Page EnableSessionState="true|false|ReadOnly" %> (false or ReadOnly)
Alternatively, if it's just a fire and forget operation, you could set it up as a scheduled task, and then fire it off manually from your method, allowing the method to end immediately (running the job in the background)

How can I use correctly the activity indicator on Xamarin.forms for Android?

I'm using this plugin for showing that the app is busy, but on Android the animation is always stuck.
For example I use it in this code:
private async Task SelectWorkOrderItemAsync(WorkOrderLista WoLista) {
if (WoLista == null) return;
// show the loading
Acr.UserDialogs.UserDialogs.Instance.ShowLoading("Loading..");
// get datas from DB
WorkOrderDettaglio WoDett = await _WorkOrderService.GetDettaglioWorkOrder(WoLista.Guid_servizio);
// this code opens another page with the datas extracted above
await NavigationService.NavigateToAsync<DettaglioWoViewModel>(WoDett, Costanti.TipoPush.Normale, WoDett.NumWoAnno);
// hide the loading
Acr.UserDialogs.UserDialogs.Instance.HideLoading();
}
This is the result:
as you can see, the loading indicator after some seconds become freezed.
This behaviors is the same if I use the default ActivityIndicator.
On IOS all works fine.
How can I correctly use it?
I don't have an Android device/simulator to test at the moment and can't reproduce on UWP, but your service call is being executed asynchronously on the main thread, your ActivityIndicator should not be blocked if you execute your service call in a worker thread.
// get datas from DB
WorkOrderDettaglio WoDett = null;
await Task.Run(async () => WoDett = await _WorkOrderService.GetDettaglioWorkOrder(WoLista.Guid_servizio));
The activity indicator must be binded (isVisibleProperty and isRunningProperty) to a bool property on your ViewModel and your ViewModel must implement
INotifyPropertyChanged
Here is a greate explanation on how to achieve that.
If an overlay is needed in this answer you can find out how to achieve that.

CefSharp offscreen - wait for page for render

I have a problem as below. I use the CefSharp offscreen for webpage automation as follows (I open only one and the same page):
1. Open page and wait untill it renders*.
2. With EvaluateScriptAsync I put on value to input form and then with the same method I click the button on webpage.
3. Then there is some JS on this webpage that check result and displays a message.
4. When the message is displayed I make a screenshot. **
However, I have two problems:
* My sulution has to be Internet speed proof. And As I used BrowserLoadingStateChanged event and IsLoading method, even though that the events fired the webpage did not load completly - when I started the EavluateScriptAsync method it gives back error because the page was not completly loaded. Sure, I can put sth like ThreadSleep but it does not always work - it is strongly dependent on Your internet speed.
** When I try to make a screenshot it does not always contain the result message displayed by JS - sometimes there is a loading circle instead of message. And here again I can use THreadSleep but it does not always work.
Do You have any ideas? Thanks in advance.
private static void BrowserLoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
{
// Check to see if loading is complete - this event is called twice, one when loading starts
// second time when it's finished
// (rather than an iframe within the main frame).
if (!e.IsLoading)
{
// Remove the load event handler, because we only want one snapshot of the initial page.
browser.LoadingStateChanged -= BrowserLoadingStateChanged;
Thread.Sleep(1800); // e. g. but it isn't a solution in fact
var scriptTask = browser.EvaluateScriptAsync("document.getElementById('b-7').value = 'something'");
scriptTask = browser.EvaluateScriptAsync("document.getElementById('b-8').click()");
//scriptTask.Wait();
if (browser.IsLoading == false)
{
scriptTask.ContinueWith(t =>
{
//Give the browser a little time to render
//Thread.Sleep(500);
Thread.Sleep(500); // still not a solution
// Wait for the screenshot to be taken.
var task = browser.ScreenshotAsync();
task.ContinueWith(x =>
{
// Make a file to save it to (e.g. C:\Users\jan\Desktop\CefSharp screenshot.png)
var screenshotPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "CefSharp screenshot.png");
Console.WriteLine();
Console.WriteLine("Screenshot ready. Saving to {0}", screenshotPath);
// Save the Bitmap to the path.
// The image type is auto-detected via the ".png" extension.
task.Result.Save(screenshotPath);
// We no longer need the Bitmap.
// Dispose it to avoid keeping the memory alive. Especially important in 32-bit applications.
task.Result.Dispose();
Console.WriteLine("Screenshot saved. Launching your default image viewer...");
// Tell Windows to launch the saved image.
Process.Start(screenshotPath);
Console.WriteLine("Image viewer launched. Press any key to exit.");
}, TaskScheduler.Default);
});
}
}
}
Ok, so in my case the best sollution was to use javascript to check if element by id exists. If yes then the page is loaded.
I noticed that render time may vary significantly depending on your hardware. It can take up to 5 seconds to render after EvaluateScriptAsync was called. So it always better to do longer delays before calling ScreenshotAsync() if you do not want to get outdated screenshot.
Thread.Sleep(5000);

How to suppress push toast notification only when App in foreground?

Note: I have only tested this using the emulator, and pushing toasts using the built in functionality. I am assuming this isn't an emulator issue.
I followed this guide in order to intercept push toast notifications while the app is running. However, I only want to suppress the toast notification when the app is in the foreground. It should still display when another app is in the foreground. So I wrote the following handler in App.xaml.cs (and subscribed to the PushNotificationReceived event):
private async void OnPushNotification(PushNotificationChannel sender, PushNotificationReceivedEventArgs e)
{
string msg = "";
if (e.NotificationType == PushNotificationType.Toast)
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
if (Window.Current.Visible)
{
msg += " Toast canceled.";
e.ToastNotification.SuppressPopup = true;
}
});
if (true) // actually determines if it's a certain type of toast
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
ConfirmationContentDialog confirmationDialog = new ConfirmationContentDialog();
confirmationDialog.SetMessage("Please confirm that you like turtles." + msg);
await confirmationDialog.ShowAsync();
});
}
}
}
So this works, in the sense that I only see the "toast canceled" message when the app was in the foreground when receiving the push notification. When I'm on the start screen or somewhere else I always get the toast. This is good. However, when the app is in the foreground, sometimes (usually after sending the second push) the toast shows up anyway (even though "Toast canceled" displays). But sometimes it doesn't. It's rather inconsistent.
This is leading me to believe that due to the await, sometimes the toast gets through before the code gets run on the UI thread to check whether the app is visible or not. However, I can't access Window.Current.Visible from here without using the dispatcher. I even tried CoreApplication.MainView.CoreWindow.Visible but that gives me "interface marshalled for different thread etc" exception. Speaking of which, I don't understand how CoreApplication.MainView.CoreWindow.Dispatcher can be called from anywhere but CoreApplication.MainView.CoreWindow.Visible not? How does that even work.
Anyway, how do I fix this? I would like to keep this within App.xaml.cs because I have a number of pages in this app, but this content dialog needs to be shown no matter which page the user is on, and without the user being redirected to a different page. However, I am of course open for new suggestions.
I fixed this as per Kai Brummund's suggestion by using a simple boolean toggle in the App class, and subscribing to the VisibilityChanged event like so:
private bool APP_VISIBLE = true;
protected override async void OnLaunched(LaunchActivatedEventArgs e)
{
// Stuff put here by Visual Studio
Window.Current.VisibilityChanged += OnVisibilityChanged;
Window.Current.Activate();
}
private void OnVisibilityChanged(object sender, VisibilityChangedEventArgs e)
{
if (e.Visible)
APP_VISIBLE = true;
else
APP_VISIBLE = false;
}
That way I can use APP_VISIBLE to suppress the popup without having to use the dispatcher and the toast is suppressed immediately.

How to display a message in Windows Store Apps?

How to display a message box in windows 8 apps using c# like calling MessageBox.Show() in windows phone 7?
MessageDialog msgDialog = new MessageDialog("Your message", "Your title");
//OK Button
UICommand okBtn = new UICommand("OK");
okBtn.Invoked = OkBtnClick;
msgDialog.Commands.Add(okBtn);
//Cancel Button
UICommand cancelBtn = new UICommand("Cancel");
cancelBtn.Invoked = CancelBtnClick;
msgDialog.Commands.Add(cancelBtn);
//Show message
msgDialog.ShowAsync();
And your call backs
private void CancelBtnClick(IUICommand command)
{
}
private void OkBtnClick(IUICommand command)
{
}
P.S. You can follow this tutorial for more.
The MessageDialog class should fit your needs.
My simpler way, for confirmation type message boxes:
var dlg = new MessageDialog("Are you sure?");
dlg.Commands.Add(new UICommand("Yes", null, "YES"));
dlg.Commands.Add(new UICommand("No", null, "NO"));
var op = await dlg.ShowAsync();
if ((string)op.Id == "YES")
{
//Do something
}
For simpler way, Just to display the message text and OK button. Use Windows.UI.Popups namespace. Create a method messagebox() that method should be
using Windows.UI.Popups;
protected async void messageBox(string msg)
{
var msgDlg = new Windows.UI.Popups.MessageDialog(msg);
msgDlg.DefaultCommandIndex = 1;
await msgDlg.ShowAsync();
}
Then call this method in your code like
messageBox("Unexpected error held");
Additional tidbit:
It appears in a modern Windows App a MessageDialog will not show prior to your app making its Window.Current.Active() call, which usually happens in the app class' OnLaunched() method. If you're trying to use MessageDialog to display something like a start-up exception, that's important to keep in mind.
My testing indicates MessageDialog.ShowAsync() may actually await but without the dialog being shown if Window.Current.Active() hasn't been called yet, so from a code execution standpoint it'll look like everything is working but yet no dialog is displayed.
If the goal is to display an exception during start-up, I can think of two options (there may be more).
Capture the exception information and then wait to display it until after Window.Current.Activate(). This can work if the exception is such that the application can recover from it and continue with start-up. For example, if there is an error restoring saved state information the app might want to report that to the user but then continue to start-up as if there was no saved state.
If the situation is such that the app is throwing up its hands and intending to terminate, but wants to let the user know what happened, then another solution might be to have a separate dedicated code block/method that plugs a new clean frame into Windows.Current.Content, activates it using Windows.Current.Activate(), and then invokes MessageDialog.ShowAsync(). I haven't experimented with this approach so I'm not sure if other conditions also need to be met like possibly loading a page into the frame.
use for page like:
private async void AppBarButton_Click(object sender, RoutedEventArgs e)
{
Windows.UI.Popups.MessageDialog a = new Windows.UI.Popups.MessageDialog("hello this is awth");
await a.ShowAsync();
}

Categories

Resources