Dynamically change android's accent color

Viewed 1997

I set Android's accent color to grey, so it would look normal in any theme (light or dark). And gray works great for edit control for example, but it turns out that is also used in alert cancel button's text. So now it looks fine in light theme, but very bad in the dark one.

How can I change colorAccent for Android dynamically from Xamarin.Forms app?

enter image description here enter image description here

Edit: Here is my theme changing code as of now. (I'm not using AppThemeBinding since this approach allows to more than two themes)

4 Answers

In Xamarin Forms, we could use DependencyService to call native method. Fortunately, Android document provide the method setLocalNightMode to modify the local DarkMode. We should note that this mehtod can not modify the configure of Settings for the Mobile.

Now we can create a IDarkModeService interface:

public interface IDarkModeService
{
    void SetDarkMode(bool value);
}

Then implement its method in Android solution:

public class DarkModeService : IDarkModeService
{
    public void SetDarkMode(bool value)
    {
        if (value)
        {
            
     MainActivity.instance.Delegate.SetLocalNightMode(AppCompatDelegate.ModeNightYes);
            MainActivity.instance.Recreate();
        }
        else
        {
       
     MainActivity.instance.Delegate.SetLocalNightMode(AppCompatDelegate.ModeNightNo);
            MainActivity.instance.Recreate();
        }
      
    }
}

Here we need to create a static instance from MainActivity

public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    public static MainActivity instance { set; get; }

    protected override void OnCreate(Bundle savedInstanceState)
    {
        TabLayoutResource = Resource.Layout.Tabbar;
        ToolbarResource = Resource.Layout.Toolbar;

        instance = this;

        base.OnCreate(savedInstanceState);

        Xamarin.Essentials.Platform.Init(this, savedInstanceState);
        global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
        LoadApplication(new App());
    }
   
}

}

And not forgetting to add configure inside styles.xml to make the app support DarkMode:

<style name="MainTheme" parent="Theme.AppCompat.DayNight.NoActionBar"></style>

Last, we could call the dependency method in Xamarin Forms as follows:

private async void ShowDialog_Clicked(object sender, EventArgs e)
{
    await DisplayAlert("Alert", "You have been alerted", "OK");
}

private void SetDarkMode_Clicked(object sender, EventArgs e)
{
    DependencyService.Get<IDarkModeService>().SetDarkMode(true);
}

private void CancelDarkMode_Clicked(object sender, EventArgs e)
{
    DependencyService.Get<IDarkModeService>().SetDarkMode(false);
}

The effect:

enter image description here

==================================Update==================================

If need to custom style of each Theme, you could exchange Theme on runtime.

First, you could store a Theme flag(DarkMode) in Xamrin Forms:

private void SetDarkMode_Clicked(object sender, EventArgs e)
{
    Preferences.Set("DarkMode", true);
    DependencyService.Get<IDarkModeService>().SetDarkMode(true);
}

private void CancelDarkMode_Clicked(object sender, EventArgs e)
{
    Preferences.Set("DarkMode", false);
    DependencyService.Get<IDarkModeService>().SetDarkMode(false);
}

Then add each Theme style inside styles.xml:

<?xml version="1.0" encoding="utf-8" ?>
<resources>

  <style name="MainTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
   
  </style>

  <style name="DayTheme" parent="MainTheme">
   
  </style>

  <style name="NightTheme" parent="MainTheme" >
    <item name="buttonBarPositiveButtonStyle">@style/positiveBtnStyle</item>
    <item name="buttonBarNegativeButtonStyle">@style/negativeBtnstyle</item>
  </style>

  <!--style of sure button-->
  <style name="positiveBtnStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
    <item name="android:textColor">#0000ff</item>
  </style>

  <!--style of cancel button-->
  <style name="negativeBtnstyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
    <item name="android:textColor">#999999</item>
  </style>

</resources>

Last, change the Theme before create view in MainActivity.cs:

public static MainActivity instance { set; get; }
protected override void OnCreate(Bundle savedInstanceState)
{
    TabLayoutResource = Resource.Layout.Tabbar;
    ToolbarResource = Resource.Layout.Toolbar;

    instance = this;

    var darkMode = Preferences.Get("DarkMode", false);
    if (darkMode)
    {
        this.SetTheme(Resource.Style.NightTheme);
    }
    else
    {
        this.SetTheme(Resource.Style.DayTheme);
    }

    base.OnCreate(savedInstanceState);

    Xamarin.Essentials.Platform.Init(this, savedInstanceState);
    global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
    LoadApplication(new App());
}

Now we could see the color style of button will change:

enter image description here

you can't, because the accent color is defined in the theme and themes are read-only. I assume that dynamically means programmatically.

I found the solution!

  1. All you have to put accent (or any other) color for light theme in MyApp.Android\Resources\values\colors.xml and for dark theme in MyApp.Android\Resources\values-night\colors.xml. Then reference that color by name in your theme in styles.xml <item name="colorAccent">@color/colorAccent</item>. Now, when device would switch to dark theme accent color also would change.
  2. Now. What if you have manual theme control in your app? You can force Android to display color for light or dark theme. Add similar interface to the shared project:
namespace MyApp.Core.Models.InterplatformCommunication
{
    public interface INightModeManager
    {
        NightModeStyle DefaultNightMode { get; set; }
    }

    public enum NightModeStyle
    {
        /// <summary>
        /// An unspecified mode for night mode.
        /// </summary>
        Unspecified = -100,

        /// <summary>
        /// Mode which uses the system's night mode setting to determine if it is night or not.
        /// </summary>
        FollowSystem = -1,

        /// <summary>
        /// Night mode which uses always uses a light mode, enabling non-night qualified resources regardless of the time.
        /// </summary>
        No = 1,

        /// <summary>
        /// Night mode which uses always uses a dark mode, enabling night qualified resources regardless of the time.
        /// </summary>
        Yes = 2,

        /// <summary>
        /// Night mode which uses a dark mode when the system's 'Battery Saver' feature is enabled, otherwise it uses a 'light mode'.
        /// </summary>
        AutoBattery = 3
    }
}
  1. Add this implementation in Android project. Setting AppCompatDelegate.DefaultNightMode forces the app to load resources for light or dark theme without restarting the app.
[assembly: Xamarin.Forms.Dependency(typeof(NightModeManager))]
namespace MyApp.Droid.Dependences
{
    public class NightModeManager : INightModeManager
    {
        public NightModeStyle DefaultNightMode
        {
            get => (NightModeStyle)AppCompatDelegate.DefaultNightMode;
            set => AppCompatDelegate.DefaultNightMode = (int)value;
        }
    }
}
  1. Add this logic when changing theme of your app (AppTheme is a custom enum):
private static void UpdateNativeStyle(AppTheme selectedTheme)
        {
            NightModeStyle style = selectedTheme switch
            {
                AppTheme.Dark => NightModeStyle.Yes,
                AppTheme.Light => NightModeStyle.No,
                AppTheme.FollowSystem => NightModeStyle.FollowSystem,
                _ => throw new InvalidOperationException("Unsupported theme"),
            };

            var nightModeManager = DependencyService.Get<INightModeManager>();
            nightModeManager.DefaultNightMode = style;
        }

More info about this:

As as side note, after setting:

AppCompatDelegate.DefaultNightMode = ... 

you need to recreate the activity to apply changes to current Page:

activity.Recreate();

For example, if you are using CrossCurrentActivity plugin for Xamarin, you can do:

CrossCurrentActivity.Current.Activity.Recreate();
Related