I want to run some code while the splash screen is shown in a MAUI application. The official documentation has only instruction about the image (so far).
I assume I need to do a solution for each platform. I have tried to follow the instruction for Xamarin on Android and getting pretty close. I have created this Activity:
[Activity(Theme = "#style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
public class SplashActivity : MauiAppCompatActivity
{
static readonly string TAG = "X:" + typeof(SplashActivity).Name;
public override void OnCreate(Bundle savedInstanceState, PersistableBundle persistentState)
{
base.OnCreate(savedInstanceState, persistentState);
Log.Debug(TAG, "SplashActivity.OnCreate");
}
// Launches the startup task
protected override void OnResume()
{
base.OnResume();
Task startupWork = new Task(() => { SimulateStartup(); });
startupWork.Start();
}
// Simulates background work that happens behind the splash screen
async void SimulateStartup ()
{
Log.Debug(TAG, "Performing some startup work that takes a bit of time.");
await Task.Delay (3000); // Simulate a bit of startup work.
Log.Debug(TAG, "Startup work is finished - starting MainActivity.");
//StartActivity(new Intent(MainApplication.Context, typeof (MainActivity)));
}
}
My problem is that when SimulateStartup starts to run the UI is launching, despite MainActivity is never created.
In then end of MauiProgram.CreateMauiApp() I have added this:
var app = builder.Build();
Task.Run(async () =>
{
await Task.Delay(4000);
var bootstrap = app.Services.GetRequiredService<MyBootstrapService>();
await bootstrap.SetupAsync();
}).Wait();
return app;
It works on Android. In Windows there are no splash screen, so user will se nothing until the bootstrapping is ready.
Related
I'm getting started with WinUI3. Having no previous experience with UWP or WPF or even much C#, it's tough going.
Today's question is how to display a simple dialog at startup. Consider that we start with a simple app, as generated by Visual Studio. We have a MainWindow class defined in MainWindow.xaml.cs and its associated XAML (MainWindow.xaml). I believe the class is called a code-behind class.
So I want to do something as simple as display a dialog when the (desktop) app runs. It looks as though a ContentDialog is the way to go. But how to display it? As I understand it, I'm going to need to set the XamlRoot, so naively I tried this:
public MainWindow()
{
this.InitializeComponent();
DisplayDialog();
}
private async void DisplayDialog()
{
var dlg = new ContentDialog();
dlg.XamlRoot = this.Content.XamlRoot; // <-- set the XAML root here, but it's null
dlg.Content = "Hello World";
await dlg.ShowAsync();
}
This doesn't work. When called, the main window's XAML root is null and trying to show the dialog throws an exception:
How do I detect when it's ok to use the main window's XAML root? This issue seems to hint at an OnLoaded event, but I can't find anything about OnLoaded events in WinUI. Did I miss something?
This only way I can get this to work is to hook into the window's button and respond to its Loaded event, i.e.
public MainWindow()
{
this.InitializeComponent();
myButton.Loaded += MyButton_Loaded; // <-- hack
}
private void MyButton_Loaded(object sender, RoutedEventArgs e)
{
DisplayDialog();
}
private async void DisplayDialog()
{
var dlg = new ContentDialog();
dlg.XamlRoot = this.Content.XamlRoot; // <-- this is non-null now!
dlg.Content = "Hello World";
await dlg.ShowAsync();
}
But this feels really dirty. I don't even know if it's guaranteed that the XamlRoot will be non-null just because a button has loaded. And anyway, latching onto the button load seems very much like a hack. It relies on there being a button for one thing!
So how should I achieve the simple task of putting a dialog on the screen when all I have is the main window?
All help very gratefully received. Please try to make any answers as newbie-friendly as possible.
Unfortunately, the MainWindow has no Loaded events. What you can do is to work with a Page instead. So basically use the MainWindow as a "Window" and use Pages for your contents.
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.Loaded += MainPage_Loaded;
}
private async void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var dlg = new ContentDialog();
dlg.XamlRoot = this.XamlRoot;
dlg.Content = "Hello World";
await dlg.ShowAsync();
}
}
MainWindow.xaml
<Window
x:Class="ContentDialogs.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:ContentDialogs"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<local:MainPage />
</Window>
A WinUI Window is just an abstraction of each of the low-level window implementations used by supported (UWP and desktop) app models.
The "trick" is to handle the Loaded event of the window's root element. The default template includes a Grid root element. A "generic" solution could be implemented something like this:
public MainWindow()
{
this.InitializeComponent();
var root = this.Content as FrameworkElement;
if (root != null)
root.Loaded += async (s, e) => await DisplayDialog();
}
private async Task DisplayDialog()
{
var dlg = new ContentDialog();
dlg.XamlRoot = this.Content.XamlRoot;
dlg.Content = "Hello World";
await dlg.ShowAsync();
}
Also tote that an async method, with the exception of event handlers, should return a Task or a Task<T> and be awaited by the caller.
I'm developing some application using Xamarin Forms that has a function of route following. When it is used in real conditions on a vehicle it crashes whithout any logs in spite of the fact that I'm using AppCenter, i.e. in App.xaml.cs OnStart I added
protected async override void OnStart()
{
AppCenter.Start("android=__mycode___" +
"uwp={Your UWP App secret here};" +
"ios={Your iOS App secret here}",
typeof(Analytics), typeof(Crashes));
bool hadMemoryWarning = await Crashes.HasReceivedMemoryWarningInLastSessionAsync();
ErrorReport crashReport = await Crashes.GetLastSessionCrashReportAsync();
if (crashReport != null)
{
Analytics.TrackEvent(crashReport.StackTrace);
}
}
In fact I tested my app in AppCenter by using Crashes.GenerateTestCrash() and got a test crash report. Besides I caught some errors using this approach. But now there is at least one reason that makes my app crash without any messages to AppCenter. Are there any clues how to resolve this problem?
Try using an actual asynchronous event handler.
private event EventHandler onStart = delegate { };
protected override void OnStart() {
onStart += handleStart; //subscribe
onStart(this, EventArgs.Empty); //raise event
}
private async void handleStart(object sender,EventArgs args) {
onStart -= handleStart; //unsubscribe
try {
AppCenter.Start("android=__mycode___" +
"uwp={Your UWP App secret here};" +
"ios={Your iOS App secret here}",
typeof(Analytics), typeof(Crashes));
bool hadMemoryWarning = await Crashes.HasReceivedMemoryWarningInLastSessionAsync();
ErrorReport crashReport = await Crashes.GetLastSessionCrashReportAsync();
if (crashReport != null) {
Analytics.TrackEvent(crashReport.StackTrace);
}
} catch (Exception ex) {
//...handle exception or log as needed
}
}
OnStart is a simple void method and not an actual event handler. This mean that when made async it will become a fire and forget function which wont allow you to catch any exceptions that may have occurred within it.
If it is not caught at startup then it probably means that you are doing something similar somewhere within the app using a non event async void that goes uncaught and is crashing the application.
Start by doing a search for any async void in your code and checking to make sure that it is an actual event handler.
I have a Xamarin.Forms app and I am using FreshMvvm framework.
If I do this from ViewIsAppearing method of FirstPageModel:
CoreMethods.PushPageModel<SecondPageModel>();
I go the "SecondPageModel". Then, when I am in the "SecondPageModel" if I do:
CoreMethods.PopPageModel();
or press hard back button, or press title bar back button not works in Android (anything happens). I am using FreshMasterDetailNavigationContainer.
In iOS it works OK, I get back to FirstPageModel.
This is because ViewIsAppearing will always be called when the page starts displaying on the screen. When you pop the second page then go to the first page, the first page's ViewIsAppearing will fire again. It caused a dead cycle and prohibited your app from returning to the first page.
Add a property to avoid that:
bool isInitialized;
public FirstPageModel()
{
// ...
isInitialized = true;
}
protected async override void ViewIsAppearing(object sender, EventArgs e)
{
base.ViewIsAppearing(sender, e);
if (isInitialized)
{
await Task.Delay(100);
await CoreMethods.PushPageModel<SecondPageModel>();
isInitialized = false;
}
}
iOS may optimize this process, but I still recommend you to add this judgment statement.
Update:
Call it when your app has reached the main thread.
protected override void ViewIsAppearing(object sender, EventArgs e)
{
base.ViewIsAppearing(sender, e);
if (isInitialized)
{
Device.BeginInvokeOnMainThread(() =>
{
CoreMethods.PushPageModel<SecondPageModel>();
isInitialized = false;
});
}
}
I am working on a UWP app generated by Unity. Following the documentation on StartUpTasks here I have set the app to launch at startup, however when it does it launches minimized in the taskbar. I need the app to be open when it launches, but do not know what to call and where to make this happen.
This is the code generated by Unity:
namespace UnityToUWPApp
{
class App : IFrameworkView, IFrameworkViewSource
{
private WinRTBridge.WinRTBridge m_Bridge;
private AppCallbacks m_AppCallbacks;
public App()
{
SetupOrientation();
m_AppCallbacks = new AppCallbacks();
}
public virtual void Initialize(CoreApplicationView applicationView)
{
applicationView.Activated += ApplicationView_Activated;
CoreApplication.Suspending += CoreApplication_Suspending;
// Setup scripting bridge
m_Bridge = new WinRTBridge.WinRTBridge();
m_AppCallbacks.SetBridge(m_Bridge);
m_AppCallbacks.SetCoreApplicationViewEvents(applicationView);
}
private void CoreApplication_Suspending(object sender, SuspendingEventArgs e)
{
}
private void ApplicationView_Activated(CoreApplicationView sender, IActivatedEventArgs args)
{
CoreWindow.GetForCurrentThread().Activate();
}
public void SetWindow(CoreWindow coreWindow)
{
m_AppCallbacks.SetCoreWindowEvents(coreWindow);
m_AppCallbacks.InitializeD3DWindow();
}
public void Load(string entryPoint)
{
}
public void Run()
{
m_AppCallbacks.Run();
}
public void Uninitialize()
{
}
[MTAThread]
static void Main(string[] args)
{
var app = new App();
CoreApplication.Run(app);
}
public IFrameworkView CreateView()
{
return this;
}
private void SetupOrientation()
{
Unity.UnityGenerated.SetupDisplay();
}
}
}
How can I ensure that when the app starts it is open and active?
UWP apps that are set as Startup task get launched as minimized/suspended by design. This is to ensure a good user experience for Store app by default and avoid having a ton of apps launch into the user's face on boot.
Classic Win32 apps as well as desktop bridge apps can continue to start in the foreground for backwards compat/consistency reasons.
To accomplish your goal, you can include a simple launcher Win32 exe in your package and set that as startup task. Then when it starts you just launch the UWP from there into the foreground. Note that doing this will require you to declare the "runFullTrust" capability, which will require an additional onboard review for the Microsoft Store.
To declare the Win32 exe as startup task, follow the "desktop bridge" section from this doc topic:
https://learn.microsoft.com/en-us/uwp/api/Windows.ApplicationModel.StartupTask
EDIT: It is now clear that the problem is not the the app, but the webpage. A problem that so far only has been spotted in a android-4.4.2-webview.
I have WebViews with a custom WebChromeClient. When something wants to go full screen on a 4.2.2 or 4.3 device the custom WebChromeClient's onShowCustomView is invoked. However with 4.4.2 (specifically I've checked on the devices Nexus 7 and Galaxy S4) it has worked with HTML 5 videos, but flash video plugins trying to go full screen does not invoke onShowCustomView. Other than that the flash video plugins work fine.
I have checked that the WebChromeClient is being added on a 4.4.2 device and it is.
public final WebChromeClient fullscreenWebChromeClient = new WebChromeClient() {
private View fullscreen = null;
private WebChromeClient.CustomViewCallback fullscreenCallback;
#Override
public void onShowCustomView(View view, CustomViewCallback callback) {
super.onShowCustomView(view, callback);
fullscreenCallback = callback;
if (fullscreenMode) {
fullscreenCallback.onCustomViewHidden();
return;
}
fullscreen = view;
fullscreenMode = true;
pager.setVisibility(View.GONE);
actionbarGestureDetector.setVisibility(View.GONE);
pageViewWrapper.addView(fullscreen);
}
#Override
public void onHideCustomView() {
super.onHideCustomView();
if (! fullscreenMode) {
return;
}
pageViewWrapper.removeView(fullscreen);
pager.setVisibility(View.VISIBLE);
actionbarGestureDetector.setVisibility(View.VISIBLE);
fullscreenCallback.onCustomViewHidden();
fullscreen = null;
fullscreenMode = false;
}
};
Somewhere else:
pageWebView.setWebChromeClient(parentActivity.fullscreenWebChromeClient);