Cannot display an alert on app launch Xamarin Forms - c#

I’m working on a Xamarin Forms app in which I want to perform some actions on launch. Part of the method that I want to call is responsible for checking whether some conditions are fulfilled, if not I want to display an alert (using DisplayAlert method) to inform user about the actions that he/she should take in order to allow for correct execution of apps functions. Unfortunately, on Android the alert is not being displayed, it doesn’t happen always, sometimes it gets displayed, but in most of the cases it doesn’t. After doing some research I think that the issue might be connected with threading. I added some breakpoints and I noticed that on Android the line of code responsible for displaying an alert is being executed before the page becomes visible. On iOS everything works fine (alert is being displayed when around half of the UI is shown). I have tried to execute everything from the main thread. On Android it helped partially, alert is being displayed correctly in more cases, although I still didn’t achieve 100% accuracy. Moreover, on iOS mentioned changes caused an app to get stuck on splash screen. Below there are two versions of the code that I’ve tried to use.
First the base code that I've started with:
public partial class App : Application
{
MainPage mainPage;
public static string DatabaseLocation = string.Empty;
public App(string databaseLocation)
{
//All of the UI code is written in C# in MainPage initializer
mainPage = new MainPage();
MainPage = new NavigationPage(mainPage);
DatabaseLocation = databaseLocation;
}
protected override async void OnStart()
{
base.OnStart();
//in DoThings there is a code containing DisplayAlert
await mainPage.DoThings();
}
}
Below is version of the code in which I've tried to put everything on main thread:
public partial class App : Application
{
MainPage mainPage;
public static string DatabaseLocation = string.Empty;
public App(string databaseLocation)
{
Device.BeginInvokeOnMainThread(() =>
{
mainPage = new MainPage();
MainPage = new NavigationPage(mainPage);
});
DatabaseLocation = databaseLocation;
}
protected override async void OnStart()
{
base.OnStart();
Device.BeginInvokeOnMainThread(async () =>
await mainPage.DoThings());
}
}
I have also tried to run from main thread the line of code specifically responsible for displaying an alert but it also didn't help. DoThings is a method inside MainPage.
public async Task DoThings() {
if (somethingMissing()){
Device.BeginInvokeOnMainThread(async () =>
await DisplayAlert("Error", "Fix errors", "Ok"));
}
else
{
DoStuff();
}
}

In MainPage you can try this code
public async Task DoThings() {
if (somethingMissing()){
Device.BeginInvokeOnMainThread(async () =>
await App.Current.MainPage.DisplayAlert("Error", "Fix errors", "Ok"));
}
else
{
DoStuff();
}
}

Related

Event when Xamarin page is FULLY loaded?

community.
In advance, I apologize if this really has a simple solution. I have a troublesome problem, considering my skill level with Xamarin, that I need to get resolved.
The problem is it seems like Xamarin doesn't have an event/function/etc to call when a page is FULLY displayed, as in you can see everything the page is supposed to display, and then that function is called. Quick note, what does not work for me is the OnAppearing function because it fires off too early before the screen is visible.
I was attempting to decipher a solution here. In this post, a user answers with the following code.
Here is step 1):
private bool _toggleTemp;
public bool ToggleTemp
{
get => _toggleTemp;
set => SetProperty(ref _toggleTemp, value);
}
Step 2)
LoadingVm.ToggleTemp = true;
Step 3)
<Switch IsToggled="{Binding ToggleTemp}" Toggled="Switch_OnToggled" IsVisible="False" />
Step 4)
private async void Switch_OnToggled(object sender, ToggledEventArgs e)
{
/* Your code goes here... */
}
First concern. I created a ViewModel file, it's inside of a folder called "ViewModels", and the user who posted the code in the link said to create a property in the view model, for some reason I'm getting an error stating "the name set property does not exist in the current context". So is it alright I swap that code out and just put the following instead? I mean it SEEMS like the same thing, right?
public bool Temp
{
get { return _toggleTemp; }
set { _toggleTemp = value; }
}
Second concern. I have no clue what "LoadingVm" is in his code. It doesn't come up for me. Is it because I'm missing a "using" at the top? But regardless, the whole code line was this "LoadingVm.ToggleTemp = true;", so he's just calling the function to set it to be true.
In the end, I'm ASSUMING that this will no doubt help me get the code working where I can do whatever I please AFTER the page is completely loaded, correct? I don't really see too many people disagreeing with the method the guy in the link has given, but if so, feel free to give other suggestions.
Again, please forgive me if this is a simple error. I'm still getting used to Xamarin and C# is still somewhat fresh in my mind from not having used it in a while.
You can use custom renderer of ContentPage to know if the view is fully loaded and then use MessagingCenter to notify your shared project:
In iOS:
[assembly:ExportRenderer (typeof(ContentPage), typeof(CameraPageRenderer))]
namespace App362.iOS
{
public class CameraPageRenderer : PageRenderer
{
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
Console.WriteLine("ViewDidAppear");
MessagingCenter.Send<object>(new object(), "ViewLoaded");
}
}
}
In Android:
[assembly: ExportRenderer(typeof(ContentPage), typeof(CameraPageRenderer))]
namespace App362.Droid
{
[Obsolete]
public class CameraPageRenderer : PageRenderer{
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
Console.WriteLine("OnAttachedToWindow");
MessagingCenter.Send<object>(new object(), "ViewLoaded");
}
}
}
In Shared project:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
Console.WriteLine("OnAppearing");
MessagingCenter.Subscribe<object>(new object(), "ViewLoaded", (sender) =>
{
// Do something whenever the "ViewLoaded" message is received
Console.WriteLine("Do something whenever the ViewLoaded message is received");
});
}
}

await Task.Delay() doesn't work on Xamarin.iOS using MVVMCross

I am working on a Xamarin.iOS app using mvvm architecture. The problem is, that if I try to run await Task.Delay(2000) in the Initialize method of the main ViewModel class, the app stops on the LaunchScreen without any error messages.
Without that line the app works perfectly.
MainViewModel.cs
public override async Task Initialize()
{
await Task.Delay(2000);
}
Any Ideas what I'm doing wrong?
Edit:
Initialize is beeing called by MVVMCross after I call awaitNavigationService.Navigate()
public class CustomAppStart : MvxAppStart
{
public CustomAppStart(IMvxApplication application, IMvxNavigationService navigationService) : base(application, navigationService)
{
}
protected override async Task NavigateToFirstViewModel(object hint = null)
{
await NavigationService.Navigate<MainViewModel>();
}
}
And by the documentation I should be able to use await safely: MvvmCross ViewModel Lifecycle
As #kistelekig pointed out above, he was able to resolve it by placing the task delay in the ViewAppearing lifecycle function of the view model instead. So it would look like this
public override async Task ViewAppearing()
{
base.ViewAppearing()
await Task.Delay(2000);
...
}

Execute same function on every page in Xamarin.Forms

I have a function that I need to perform to do some checks (e.g: CheckForUpdate, CheckNetworkConnection, CheckUserAuthorization, ...) on every page appearing or somehow before user request completed.
so I made a c# class and called it BasePage.cs:
public static class BasePage
{
public static async void CheckForUpdate()
{
// actual codes to check for updates are not included here
// just a sample alert
await App.Current.MainPage.DisplayAlert("Update", "There is a new version avaiable to update, would you like to download?", "Download", "Skip");
}
}
and used it in my pages like below:
LoginPage.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class LoginPage : ContentPage
{
public LoginPage()
{
InitializeComponent();
}
protected async override void OnAppearing()
{
await Task.Run(() => BasePage.CheckForUpdate());
}
}
I don't know if this is best practice or not (I guess not) but it's not displaying alert anyhow.
so my question is what is the best way to execute functions on every page and why the code above doesn't work.
Your code doesn't seem to run on UI thread. Just use Device.BeginInvokeOnMainThread, try like below
protected override void OnAppearing()
{
Device.BeginInvokeOnMainThread(() => {
BaseClass.CheckForUpdate();
});
}
Hope it help you.

WPF 4.5: How to create child thread and continue task to UI main thread?

I use WPF 4.5 and MVVM Caliburn Micro and have following WPF code:
public class MainViewModel: Screen
{
public MainViewModel()
{
if (!ConnectServer())
{
Console.WriteLine("Connection failed");
return;
}
// Following method can only be run if server connection established
ProcessThis();
}
}
My code above has only one chance to connect and if it is failed it shows the view and do nothing. If I use while(!ConnectServer()) it will block the UI thread all the time, means nothing will be displayed to user while the connection is still failed.It is very ugly.
What I want:
if the connection is failed, means ConnectServer() returns false, it should wait for 10 seconds and try to connect again and again (eg. call a method RetryConnect()) till it is successful WITHOUT blocking the UI thread.
And after it is connected, it should continue to main thread and run ProcessThis().
Theoretically I know it needs background separated thread, but I don't know how to implement it simple and good. Please feel free to use my sample code to explain. Thank you in advance.
To start a background task you can use Task.Run method.
And to execute a code in the main thread you can use Dispatcher of the page (in case of VM context I have placed call of Application.Current.Dispatcher)
public class MainViewModel: Screen
{
public MainViewModel()
{
Task.Run(() =>
{
while (!ConnectServer())
{
Console.WriteLine("Connection failed");
Thread.Sleep(10*1000);
}
// Following method can only be run if server connection established
Application.Current.Dispatcher.Invoke(ProcessThis);
}
}
}
Instead of usage of Dispatcher you can utilize a new async/await functionality to implement it.
public class MainViewModel: Screen
{
public MainViewModel()
{
Initialize();
}
}
private async void Initialize()
{
await Task.Run(async () =>
{
while (!ConnectServer())
{
Console.WriteLine("Connection failed");
await Task.Delay(10*1000);
}
}
// Following method can only be run if server connection established
ProcessThis();
}

How do I update a WPF Window with information from a background thread?

I'm creating a wpf application which performs many tasks in the backgound, but still requires the UI to be responsive and to display the status of the various background tasks. It also has the option to not display the UI at all, in which case the status messages should be discarded without creating an instance of the main form at all.
What I've attempted is to remove
StartupUri="MainWindow.xaml"
from App.xaml. Then, in App.xaml.cs, I have
`
public App()
{
Startup += new StartupEventHandler(App_Startup);
}
void App_Startup(object sender, StartupEventArgs e)
{
// Code which loads application settings is here
if (pf.ShowUI)
{
MainWindow mainWindow = new MainWindow();
mainWindow.Show();
}
// The background processes will begin here.
}`
This shows the main form, if necessary, and starts all the background processes. This part works.
In order to send messages from the background to the UI, I've implemented a very basic messenger:
`
internal interface IMessageHandler
{
void ReceiveMessage(string message);
}
internal class Messenger
{
private static List<IMessageHandler> _handlers;
internal static void AddHandler(IMessageHandler handler)
{
_handlers.Add(handler);
}
internal static void RemoveHandler(IMessageHandler handler)
{
try
{
_handlers.Remove(handler);
}
catch (Exception ex) {}
}
internal static void Broadcast (string message)
{
foreach (IMessageHandler handler in _handlers)
{
handler.ReceiveMessage(message);
}
}
}`
The main form implements the IMessageHandler interface, and adds itself to the Messenger as a handler when it starts up. Any process that needs to send a status to the main form just needs to call the Broadcast method of the messenger.
The problem I'm having, is that the messages are not being shown on the form until the background processes complete, and the UI is locked up until then as well.
The code in the UI which handles receiving messages is as follows:
`
public void ReceiveMessage(string message)
{
Dispatcher.Invoke(DispatcherPriority.Normal,
new Action<string>(AddText),
message);
}
private void AddText(string text)
{
Label myLabel = new Label();
myLabel.Content = text;
stackPanel1.Children.Add(myLabel);
if (stackPanel1.Children.Count > 5)
{
stackPanel1.Children.RemoveAt(0);
}
}`
Why are my background processes freezing my UI? What can I do to prevent it? And why is my UI not updating with the status messages?
Maybe this is your problem:
Dispatcher.Invoke(DispatcherPriority.Normal,
new Action<string>(AddText),
message);
Try change this to,
Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action<string>(AddText),
message);
Because when you use Invoke, the method gets executed and the application waits for it to complete, but with BeginInvoke the method is invoked Asychnronously and the application continues to execute while the method referenced in BeginInvoke is executed.
Read this: whether to use Invoke or BeginInvoke
Use the below code to avoid freezing the UI. In my application I have used a BackgroundWorker class. By changing anything on the form using code, a run time error is thrown.
I am using the below code to avoid this and it works perfectly for me.
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate()
{
rtf_status.AppendText("Validating XML against schema...Please wait\n");
}));
Note the part after the between braces ('{}') this is where you should place your code if you wish to change something on the form.

Categories