Android API level 30 setSystemBarsAppearance doesn't overwrite theme data - android

Pre-Android 11 (API Level 30) I had
<item name="android:windowLightStatusBar">true</item>
set in my theme and was additionally changing this (when needed) in the the code with
fun setLightStatusBar(){
window?.decorView?.let { it.systemUiVisibility = it.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR }
}
fun setDarkStatusBar(){
window?.decorView?.let { it.systemUiVisibility = it.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() }
}
However, Android-30 adds a new way to control with
fun setLightStatusBar(){
window?.insetsController?.setSystemBarsAppearance(APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS)
}
fun setDarkStatusBar(){
window?.insetsController?.setSystemBarsAppearance(0, APPEARANCE_LIGHT_STATUS_BARS)
}
but my issue is that this cannot overwrite the theme-set values and thus I need to either do it all with styles or all in code.
My question is if this is intended to be like this or am I missing something somewhere?

I removed <item name="android:windowLightStatusBar">true/false</item> from styles.xml and it worked correctly.

If you set android:windowLightStatusBar to true in your theme, you need to do the deprecated call, which removes the system UI flag, to make it work.
activity.window.decorView.run {
systemUiVisibility =
systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
}
It seems like both WindowInsetsController and systemUiVisibility both controls the status bar theme, but in different mechanisms.

Related

How to use or opt out in Android 12 SplashScreen

The new API SplashScreen in Android 12 seems good but just like before the sample code in the documentation does not really help explaining the whole and proper implementation. There is also some cases where you might do some task during splash screen in our case this is to launch Firebase Auth so probably the best way is just to opt out on using this new featured API but according to lint warning it seems like it is mandatory and there is no way to opt out.
The application should not provide its own launch screen
Application-defined Launch Screen Starting in Android 12 (API 31+),
the application's Launch Screen is provided by the system and the
application should not create its own, otherwise the user will see two
splashscreen. Please check the SplashScreen class to check how the
Splash Screen can be controlled and customized.
How about the backward compatibility for older devices, how to handle it? Is there any codelab project to play and test with?
Can we opt out of SplashScreen?
It looks like we can't opt out as Android Team is trying to unify the app loading experience: https://9to5google.com/2021/04/21/android-12-dp3-all-apps-now-show-the-same-splash-screen-while-loading-gallery/
How to use it?
If you don't do anything then it will use windowBackground of the theme & your launcher icon & dismissed as soon as your app draws its first frame.
There are bunch of properties that you can modify like background, icon etc: https://developer.android.com/about/versions/12/features/splash-screen#set-theme
What if I want splash to stay longer? Like fetching a local DataBase.
You can use ViewTreeObserver.OnPreDrawListener & make a blocking call from your viewmodel return if it's ready to go ahead.
Activity:
// My Launcher Activity
class MainActivity : AppCompatActivity() {
private val viewModel : JustDelayViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val content: View = findViewById(android.R.id.content)
content.viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
// Check if the initial data is ready.
return if (viewModel.getIsReady()) {
// The content is ready; start drawing.
content.viewTreeObserver.removeOnPreDrawListener(this)
true
} else {
// The content is not ready; suspend.
false
}
}
}
)
}
}
ViewModel:
class JustDelayViewModel : ViewModel() {
fun getIsReady(): Boolean {
val result = viewModelScope.runCatching {
runBlocking {
//do some blocking call check for Firebase result or something
delay(5000)
}
true //return the result
}
return result.isSuccess
}
}
You can read more about this: https://developer.android.com/about/versions/12/features/splash-screen#suspend-drawing
To complement Mayur's answer for older device support.
The new windowSplashScreen* attributes need to be added in the res/values-v31/style.xml file.
Then for the legacy splashscreen it depend of the current implementation of the app.
If the application simply uses a starting theme with a custom windowBackground there is nothing to do since the windowBackground isn't used for the new splash screen (only if it's a simple color).
If the application has some visible splash screen Activity, there will be a double splash screen on Android 12. To solve this, the application can migrate to the windowBackground solution.
If the application really need to keep its splash screen Activity, it can update the layout to match the system splash screen on Android 12 and/or create a smooth transition from the system splash screen to the app splash screen using the SplashScreen.setOnExitAnimationListener()
We can also use android's splash screen library - link
android {
compileSdk 31
...
}
dependencies {
...
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
}
This will give splash screen options in style.xml, you just need to create 2 style.xmls 1 for android api 31 and above and one of below api 31
<style name="Theme.CustomSplashScreenTheme" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">#color/white</item>
<item name="windowSplashScreenAnimatedIcon">#drawable/logo</item>
<item name="windowSplashScreenAnimationDuration">300</item>
<item name="postSplashScreenTheme">#style/Theme.YourAppTheme</item>
</style>
Learn more about this library using this example
you can add this line:
<item name="android:windowIsTranslucent">true</item>
in your style.xml file before close style tag. it`s make your default android splash transparent!

How can I detect programmatically if the Android Device is in Dark Mode?

I'm trying to support the Android Q Dark theme for my Android app and I can't figure out how to import different assets based on the theme I'm currently in.
Im using the official DayNight theme for making the dark/light versions and for drawables is very easy to just point to the XML and it will choose the correct value either from values or values-night depending on what is enabled.
I wanted to do something similar where depending on the theme it would load either the asset "priceTag_light.png" or "priceTag_dark.png".
val inputStream = if(darkIsEnabled) {
assets.open("priceTag_dark.png")
} else {
assets.open("priceTag_light.png")
}
Is there a way I get that flag?
Okay finally found the solution I was looking for. As #deepak-s-gavkar points out the parameter that gives us that information is on the Configuration. So, after a small search I found this article that gives this example method that has worked perfectly for what I wanted:
fun isDarkTheme(activity: Activity): Boolean {
return activity.resources.configuration.uiMode and
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
}
You first need to do this changes in manifest
<activity
android:name=".MyActivity"
android:configChanges="uiMode" />
then onConfigurationChanged of activity
val currentNightMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
when (currentNightMode) {
Configuration.UI_MODE_NIGHT_NO -> {} // Night mode is not active, we're using the light theme
Configuration.UI_MODE_NIGHT_YES -> {} // Night mode is active, we're using dark theme
}
Use the following code:
boolean isDarkThemeOn = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;

WebView resetting UiMode and breaking dark theme

Our app relies on AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) to make us pick up Light and Dark theme colors from values/colors and values-night/colors
But every time we try to use the WebView, it starts by resetting the UiMode and our app gets confused which color values to pick for our themes
Some people discussed the issue in detail here and here
Anyone out there run into a similar issue?
As previous issue was closed I opened the new one: https://issuetracker.google.com/issues/170328697
And I tried to fix it in this way:
class UiModeCareWebView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : WebView(context, attrs, defStyleAttr) {
init {
fixUiModeIfNeeded()
}
private fun fixUiModeIfNeeded() {
val configuration = context.resources.configuration
val configurationNighMode = configuration.uiMode and UI_MODE_NIGHT_MASK
val appCompatNightMode = getDefaultNightMode()
val newUiModeConfiguration = when {
configurationNighMode == UI_MODE_NIGHT_NO && appCompatNightMode == MODE_NIGHT_YES -> {
UI_MODE_NIGHT_YES or (configuration.uiMode and UI_MODE_NIGHT_MASK.inv())
}
configurationNighMode == UI_MODE_NIGHT_YES && appCompatNightMode == MODE_NIGHT_NO -> {
UI_MODE_NIGHT_NO or (configuration.uiMode and UI_MODE_NIGHT_MASK.inv())
}
else -> null
}
if (newUiModeConfiguration != null) {
val fixedConfiguration = Configuration().apply {
uiMode = newUiModeConfiguration
}
#Suppress("DEPRECATION")
context.resources.updateConfiguration(
fixedConfiguration,
context.resources.displayMetrics
)
}
}
}
Answering my own question, looks like Google fixed the issue https://issuetracker.google.com/issues/37124582
with https://developer.android.com/jetpack/androidx/releases/appcompat#1.1.0-alpha03 Fixed WebView resets DayNight Resources
First of all you need to add android.webkit dependency into your project
dependencies {
implementation "androidx.webkit:webkit:1.3.0"
}
At the time of writing this post the latest stable version of webkit is 1.3.0.
It is worth noting that the Dark Theme support has been added with version 1.2.0, before this version it was impossible to add Dark Theme support to Webview.
Next step would be to check whether Webview and Android framework on user’s device supporting theming:
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
...
}
Please note that WebViewFeature.FORCE_DARK is only supported starting with Webview version 76. Unfortunately before that version there is no straightforward way to support the dark theming.
If you own the contents displayed within the Webview you might want to implement your custom CSS themes and toggle them using #JavascriptInterface from your app.
If WebViewFeature.FORCE_DARK is supported we can choose from three available options:
FORCE_DARK_OFF - Disable dark theme, the content will be rendered in default Light Theme
FORCE_DARK_ON - Enable dark theme, the content will be rendered in Dark Theme
FORCE_DARK_AUTO - Enable dark theme based on the state of parent view
Then we need to apply the setting using WebSettingsCompat
WebSettingsCompat.setForceDark(webView.settings, WebSettingsCompat.FORCE_DARK_ON)
You can read about in more detail in the below blogpost
https://androidexplained.github.io/ui/android/material-design/2020/09/24/dark-mode-webview.html

Trouble with DayNight mode status bar/controls inconsistently themed after recreate

I'm having quite a bit of trouble getting the DayNight theme to play nicely after a preference change and activity.recreate() call. Depending on the value (and which SDK I'm on), I get inconsistent theming (icons are dark on dark or light on light).
In my PreferenceFragment, I have a preference that allows the user to set one of three values: Light, Dark, or Auto, which correspond with AppCompatDelegates MODE_NIGHT_NO, MODE_NIGHT_YES, or MODE_NIGHT_AUTO, respectively. Here's what the implementation looks like:
PreferenceFragment.kt
override fun onPreferenceChange(preference: Preference, value: Any): Boolean {
setSummary(preference, value.toString())
return when (preference.key) {
themePreference.key -> consume {
AppCompatDelegate.setDefaultNightMode(appSharedPreferences.string(R.string.preference_theme_key).toInt())
activity?.recreate()
}
}
}
BaseActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
AppCompatDelegate.setDefaultNightMode(appSharedPreferences.string(R.string.preference_theme_key).toInt())
super.onCreate(savedInstanceState)
}
I've also tried combinations of:
delegate.setLocalNightMode(appSharedPreferences.string(R.string.preference_theme_key).toInt())
and
AppCompatDelegate.setDefaultNightMode(appSharedPreferences.string(R.string.preference_theme_key).toInt()
peppered into different places in the activity/application lifecycle with no success.
What I'm seeing mostly is dark icons on a dark status bar, and inconsistencies on pre-P app switcher (the theme is Night, but the app switcher toolbar shows up as a light theme). I don't appear to have issues with the theme of my own app controls, like tabs or text - it's mainly on the android system views like the status bar and app switcher toolbar).
If I kill the app and re-launch, I have no issues whatsoever. It's only after an activity.recreate call do I see these issues.
After searching for a simple solution for a long while, I found an attribute that conveniently handles this scenario. I'm not sure how proper this approach is, but from my testing it works as expected.
Given that you're using Theme.MaterialComponents.DayNight or some similar variant in your style XML, you can use ?attr/isLightTheme as a true/false flag.
<item name="android:windowLightStatusBar">?attr/isLightTheme</item>
The value returned by ?attr/isLightTheme is handled by the system. You can check the current value with:
AppCompatDelegate.getDefaultNightMode();
// or
Configuration configuration = getResources().getConfiguration();
int currentNightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;

AppCompat DayNight theme not work on Android 6.0?

I am using the new Theme.AppCompat.DayNight added in Android Support Library 23.2
On Android 5.1 it works well.
On Android 6.0, activity looks like using light theme, but dialog looks using dark theme.
My Application class:
public class MyApplication extends Application {
static {
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_YES);
}
}
My styles.xml
<style name="AppTheme" parent="Theme.AppCompat.DayNight">
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
</style>
<style name="Dialog.Alert" parent="Theme.AppCompat.DayNight.Dialog.Alert"/>
My code to show a dialog:
new AlertDialog.Builder(mContext, R.style.Dialog_Alert)
.setTitle("Title")
.setMessage("Message")
.show();
Google have fix it in support 23.2.1
Old answer:
On Android 6.0, system's night mode setting defalut is UiModeManager.MODE_NIGHT_NO, it will change Resources.Configuration.uiMode before onCreate is called. However, support library apply its night mode setting in onCreate in AppCompatActivity, it's too late, I think thats why it not work on 6.0.
So if we can Override getResources() in AppCompatActivity and change uiMode.
Old answer:
Here are code to fix not work on Android 6.0
public class Application extends android.app.Application {
static {
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_);
}
#Override
public void onCreate() {
super.onCreate();
// add this code for 6.0
// DO NOT DO THIS. It will trigger a system wide night mode.
// This is the old answer. Just update appcompat.
// UiModeManager uiManager = (UiModeManager) getSystemService(Context.UI_MODE_SERVICE);
// uiManager.setNightMode(UiModeManager.MODE_NIGHT_);
}
}
Note: If your app don't have location permission, your app will not have the same calculate result of system. It means it is possible support library thinks it is night now when system not, this will cause some of your UI looks dark some light.
The best way is wait for Google to fix it.
The best solution is to update context with proper config. Here is a snippet of what I do:
public Context setupTheme(Context context) {
Resources res = context.getResources();
int mode = res.getConfiguration().uiMode;
switch (getTheme(context)) {
case DARK:
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
mode = Configuration.UI_MODE_NIGHT_YES;
break;
case LIGHT:
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
mode = Configuration.UI_MODE_NIGHT_NO;
break;
default:
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO);
break;
}
Configuration config = new Configuration(res.getConfiguration());
config.uiMode = mode;
if (Build.VERSION.SDK_INT >= 17) {
context = context.createConfigurationContext(config);
} else {
res.updateConfiguration(config, res.getDisplayMetrics());
}
return context;
}
Then use the context in your Application like so
#Override
protected void attachBaseContext(Context base) {
Context context = ThemePicker.getInstance().setupTheme(base);
super.attachBaseContext(context);
}
Add getDelegate().applyDayNight(); after setDefaultNightMode.
This issue was reported on https://code.google.com/p/android/issues/detail?id=201910
But after release of Android Support Library, revision 23.2.1 (March 2016). This issue has been resolved.
Fixed a compatibility issue with Night Mode and API level 23
update Support Library to Android Support Library to 23.2.1
As of now, no Gradle dependency is needed to enable the night mode other than androidx.appcompat:appcompat:1.0.2 which will already be present. Make sure to change the default theme from Theme.AppCompat.Light.DarkActionBar to Theme.AppCompat.DayNight.DarkActionBar in the styles.xml file and then do AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) to switch to the night mode. I have tested it in APIv23(Android 6.0) and above and it is working fine.
For a better explanation see this codelab by Android
just add this in your values-v21
<style name="Theme.AppCompat.DayNight">
work for me
done.

Categories

Resources