Closed TheJulianJES closed 2 years ago
Thank you @TheJulianJES this is an excellent summary of the current situation and improvement suggestions needed to make it even easier for Flutter developers to make apps that follow the Android guidelines for both current and older API levels. It covers all and then some topics I had noticed as well. ππ» π
it is work for me
final SystemUiOverlayStyle light = SystemUiOverlayStyle(
statusBarColor: Colors.transparent, // 23
statusBarIconBrightness: Brightness.dark, // 23
systemNavigationBarColor: Colors.transparent, // 27
systemNavigationBarDividerColor: Colors.transparent.withAlpha(1) /* δΈθ½η¨ε
¨ιζ */, // 28
systemNavigationBarIconBrightness: Brightness.dark, // 27
systemNavigationBarContrastEnforced: false, // 29
);
SystemChrome.setSystemUIOverlayStyle(light);
@v7lin I agree, that worked well for me up to Android10 API29 too.
However, in Android 11 API30 when I do basically what you do above, it get this:
The status bar and system nav bar icons are light and not dark as they should be.
If I in this sample comment the line:
// systemNavigationBarDividerColor: Colors.transparent.withAlpha(1),
I get the correct brightness on both the system navigation bar icons and the status bar icons also on Android11 API30. The impact on the status bars is very is very odd since the status bar icons are not even touched in this example in the AnnotatedRegion. The status bar is setup in this example via the AppBarTheme
, as it should preferably be, imo.
This is quite strange and annoying. As mentioned, the above code works correctly on Android 10, API29 producing the dark status bar icons and system navigation bar icons even with the systemNavigationBarDividerColor
line present.
I did not yet test how how Android12 API31 behaves in this case. I'm assuming it is like A11 API30, but the way these things changes, it is maybe not a safe assumption.
I should probably open a separate issue with this finding, but I feel a bit Flutter issue fatigue at the moment. I'll probably get back to it later...
EDIT: Made an own issue of it here: https://github.com/flutter/flutter/issues/100027
ping: @TheJulianJES @Piinks
@rydmike This is best practice
<style name="Theme.App" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowDrawsSystemBarBackgrounds" tools:targetApi="lollipop">true</item>
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="p">shortEdges</item>
<!-- SystemUiOverlayStyle -->
<item name="android:statusBarColor" tools:targetApi="m">@android:color/transparent</item>
<item name="android:windowLightStatusBar" tools:targetApi="m">true</item>
<item name="android:enforceStatusBarContrast" tools:targetApi="q">false</item>
<item name="android:navigationBarColor" tools:targetApi="o_mr1">@android:color/transparent</item>
<item name="android:navigationBarDividerColor" tools:targetApi="o_mr1">#01000000</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">true</item>
<item name="android:enforceNavigationBarContrast" tools:targetApi="q">false</item>
</style>
<style name="Theme.App.Dark" parent="Theme.App">
<!-- SystemUiOverlayStyle -->
<item name="android:windowLightStatusBar" tools:targetApi="m">false</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">false</item>
</style>
<style name="LaunchTheme" parent="Theme.App">
<!-- SplashScreen -->
<item name="android:windowSplashScreenBackground" tools:targetApi="s">@android:color/white</item>
<item name="android:windowSplashScreenAnimatedIcon" tools:targetApi="s">@android:color/transparent</item>
<item name="android:windowSplashScreenBrandingImage" tools:targetApi="s">@drawable/branding_icon</item>
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<style name="NormalTheme" parent="Theme.App">
<item name="android:windowBackground">@android:color/white</item>
</style>
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import androidx.annotation.Nullable;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Window window = getWindow();
final View decorView = window.getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);// edgeToEdge
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
window.setStatusBarColor(Color.TRANSPARENT);
decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
window.setNavigationBarColor(Color.TRANSPARENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
window.setNavigationBarDividerColor(Color.parseColor("#01000000"));// δΈθ½η¨ε
¨ιζ
}
decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.setNavigationBarContrastEnforced(false);
}
}
}
}
runZonedGuarded(
() async {
WidgetsFlutterBinding.ensureInitialized();
final SystemUiOverlayStyle dark = SystemUiOverlayStyle(
statusBarColor: Colors.transparent /*Android=23*/,
statusBarBrightness: Brightness.light /*iOS*/,
statusBarIconBrightness: Brightness.dark /*Android=23*/,
systemStatusBarContrastEnforced: false /*Android=29*/,
systemNavigationBarColor: Colors.transparent /*Android=27*/,
systemNavigationBarDividerColor: Colors.transparent.withAlpha(1) /*Android=28,δΈθ½η¨ε
¨ιζ */,
systemNavigationBarIconBrightness: Brightness.dark /*Android=27*/,
systemNavigationBarContrastEnforced: false /*Android=29*/,
);
SystemChrome.setSystemUIOverlayStyle(dark);
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
runApp(Root());
},
(Object error, StackTrace stack) {
// TODO
},
zoneSpecification: ZoneSpecification(
print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
// TODO
},
),
);
Any update about SystemUiMode.edgeToEdge ? I want it to be the default option for android 10+. Thanks.
To address the components of the issue individually:
Any update about SystemUiMode.edgeToEdge ? I want it to be the default option for android 10+.
Making SystemUiMode.edgeToEdge
the default mode for API 10+ would be a breaking change because it involves not only making the system bars transparent by default, but also having developers lay out their apps to accommodate the insets that will now be drawn in front of some of the app content.
This migration should be made to be consistent with Android and to avoid difficulty in using this mode for particular Android versions, though. In the meantime, I think it could be useful to provide guidance on how to achieve behavior that emulates the Android behavior across versions. @Piinks any thoughts on this?
SDK 26-28: Because the transparent navigation bar is still set, it will show a completely black navigation bar with black navigation bar buttons (which are completely invisible) if light mode is selected. This is behavior that needs to be fixed. (comment)
I'll take a look at this issue and update here.
This is quite strange and annoying. As mentioned, the above code works correctly on Android 10, API29 producing the dark status bar icons and system navigation bar icons even with the systemNavigationBarDividerColor line present. (comment)
This issue with systemNavigationBarDividerColor
was fixed with PR #32167.
provide guidance on how to achieve behavior that emulates the Android behavior across versions. @Piinks any thoughts on this?
There are probably some best practices here, but I do not know them myself. The framework does not distinguish between versions of TargetPlatform, and this API is not specific to Android either. I affects iOS as well IIRC.
I did see recent discussion on a similar issue that a plugin is probably the best approach. It could avoid the breaking change by having users decide to add it, and from there it can provide the automatic defaults that are being requested here.
There are two components included in this issue: one concerning setting edge-to-edge mode by default for API 29+ and the other concerning hidden navigation bar icons. I split them into two issues to continue discussion (see links above!) and am closing this issue.
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v
and a minimal reproduction of the issue.
Background
https://github.com/flutter/flutter/pull/81303 added support for the Edge to Edge fullscreen mode introduced in Android 10 (SDK 29). https://github.com/flutter/engine/pull/28616 limited the support for Edge to Edge to Android 10 (SDK 29) and higher.
In the following, "Android 10+" will refer to "Android 10 (SDK 29) and upwards".
Expectations
The guidelines over at https://developer.android.com/training/gestures/edge-to-edge show the following: With Android 10+, apps should basically always set a transparent navigation bar color. Setting a
SystemUiOverlayStyle
in Flutter withsystemNavigationBarColor: Colors.transparent
andsystemNavigationBarContrastEnforced: true
(the enforced contrast already defaults to on) enables Android 10+ to (dynamically) show a scrim behind the navigation bar to make the buttons clearly visible. This can be seen on the left image.When the user chooses gesture navigations, it's expected that apps do not show a a color/overlay behind the bar. This can be seen on the right image. (This is automatically done by Android.)
In both images, the transparent navigation bar color takes effect. In the first image, the white scrim is created by Android (as the transparent navigation bar color possibly couldn't provide enough contrast). In the second image (with gesture navigation), no overlay is shown (as the transparent navigation bar color fully takes effect and no overlay is needed with only the "gesture bar").
Reality
Currently, Flutter apps do the following by default: (Taken from: flutter/gallery#643) This looks especially weird on devices with rounded corners when the user has gesture navigations on.
SDK >= 29
Here,
darkMode
defines if the user has currently selected a dark theme.When this is called on Android 10+, the behavior is perfect and as expected:
However, when this is called on Android versions below 10 (below SDK 29), the following happens: While the Edge-to-Edge fullscreen mode will be ignored because of https://github.com/flutter/engine/pull/28616, it still causes the following issues:
SDK < 29
For everything older than Android 10 (below SDK 29), the following code produces the expected behavior: Here,
darkMode
defines if the user has currently selected a dark theme.Like expected, if a light theme is shown, the user sees a white navigation bar with dark buttons (and the other way around). For Android 10+, the user gets the weird "navigation bar overlay" even when using gesture navigations (because edge-to-edge is not enabled). (-> the current "default" behavior in Flutter)
Combining?
One could make the assumption that the code can be combined without doing any Android version checking in Dart:
However, this causes issues on everything older than Android 10 (below SDK 29):
Correct Usage
The following code should produce expected behavior on all Android verisons.
Caveats:
if (Platform.isAndroid) await deviceInfoPlugin.androidInfo;
inmain.dart
(before "starting the app") on a globally defined DeviceInfoPlugin instance:final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
, as the plugin caches these results in the instanceUse case
Make it easier for users to implement the new Edge-to-Edge fullscreen behavior in Android 10 and onwards while maintaining proper (navigation bar) behavior on older SDK versions without much effort. Basically, it shouldn't be required to check the Android SDK version in Dart logic for the expected/default Android 10+ behavior (and not break on older Android versions).
Proposal
There are different possibilities to solve this issue:
SystemUiMode.edgeToEdge
always set the navigation bar color to transparent -> This seems like the easiest solution but it could potentially confuse users as to why their customsystemNavigationBarColor
is ignored on Android 10+. (Although I do not see a use-case where you could ever want that?) Currently, some users are confused that enabling the edge-to-edge mode requires a transparent navigation bar color in order to do anything really. I'd assume that because of the few people using edge-to-edge behavior in Flutter at the moment, this change might be feasible. (stable
branch still activates it for more SDK versions) (also, nobody sets a custom navigation bar color when wanting to use proper edge-to-edge behavior?)SystemChrome
to handle this -> This is worded very openly and could mean anything -> Perhaps introduce a fallback navigation bar color (black/white for older Android versions) but still require to explicitly set the transparent navigation bar color for Android 10+?Notes
Maintainers, feel free to edit/update this issue with with your ideas. To everyone else, I'll try to update this issue with your ideas/feedback to collect the "main ideas".
cc @Piinks