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
    {
        Task<Theme> GetOperatingSystemTheme();
    }

    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 = await DependencyService.Get<IEnvironment>().GetOperatingSystemTheme(); 
            
            SetTheme(theme);
        }
        
        protected override async void OnResume()
        {
            base.OnResume();
            
           	Theme theme = await 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 async Task<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 = await 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;
            }
        }
        
        static Task<UIViewController> GetVisibleViewController()
        {
            // UIApplication.SharedApplication can only be referenced on by Main Thread, so we'll use Device.InvokeOnMainThreadAsync which was introduced in Xamarin.Forms v4.2.0
            return Device.InvokeOnMainThreadAsync(() =>
            {
            	var rootController = UIApplication.SharedApplication.KeyWindow.RootViewController;

	    		switch (rootController.PresentedViewController)
		    	{
			    	case UINavigationController navigationController:
				    	return navigationController.TopViewController;

    				case UITabBarController tabBarController:
	    				return tabBarController.SelectedViewController;

		    		case null:
			    		return rootController;

				    default:
					    return rootController.PresentedViewController;
    			}
            }
        }
    }
}

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> 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 Task.FromResult(Theme.Dark);
                    
                    case UiMode.NightNo:
                        return Task.FromResult(Theme.Light);
                        
                    default:
                        throw new NotSupportedException($"UiMode {uiModelFlags} not supported");
                }
            }
            else
            {
                return Task.FromResult(Theme.Light);
            }
        }
    }
}