Check for Dark Mode in Xamarin.Forms

Both iOS 13 and Android Q now let the user enable Dark Mode for the operating system. Let's explore how to check whether the user has enabled Dark Mode in our Xamarin.Forms apps.

Check for Dark Mode in Xamarin.Forms

Update (10 April 2020): Custom Renderers are no longer required to implement theming. Xamarin.Forms has introduced the following APIs  in Xamarin.Forms Xamarin.Forms v4.6.0.616-pre4:

  • Xamarin.Forms.Application.Current.RequestedTheme
  • Xamarin.Forms.Application.Current.RequestedThemeChanged

You can find the completed solution that uses these updated APIs in this app:


Both iOS 13 and Android Q now let the user enable Dark Mode for the operating system. Let's explore how to check if the user has enabled Dark Mode in our Xamarin.Forms apps!

You can find the completed solution in the following apps:

Xamarin.Forms

In our Xamarin.Forms project, let's create an interface called IEnvironment and an enum called Theme that will be used by the platform-specific libraries

using System.Threading.Tasks;

namespace MyNamespace
{
    public interface IEnvironment
    {
        Theme GetOperatingSystemTheme();
        Task<Theme> GetOperatingSystemThemeAsync();
    }

    public enum Theme { Light, Dark }
}

After we've added the platform-specific code (in the Xamarin.iOS and Xamarin.Android sections below), we'll be able to detect the Theme from our Xamarin.Forms code. I recommend checking/setting the theme in Application.OnStart and Application.OnResume to ensure that our app always adhere's to the user's preference.

using Xamarin.Forms;

namespace MyNamespace
{
    public App : Application
    {
        // ...
        
        protected override async void OnStart()
        {
            base.OnStart();

            Theme theme = DependencyService.Get<IEnvironment>().GetOperatingSystemTheme(); 
            
            SetTheme(theme);
        }
        
        protected override async void OnResume()
        {
            base.OnResume();
            
           	Theme theme = DependencyService.Get<IEnvironment>().GetOperatingSystemTheme(); 
            
            SetTheme(theme);            
        }
        
        void SetTheme(Theme theme)
        {
            //Handle Light Theme & Dark Theme
        }
    }    
}

Xamarin.iOS

In our Xamarin.iOS project, we will retrieve the TraitCollection.UserInterfaceStyle value from the current UIViewController and check whether it is UIUserInterfaceStyle.Light or UIUserInterfaceStyle.Dark.

These APIs were introduced in iOS 12.0, so we'll first check whether the device is running iOS 12 (or higher). If it is on a earlier version of iOS, we will return Theme.Light.

using System;
using UIKit;
using Xamarin.Forms;
using MyNamespace;
using MyNamespace.iOS;

[assembly: Dependency(typeof(Environment_iOS))]
namespace MyNamespace.iOS
{
    public class Environment_iOS : IEnvironment
    { 
        public Theme GetOperatingSystemTheme()
        {
            //Ensure the current device is running 12.0 or higher, because `TraitCollection.UserInterfaceStyle` was introduced in iOS 12.0
            if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
            {
                var currentUIViewController = GetVisibleViewController();
            
                var userInterfaceStyle = currentUIViewController.TraitCollection.UserInterfaceStyle;

                switch (userInterfaceStyle)
                {
                    case UIUserInterfaceStyle.Light:
                        return Theme.Light;
                    case UIUserInterfaceStyle.Dark:
                        return Theme.Dark;
                    default:
                        throw new NotSupportedException($"UIUserInterfaceStyle {userInterfaceStyle} not supported");
                }
            }
            else
            {
                return Theme.Light;
            }
        }
        
        // UIApplication.SharedApplication can only be referenced by the Main Thread, so we'll use Device.InvokeOnMainThreadAsync which was introduced in Xamarin.Forms v4.2.0
        public async Task<Theme> GetOperatingSystemThemeAsync() =>
        	Device.InvokeOnMainThreadAsync(GetOperatingSystemTheme);
        
		static UIViewController GetVisibleViewController()
        {
            UIViewController viewController = null;

            var window = UIApplication.SharedApplication.KeyWindow;

            if (window.WindowLevel == UIWindowLevel.Normal)
                viewController = window.RootViewController;

            if (viewController is null)
            {
                window = UIApplication.SharedApplication
                    .Windows
                    .OrderByDescending(w => w.WindowLevel)
                    .FirstOrDefault(w => w.RootViewController != null && w.WindowLevel == UIWindowLevel.Normal);                    

                viewController = window?.RootViewController ?? throw new InvalidOperationException("Could not find current view controller.");
            }

            while (viewController.PresentedViewController != null)
                viewController = viewController.PresentedViewController;

            return viewController;
        }
    }
}

Xamarin.Android

In our Xamarin.Android project, we'll retrieve Configuration.UiMode from the current Context and check whether it is UiMode.NightYes or UiMode.NightNo.

Note: This code requires the Plugin.CurrentActivity NuGet Package. Make sure to first add this NuGet package to your Xamarin.Android project.

using System;
using System.Threading.Tasks;
using Android.Content.Res;
using Plugin.CurrentActivity;
using Xamarin.Forms;

using MyNamespace;
using MyNamespace.Android;

[assembly: Dependency(typeof(Environment_Android))]
namespace MyNamespace.Android
{
    public class Environment_Android : IEnvironment
    {
	    public Task<Theme> GetOperatingSystemThemeAsync()  =>
        	Task.FromResult(GetOperatingSystemTheme());
            
        public Theme GetOperatingSystemTheme()
        {
            //Ensure the device is running Android Froyo or higher because UIMode was added in Android Froyo, API 8.0
            if(Build.VERSION.SdkInt >= BuildVersionCodes.Froyo)
            {
                var uiModeFlags = CrossCurrentActivity.Current.AppContext.Resources.Configuration.UiMode & UiMode.NightMask;
                
                switch(uiModelFlags)
                {
                    case UiMode.NightYes:
                        return Theme.Dark;
                    
                    case UiMode.NightNo:
                        return Theme.Light;
                        
                    default:
                        throw new NotSupportedException($"UiMode {uiModelFlags} not supported");
                }
            }
            else
            {
                return Theme.Light;
            }
        }
    }
}