Search code examples
xamarin.formsxamarin.android

Screen flickers between black and white before freezing on startup after idling


I have an issue with my Xamarin Forms Android app where after idling (a number of hours), when the app resumes there will be a blank screen and then it rapidly flickers black and white for a few seconds and then goes blank and freezes up. I don't think it's getting as far as the PCL, and I get no crash logs for it. This happens on debug and release builds and on a number of real devices, including Android 13.

The only remedy is to close the app and reopen and then it works without an issue. It may coincide with the device theme changing from light to dark and vice versa and a long idle period. If the device theme is changed on demand and the app closed and reopened, it doesn't suffer from the flicker.

Epilepsy warning I have a video of it: https://drive.google.com/file/d/1baXvGLYpDoM9DUo5Y9L03HovCNfDOKSV/view?usp=share_link

I am really struggling to find where the offending code is, and I can't debug it because it doesn't happen for hours or days.

As for the app, there are two activities:

Splash activity

[Activity(MainLauncher = true, NoHistory = true)]
    public class SplashActivity : AppCompatActivity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            try
            {
                base.OnCreate(savedInstanceState);
                Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            }
            catch (Exception ex)
            {
                //Attempted error handling
            }
        }

        protected override void OnStart()
        {
            try
            {
                base.OnStart();
                StartActivity(new Intent(Application.Context, typeof(MainActivity)).PutExtras(Intent));
            }
            catch (Exception ex)
            {
                //Attempted error handling
            }
            finally
            {
                Finish();
            }
        }

        public override void OnBackPressed() { }
    }

And Main activity

[Activity(Icon = "@mipmap/ic_launcher", Theme = "@style/MainTheme", MainLauncher = false, LaunchMode = LaunchMode.SingleTask, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize, ResizeableActivity = false, NoHistory = false, Exported = true)]
    //auth0 intent filter
    public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity, IOnSuccessListener
    {
        INotificationActionService _notificationActionService;
        IDeviceInstallationService _deviceInstallationService;

        INotificationActionService NotificationActionService => _notificationActionService ??= ServiceContainer.Resolve<INotificationActionService>();

        IDeviceInstallationService DeviceInstallationService => _deviceInstallationService ??= ServiceContainer.Resolve<IDeviceInstallationService>();

        protected override void OnCreate(Bundle savedInstanceState)
        {
            try
            {
                TabLayoutResource = Resource.Layout.Tabbar;
                ToolbarResource = Resource.Layout.Toolbar;
                base.OnCreate(savedInstanceState);
                Bootstrap.Begin(() => new DeviceInstallationService());
                if (DeviceInstallationService.NotificationsSupported)
                {
                    FirebaseMessaging.Instance.GetToken().AddOnSuccessListener(this, this);
                }
                Rg.Plugins.Popup.Popup.Init(this);
                Xamarin.Essentials.Platform.Init(this, savedInstanceState);
                CrossCurrentActivity.Current.Init(this, savedInstanceState);
                Forms.Init(this, savedInstanceState);
                InitFontScale();
                CachedImageRenderer.Init(true);
                _ = typeof(SvgCachedImage);
                UserDialogs.Init(this);
                LoadApplication(new App());
                ProcessNotificationActions(Intent);
            }
            catch (Exception ex)
            {
                //Attempted error handling
            }
        }

        public override void OnBackPressed() => Rg.Plugins.Popup.Popup.SendBackPressed(base.OnBackPressed);

        public void OnSuccess(Java.Lang.Object result) => DeviceInstallationService.Token = result.ToString();

        private void InitFontScale()
        {
            var configuration = Resources.Configuration;
            configuration.FontScale = Math.Min(1f, configuration.FontScale);
            //0.85 small, 1 standard, 1.15 big,1.3 more bigger ,1.45 super big 
            var metrics = new DisplayMetrics();
#pragma warning disable CS0618 // Type or member is obsolete
            WindowManager.DefaultDisplay.GetMetrics(metrics);
#pragma warning restore CS0618 // Type or member is obsolete
            metrics.ScaledDensity = configuration.FontScale * metrics.Density;
            BaseContext.ApplicationContext.CreateConfigurationContext(configuration);
            BaseContext.Resources.DisplayMetrics.SetTo(metrics);
        }

        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
        {
            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }

        protected override void OnResume()
        {
            try
            {
                var culture = new CultureInfo("en-GB");
                CultureInfo.DefaultThreadCurrentCulture = culture;
                Thread.CurrentThread.CurrentCulture = culture;
                InitFontScale();
                base.OnResume();
            }
            catch (Exception ex)
            {
                //Attempted error handling
            }
        }

        protected override void OnNewIntent(Intent intent)
        {
            try
            {
                base.OnNewIntent(intent);
                Auth0.OidcClient.ActivityMediator.Instance.Send(intent.DataString);
                ProcessNotificationActions(intent);
            }
            catch (Exception ex)
            {
                //Attempted error handling
            }
        }

        private void ProcessNotificationActions(Intent intent)
        {
            try
            {
                if (intent?.HasExtra("action") == true)
                {
                    var bundle = intent.Extras;
                    var dict = bundle.KeySet().ToDictionary(key => key, key => bundle.Get(key).ToString());
                    var action = intent.GetStringExtra("action");
                    if (!string.IsNullOrEmpty(action))
                    {
                        NotificationActionService.TriggerAction(action, dict);
                    }
                }
            }
            catch (Exception ex)
            {
                //Attempted error handling
            }
        }
    }

And some styles

values styles.xml

<?xml version="1.0" encoding="utf-8" ?>
<resources>
    <style name="MainTheme" parent="MainTheme.Base">
        <item name="colorAccent">#3E94FF</item>
        <item name="android:datePickerDialogTheme">@style/CustomDatePickerDialog</item>
    </style>
    <style name="SplashTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowBackground">@drawable/splash</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:windowActionBar">false</item>
        <item name="android:windowIsTranslucent">true</item>
    </style>
    <style name="CustomDatePickerDialog" parent="ThemeOverlay.AppCompat.Dialog">
        <!--header background-->
        <item name="colorAccent">#3E94FF</item>
        <!--selected day-->
        <item name="android:colorControlActivated">#3E94FF</item>
        <!--cancel&ok-->
        <item name="android:textColor">#000000</item>
    </style>
</resources>

values-night styles.xml

<?xml version="1.0" encoding="utf-8" ?>
<resources>
    <style name="MainTheme" parent="MainTheme.Base">
        <item name="colorAccent">#3E94FF</item>
        <item name="android:datePickerDialogTheme">@style/CustomDatePickerDialog</item>
    </style>
    <style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
        <item name="android:windowBackground">@drawable/splash</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:windowActionBar">false</item>
        <item name="android:windowIsTranslucent">true</item>
    </style>
    <style name="CustomDatePickerDialog" parent="ThemeOverlay.AppCompat.Dialog">
        <!--selected day-->
        <item name="android:colorControlActivated">#3E94FF</item>
        <!--cancel&ok-->
        <item name="android:textColor">#ffffff</item>
        <!--body background-->
        <item name="android:windowBackground">#2F343C</item>
        <!--days of the month-->
        <item name="android:textColorPrimary">#9BA3B1</item>
        <!--days of the week-->
        <item name="android:textColorSecondary">#ffffff</item>
    </style>
</resources>

The application tag in the manifest has the splash theme as the style android:theme="@style/SplashTheme"

Any help with pinpointing the cause would be very much appreciated. Thank you.

I have tried combining it into one activity to see if it was the transition from the splash to the main activity.

I have also changed it to the splash activity explicitly starting the main activity and finishing itself, but it made no difference.

As I thought it may be the main activity not getting closed properly, I have also used a timer to finish the activity after 5 minutes in the background, but it still occurred.


Solution

  • I needed to set the main page in the app.xaml.cs constructor.

    Coming back to answer my own question - the splash screen separate activity was part of the problem, but the issue with the flickering screen still occurred when the activity resumed after a long time.

    My original code was initialising the component in the constructor but doing all the logic in OnStart - working out whether the user was authenticated and setting the main page either to the home page or the login page. When the app resumed after a long time, OnStart would be called again and a second MainPage would be set. The two main pages would do battle and the app would freeze. Since defaulting the main page in the constructor, and then overriding it on OnStart, the app has not frozen again.

    public App()
    {
        InitializeComponent();
        MainPage = new LoginPage();
    }