I'm making an Android app. I made another UI for dark mode. So this is what I need; the app will switch to dark theme automatically by the local time. For example, when the sun goes down by the local time, app will be switched to dark mode.
Or another alternative is switching to dark mode by pre-setted time of the day. Hope you understand my problem. Please help me if anyone knows, I prefer the first option to do if it's possible. Thanks in advance.
Maybe you can have a look at AppCompatDelegate.setDefaultNightMode()
you simply define your theme with the parent of DayNight:
<style name="MyTheme" parent="Theme.AppCompat.DayNight">
<!-- Blah blah -->
</style>
and each style with:
<style name="Theme.AppCompat.DayNight"
parent="Theme.AppCompat.Light" />
or
<style name="Theme.AppCompat.DayNight"
parent="Theme.AppCompat" />
and then you can call : AppCompatDelegate.setDefaultNightMode()
with one of these:
MODE_NIGHT_NO. Always use the day (light) theme.
MODE_NIGHT_YES. Always use the night (dark) theme.
MODE_NIGHT_FOLLOW_SYSTEM (default). This setting follows the system’s setting, which on Android Q and above is a system setting (more on this below).
MODE_NIGHT_AUTO_BATTERY. Changes to dark when the device has its ‘Battery Saver’ feature enabled, light otherwise.
MODE_NIGHT_AUTO_TIME & MODE_NIGHT_AUTO. Changes between day/night based on the time of day.
you would typically do this in your own custom application class:
public class MyApplication extends Application {
public void onCreate() {
super.onCreate();
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_YES);
}
}
more info here
Quick way:
public class MainActivity extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//restore preferences
SharedPreferences settings0 = this.getSharedPreferences(PREFS_NAME, 0);
lightMode = settings0.getBoolean("key0", true);
//retrieve selected mode
if (lightMode) {
//light mode
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
} else {
//dark mode
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
}
Switch switch0 = findViewById(R.id.Switch0);
switch0.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (darkMode) {
text = "Mode: light";
//light mode
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_NO);
darkMode = false;
} else {
text = "Mode: dark";
//dark mode
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
darkMode = true;
}
//save music preferences
SharedPreferences setting0 = getSharedPreferences(PREFS_NAME, 0);
SharedPreferences.Editor editor0 = setting0.edit();
editor0.putBoolean("key0", darkMode);
editor0.apply();
}
});
}
Related
let's suppose that customer A wants his app to be blue, while user B wants his app to be red, both app, are the same in execution, except for the colors and image logos. So, to change the app's colors based on customer login would be a violation of googles terms ?
Make in values folder a themes.xml file (like styles.xml)
There you can define 2 or more themes with colors that can you set later in your app for some type of user:
<resources>
<style name="AppTheme.White" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#color/white</item>
...
</style>
<style name="AppTheme.Black" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">#color/black</item>
...
</style>
</resources>
Set in your AndroidManifest.xml your theme in each activity that should be affected:
...
<activity android:name="com.example.YourApp.MainActivity"
...
android:theme="#style/AppTheme.White"/>
...
Make a class for saving the states, called Utility.class:
public class Utility {
public static void setTheme(Context context, int theme) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
prefs.edit().putInt(context.getString(R.string.prefs_theme_key), theme).apply();
}
public static int getTheme(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getInt(context.getString(R.string.prefs_theme_key), 1);
}
}
And in your TargetActivity.class you will set the method to apply the states with your specific user type:
public void updateTheme() {
if (Utility.getTheme(getApplicationContext()) <= 1) {
setTheme(R.style.AppTheme_White);
} else if (Utility.getTheme(getApplicationContext()) == 2) {
setTheme(R.style.AppTheme_Black);
}
}
To set the saved theme at start of the app just type that into your MainActivity.class:
private final static int THEME_WHITE = 1;
private final static int THEME_BLACK = 2;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
updateTheme();
}
public void updateTheme() {
if (Utility.getTheme(getApplicationContext()) <= THEME_WHITE) {
setTheme(R.style.AppTheme_White);
} else if (Utility.getTheme(getApplicationContext()) == THEME_BLACK) {
setTheme(R.style.AppTheme_Black);
}
}
That's how you work with different Themes. I think your question is answered by now :)
PS: Setting different colors for users isn't a violation against the terms of google. Google itself gave each letter a different color. ;)
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?
Edit: Here is my theme changing code as of now. (I'm not using AppThemeBinding since this approach allows to more than two themes)
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:
==================================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:
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!
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.
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
}
}
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;
}
}
}
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:
https://medium.com/androiddevelopers/appcompat-v23-2-daynight-d10f90c83e94
https://www.journaldev.com/19352/android-daynight-theme-night-mode
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();
I am working to implement a dark mode into my app. Right now I try to switch the UI mode between light and dark using a PreferenceFragment nested inside an AppCompatActivity. The App behaves like this, starting from the light theme as a default value:
If I select 'dark' in the ListPreference, the summary of the
preference changes, but the UI stays light in this and all the other activities.
If I select 'dark' a second time, the activity switches to dark theme, as well as the other activities in the backstack.
The same thing happens in reverse, when I want to switch back to the light theme.
So basically everything works, but you have to select the desired value in the ListPreference twice. The code for the ListPreference:
final Preference listPreferenceDesign = findPreference(PREF_DESIGN);
listPreferenceDesign.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object o) {
if (((ListPreference) preference).getValue().equals("light")) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
} else if (((ListPreference) preference).getValue().equals("dark")) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
}
return true;
}
});
What I tried so far:
Call getActivity.recreate()before the return true; statement
Call getActivity.recreate()after a short delay using a Handler
Call this.recreate() in the onResume() method of the parent Activity when a boolean changedDesignSetting was true
I am thankful for further help.
I finally get it to work using an OnSharedPreferenceChangeListener in the parent activity. Now the code in the parent activity goes like this:
getFragmentManager().beginTransaction().replace(R.id.settingsPlaceholderID, preferenceFragment).commit();
SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(PREF_DESIGN)) {
if (sharedPreferences.getString(key, "light").equals("light")) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
}
}
}
};
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
And in the PreferenceFragment I only have:
final Preference listPreferenceDesign = findPreference(PREF_DESIGN);
listPreferenceDesign.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object o) {
return true;
}
});
Ok, so the thing is that i'm trying to get my android application to have a settings activity for the user to be able to change some of the application's features (language, theme, ...). My problem comes when trying to get the app to react to the change on the value of one of those preferences. For instance the theme one; my idea would be to have a "Switch preference". When it would be on, the app's theme would be Material.Light and when off, Material. For this I though to have some "onValueChanged" method that would react when the switch changed its position. The problem here is that I'm unable to properly get an instance of the SwitchPreference in my SettingsActivity, both because the "findPreference(key)" method is deprecated and I don't really know how to make it take the value of the needed key.
There is any way to do this, or should I change the way of thinking for this problem?
Instead of setting the theme in onValue change listener, you can also do like below:
1) Create ThemeUtils class and do switch case for theme selection and set a theme for activity. I created own style, you can select your stlye name here
public class ThemeUtils {
public final static int THEME_Professional = 1;
public final static int THEME_Default = 2;
public static void onActivityCreateSetTheme(Activity activity, int sTheme) {
switch (sTheme)
{
default:
case THEME_Professional:
activity.setTheme(R.style.ProfessionalTheme);
break;
case THEME_Default:
activity.setTheme(R.style.DefaultTheme);
break;
}
}
}
2)In onCreate method of Activity, call setTheme method
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme();
}
3)Gets user selected theme
private void setTheme() {
int themeInt = getThemeValue();
ThemeUtils.onActivityCreateSetTheme(this, themeInt);
}
4) After getting the style id, you can call ThemeUtils class method onActivityCreateSetTheme
private int getThemeValue() {
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
Boolean isGrid = pref.getBoolean(getString(R.string.grid_switch), false);
if (isGrid) {
return 2;
} else {
return 1;
}
}
Hope this option helps you !!!
I have been trying to get my app to read data from the preferences, and change the theme according to the option selected. I have found many different suggestions on the internet, including here, but have been unable to get it to work.
I have created preferences.xml and arrays.xml, and the user is able to select the theme they want. However, the change is not reflected in the app.
Here are the contents of ActivityMain.java:
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
String userTheme = preferences.getString("prefTheme", "darkab");
if (userTheme.equals("darkab"))
setTheme(R.style.darkab);
else if (userTheme.equals("light"))
setTheme(R.style.light);
else if (userTheme.equals("dark"))
setTheme(R.style.dark);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#SuppressLint("NewApi")
protected void onResume(Bundle savedInstanceState) {
recreate();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
String userTheme = preferences.getString("prefTheme", "darkab");
if (userTheme.equals("darkab"))
setTheme(R.style.darkab);
else if (userTheme.equals("light"))
setTheme(R.style.light);
else {setTheme(R.style.dark);}
super.onResume();
setContentView(R.layout.activity_main);
}
These are the styles I wish to use, as set in styles.xml:
<style name="darkab" parent="android:Theme.Holo.Light.DarkActionBar"></style>
<style name="light" parent="android:Theme.Holo.Light"></style>
<style name="dark" parent="android:Theme.Holo">
And here is my preferences.java file:
public class Preferences extends PreferenceActivity {
#SuppressWarnings("deprecation")
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
Any help would be appreciated.
setTheme() is effective only before the layout has been constructed i.e. you should call it before setContentView(). The LayoutInflater resolves theme attributes and accordingly set properties on the View's it creates. To apply a theme on an already running Activity, you would have to re-start the Activity.
I know that I am late but I would like to post a solution here:
Check the full source code here.
This is the code I used when changing theme using preferences..
SharedPreferences pref = PreferenceManager
.getDefaultSharedPreferences(this);
String themeName = pref.getString("prefSyncFrequency3", "Theme1");
if (themeName.equals("Africa")) {
setTheme(R.style.AppTheme);
} else if (themeName.equals("Colorful Beach")) {
//Toast.makeText(this, "set theme", Toast.LENGTH_SHORT).show();
setTheme(R.style.beach);
} else if (themeName.equals("Abstract")) {
//Toast.makeText(this, "set theme", Toast.LENGTH_SHORT).show();
setTheme(R.style.abstract2);
} else if (themeName.equals("Default")) {
setTheme(R.style.defaulttheme);
}
Please note that you have to put the code before setcontentview..