The Story…
I’ve seen a few comments on various channels about how to change the status bar, navigation bar, and full screen layouts on Android in a NativeScript app. In this post I want to cover a few scenarios and how to achieve the specific layout you want in your app. So lets get started.
I’m going to assume you are familiar with NativeScript, if you aren’t then check out the getting started guide for a quick intro. Let us start with a fresh app using the CLI, path into the new app, and add the android platform.
tns create screenApp cd screenApp tns platform add android
Now that we have our new app, open your IDE and let’s modify the main-page.js file.
Full Screen with Transparent Status Bar
You can copy the next snippet and run it to see the result. The important parts are requiring the application and platform modules to easily add some checks for android and sdk versions as needed. Then in the pageLoaded function we will make the code changes to achieve the end result. In short we are setting the status bar to transparent then adding some flags to the Android Window. The last important piece is adding some padding to the first view in your layout. I’m getting the status bar height and using that as the top padding here.
var app = require("application"); var platform = require("platform"); var color = require("color"); // Event handler for Page "loaded" event attached in main-page.xml function pageLoaded(args) { // Get the event sender var page = args.object; page.bindingContext = data; var View = android.view.View; if (app.android && platform.device.sdkVersion >= '21') { var window = app.android.startActivity.getWindow(); // set the status bar to Color.Transparent window.setStatusBarColor(0x000000); var decorView = window.getDecorView(); decorView.setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN // | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar // | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); } var statusHeight = getStatusBarHeight(); // // Need to add some padding to whatever your first View(widget) // var actionbar = page.actionBar._toolbar; // // Set the padding to match the Status Bar height // actionbar.setPadding(0, statusHeight, 0, 0); var lab = page.getViewById('myLabel').android; lab.setPadding(0, statusHeight, 0, 0); } exports.pageLoaded = pageLoaded; // A method to find height of the status bar function getStatusBarHeight() { var result = 0; var resourceId = app.android.currentContext.getResources().getIdentifier('status_bar_height', 'dimen', 'android'); if (resourceId) { result = app.android.currentContext.getResources().getDimensionPixelSize(resourceId); } return result; }
Resulting Layout
FullScreen “Immersive” Mode
To read about using the “Immersive” mode in Android read the docs here. The Fullscreen immersive mode will give you the full device screen for your layout and allow the user to swipe near the edges of the screen to bring the navigation bar and status bar back into view momentarily. There are some specifics to achieving this correctly, but the following code change will give you what you need. Just uncomment two lines from the .setSystemUiVisibility() method from above.
var decorView = window.getDecorView(); decorView.setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
The two flags needed are SYSTEM_UI_FLAG_HIDE_NAVIGATION and SYSTEM_UI_FLAG_FULLSCREEN.
Resulting Layout
At this point you could try various options with the different flags in the code to hide the navigation bar or just the status bar. So let’s move on to one final piece with the navigation bar and it’s a nice effect. COLOR.
Color the Navigation Bar
At the beginning of our pageLoaded function we have a call to the Android method .setStatusBarColor() to change the statusbar color. Well Android also has a nice method for the navigation bar –.setNavigationBarColor(), add the following line after your var window. *REMINDER* Do not forget to comment out the two flags we uncommented a moment ago (SYSTEM_UI_FLAG_FULLSCREEN and SYSTEM_UI_FLAG_HIDE_NAVIGATION). If you leave them enabled, you won’t have a status bar or navigation bar.
window.setNavigationBarColor(new color.Color('#3489db').android);
Resulting Layout
Conclusion
In this tutorial we covered the basic code for manipulating the Android Window using various flags and methods to style the status bar and navigation bar. I plan on making a plugin for the navigation bar styling with an XML attribute to avoid users having to deal with the native code. If you haven’t check out Burke Holland’s plugin called nativescript-statusbar which allows you to set the color of the iOS and Android statusbar with a simple XML tag. The plugin is listed on NPM here: https://www.npmjs.com/package/nativescript-statusbar I hope you’ve enjoyed reading and if you have any questions feel free to leave in the comments. Follow me on Twitter @BradWayneMartin for more NativeScript tutorials and information.
Thanks for putting this together Brad!
I’ll drop in one other tip that helped me out. In my case I wanted this code to not be tied to any individual view, as my app can potentially start on two different pages (depending on whether the user has an existing login token), so I ended up putting this bit of code in my app.js before I call
start()
.LikeLike
Thanks for the tip TJ. I hadn’t thought to do that. Gotta add that to my apps now.
LikeLike
I got an error doing this because i had the same problem. Probably I’m not doing it right. Can you please put me thru
LikeLike
This statement is not working for me in angular 2
var View = android.view.View;
LikeLike
Without debugging I would guess there is an issue existing the native Android View class then. That’s odd. Have you searched the nativescript repo to see if it’s been reported?
LikeLike
How do I apply the immersive mode to only one page? I implemented the code you put here and when I go back to the previous page, it gets immersive as well. All pages get immersive.
LikeLike
Hi, the immersive mode flag is a global change on Android for the current app when it’s added. So all you would do to only use one on page is to apply it and then remove it when you’re navigating away from that page. Nativescript has several page life cycle events that you can use. The navigatingFrom is probably a good start for your question. Hope that helps.
LikeLike
Hi, i applied the immersive mode to one page. But when i tap on the screen, the status bar appears and then it doesn’t disappear. How can i fix it?
LikeLike
Showing Error : data is not defined on line no. 9 . Please help me to figure out that error.
LikeLike
On line 11 in your first block of code:
var View = android.view.View;
where are you importing that android reference from? I’ve tried importing it from platform and it doesnt have any fields called ‘view’.
LikeLike
Pete, `android.view.View` is part of the android native API. So it’s not being imported, you just have direct access to it with NativeScript with its foundation on providing direct access to native APIs. I’m sure you figured this out by now, sorry for the late reply.
LikeLike
Your code in throwing an Unexpected token error on line 45 column 22
LikeLike
Thanks David, simple typo 🙂 should be corrected now.
LikeLike
I want to show the action bar, but hide the nav bar and the status bar. How can i do that?
LikeLike
my emulator says: “data is not defined, undefined” – did everything exactly as you wrote. anything i may have missed in the main-page.xml?
LikeLike
Probably this line `page.bindingContext = data;` which is actually irrelevant in the code so just remove that. Hope that clears it up for you.
LikeLike
Thanks for your fast reply. Emulator leaving me with “Cannot read property of ‘android’ of undefined, undefined”. Completely replaced the contents of the file main-page.js with your code.
LikeLike
K. There are a few ways to approach this. You can install the tns platform declarations package to your app which will give you access to the typings and packages of Android/iOS. You also usually want to safeguard any calls to the native apis with a check of the platform. You can check the forums or some of my plugins have plenty of sample code for this. Ive been telling myself to get back into blogging but haven’t had time 🙂 thanks for reading. Also you can find some really helpful people if you’re getting started on the NS slack community.
LikeLike
Hey Brad, thanks the tutorial! I’m running a Nativescript Angular app and calling your functions using page.on(loaded). I’ve been able to get the status bar to be transparent thanks to you, but I can’t get the code to calculate the height of the status bar to work; when I try determining the resourceId, app.android.currentContext is null. Do you have any idea as to why this is happening?
LikeLike
Yea, I believe that not all devices are going to return a value for it. However, this SO post (https://stackoverflow.com/questions/3407256/height-of-status-bar-in-android) I think is where I got some guidance on the approach myself. Might be some other helpful bits in there. Hope that helps.
LikeLike
Hi Brad,
Thank you for this article !
I tried several things but when i want to apply what you explain, i cannot access to “View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY”, i think that the problem is with the “tns-platform-declarations\android17.d.ts” which does not contains this flag.
I understand that the tns-platform-declarations that i have (fresh install) corresponds to API versions 17 and that this flag is new in the API version 19
So, if you think my feeling is good, how to update this “tns-platform-declarations” ?
Thank you in advance
LikeLike
–> get window of undefined error. with your code……. its not working for me
[Redmi]: An uncaught Exception occurred on “main” thread.
com.tns.NativeScriptException:
Calling js method onCreateView failed
TypeError: Cannot read property ‘getWindow’ of undefined
File: “file:///data/data/org.nativescript.preview/files/app/views/home/products/products.js, line: 14, column: 47
StackTrace:
Frame: function:’exports.loaded’, file:’file:///data/data/org.nativescript.preview/files/app/views/home/products/products.js’, line: 14, column: 48
Frame: function:’Observable.notify’, file:’file:///data/data/org.nativescript.preview/files/app/tns_modules/tns-core-modules/data/observable/observable.js’, line: 110, column: 23
Frame: function:’Observable._emit’, file:’file:///data/data/org.nativescript.preview/files/app/tns_modules/tns-core-modules/data/observable/observable.js’, line: 127, column: 18
Frame: function:’ViewBase.onLoaded’, file:’file:///data/data/org.nativescript.preview/files/app/tns_modules/tns-core-modules/ui/core/view-base/view-base.js’, line: 236, column: 14
Frame: function:’View.onLoaded’, file:’file:///data/data/org.nativescript.preview/files/app/tns_modules/tns-core-modules/ui/core/view/view.js’, line: 208, column: 35
Frame: function:’Page.onLoaded’, file:’file:///data/data/org.nativescript.preview/files/app/tns_modules/tns-core-modules/ui/page/page.js’, line: 43, column: 35
Frame: function:”, file:’file:///data/data/org.nativescript.preview/files/app/tns_modules/tns-core-modules/ui/core/view-base/view-base.js’, line: 311, column: 90
Frame: function:’ViewBase.callFunctionWithSuper’, file:’file:///data/data/org.nativescript.preview/files/app/tns_modules/tns-core-modules/ui/core/view-base/view-base.js’, line: 304, column: 9
Frame: function:’ViewBase.callLoaded’, file:’file:///data/data/org.nativescript.preview/files/app/tns_modules/tns-core-modules/ui/core/view-base/view-base.js’, line: 311, column: 14
Frame: function:’ViewBase.loadView’, file:’file:///data/data/org.nativescript.preview/files/app/tns_modules/tns-core-modules/ui/core/view-base/view-base.js’, line: 439, column: 18
Frame: function:’ViewBase._addViewCore’, file:’file:///data/data/org.nativescript.preview/files/app/tns_modules/tns-core-modules/ui/core/view-base/view-base.js’, line: 434, column: 18
Frame: function:’ViewBase._addView’, file:’file:///data/data/org.nativescript.preview/files/app/tns_modules/tns-core-modules/ui/core/view-base/view-base.js’, line: 420, column: 14
Frame: function:’FragmentCallbacksImplementation.onCreateView’, file:’file:///data/data/org.nativescript.preview/files/app/tns_modules/tns-core-modules/ui/frame/frame.js’, line: 588, column: 24
Frame: function:’FragmentClass.onCreateView’, file:’file:///data/data/org.nativescript.preview/files/app/tns_modules/tns-core-modules/ui/frame/fragment.js’, line: 27, column: 38
at com.tns.Runtime.callJSMethodNative(Native Method)
at com.tns.Runtime.dispatchCallJSMethodNative(Runtime.java:1101)
at com.tns.Runtime.callJSMethodImpl(Runtime.java:983)
at com.tns.Runtime.callJSMethod(Runtime.java:970)
at com.tns.Runtime.callJSMethod(Runtime.java:954)
at com.tns.Runtime.callJSMethod(Runtime.java:946)
at com.tns.FragmentClass.onCreateView(FragmentClass.java:45)
at android.app.Fragment.performCreateView(Fragment.java:2220)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:973)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1148)
at android.app.BackStackRecord.run(BackStackRecord.java:799)
at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1537)
at android.app.FragmentManagerImpl$1.run(FragmentManager.java:482)
at android.os.Handler.handleCallback(Handler.java:742)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:157)
at android.app.ActivityThread.main(ActivityThread.java:5571)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:745)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:635)
LikeLike
–> this was my js and xml
var ProductViewModel = require(“../../../view-models/main-view-model/product-view-model”);
var ProductViewModel = new ProductViewModel();
var app = require(“application”);
var platform = require(“platform”);
var color = require(“color”);
exports.loaded = function (args) {
page = args.object;
page.bindingContext = ProductViewModel;
var View = android.view.View;
if (app.android && platform.device.sdkVersion >= ’21’) {
var window = app.android.startActivity.getWindow();
// set the status bar to Color.Transparent
window.setStatusBarColor(0x000000);
var decorView = window.getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
// | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
};
exports.onNavBtnTap = function () {
frameModule.topmost().goBack();
}
————————————————————————————————————————-
xml:
LikeLike
I solved it by this: setTimeout ( () => { this.fullScreenApplier() } , 100 ); (I use it in created section!! )
LikeLike
Hi Martin, i have set fullscreen mode in the first screen , and statusbarstyle mode light in the next screen. if i go back to the first screen statusbarstyle is still apllied even after i set fullscreen mode . can you please provide a solution to set fullscreen mode for only few screens
LikeLike
You’ll need to handle this on a case by case basis. When setting these flags you’re doing so globally on the Android window screen, this isn’t something each page has but rather the entire application activity on Android. So you’d just reset the flags during navigation or on the loaded event of the page you’re going to. There is also a swiss-army-knife plugin for nativescript with many utility functions that will help simplify your code or at least help you implement the resetting and setting.
LikeLike
its not working????
LikeLike
Can you please add the same piece of code in Typescript or Angular?. The PageLoad event is not firing in my case.
LikeLike
I have searched/tried for a while to find a way to change the text color of the status bar on android too.
Just wanted to note that you can set the Android status bar text color since android lollipop?
with View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR to grey (for light backgrounds).
So:
…
window.setStatusBarColor(new color.Color(“#ffffff”).android);
…
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)
shows greyish text on white background for the status bar on Android.
LikeLike
Thanks a lot Brad! The best article about this! :heart
LikeLike
Thank you for the great articke. Can you perhaps also write an article about how to respond to UI visibility changes using nativescript-vue ?
I tried the following, but get an abstractmethoderror.
decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener({
methods: {
onSystemUiVisibilityChange(visibility) {
// Note that system bars will only be “visible” if none of the
// LOW_PROFILE, HIDE_NAVIGATION, or FULLSCREEN flags are set.
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
| View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
} else {
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
| View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
}
}
}
}))
LikeLike