Using device application settings with NativeScript


Saving user settings in a device’s local storage is a common requirement for mobile applications. Because of that, we made the creation of a settings page with NativeScript straightforward. In this blog post you’ll learn how to build a settings page using the latest bits of NativeScript—XML declaration, data-binding, and CSS styling. To separate the UI code from the app’s logic, we will follow the MVVM pattern. To demonstrate this, we will use the real-life Fitness application, which source code can be found at http://github.com/nativescript/sample-fitness.

Creating the View Model

Once you have your project up and running by either using the Telerik Platform or the NativeScript CLI, we will start by defining the view-model for the settings page. It will hold all the setting needed for our soon-to-be-fitness-application: name, weight, height and some notification preferences.

We will use the application-settings module for saving and retrieving the settings from the device’s local storage. We will use a different string key for each property.

The settingsViewModel will also be Observable object to support two-way binding .

JS (view-model.js):

var observable = require("data/observable");
var appSettings = require("application-settings");
var dialogs = require("ui/dialogs");
 
var NAME = "settings-name";
var HEIGHT = "settings-height";
var WEIGHT = "settings-weight";
var VIBRATE = "settings-vibrate";
var SOUND = "settings-sound";
var SOUND_VOLUME = "settings-sound-value";
 
var settings = new observable.Observable();
Object.defineProperty(settings, "name", {
   get: function () { return appSettings.getString(NAME, "John Doe"); },
   set: function (value) { appSettings.setString(NAME, value); },
   enumerable: true,
   configurable: true
});
 
Object.defineProperty(settings, "height", {
   get: function () { return appSettings.getString(HEIGHT, "180"); },
   set: function (value) { appSettings.setString(HEIGHT, value); },
   enumerable: true,
   configurable: true
});
 
Object.defineProperty(settings, "weight", {
   get: function () { return appSettings.getString(WEIGHT, "80"); },
   set: function (value) { appSettings.setString(WEIGHT, value); },
   enumerable: true,
   configurable: true
});
 
Object.defineProperty(settings, "vibrateEnabled", {
   get: function () { return appSettings.getBoolean(VIBRATE, true); },
   set: function (value) { appSettings.setBoolean(VIBRATE, value); },
   enumerable: true,
   configurable: true
});
 
Object.defineProperty(settings, "soundEnabled", {
   get: function () { return appSettings.getBoolean(SOUND, true); },
   set: function (value) { appSettings.setBoolean(SOUND, value); },
   enumerable: true,
   configurable: true
});
 
Object.defineProperty(settings, "soundVolume", {
   get: function () { return appSettings.getNumber(SOUND_VOLUME, 100); },
   set: function (value) { appSettings.setNumber(SOUND_VOLUME, value); },
   enumerable: true,
   configurable: true
});
 
settings.promptName = function(args) {
    console.log("in promptName");
    dialogs.prompt("Enter your name:", settings.name).then(function (promptResult) {
        console.log("prompt result:" + promptResult.result);
        if (promptResult.result) {
            settings.set("name", promptResult.text);
        }
    });
}
exports.settingsViewModel = settings;

Creating the View

Now that we have our view-model, it’s time to create the UI for the settings.
We will add two files to our project:

  • main-page.xml - containing the XML declaration of the UI.
  • main-page.js - containing the JS code for the page.

First, we will create a page with an empty StackLayout in it and handle the loaded event. In the pageLoaded function we will set the bindingContext of the page to the view-model we created earlier:

XML(main-page.xml):

<Page xmlns="http://www.nativescript.org/tns.xsd" loaded="pageLoaded">
  <StackLayout>
    ...
  </StackLayout>
</Page>


JavaScript(main-page.js):

var vmModule = require("./view-model");
 
var viewModel = vmModule.settingsViewModel;
 
function pageLoaded(args) {
   var page = args.object;
   page.bindingContext = viewModel;
}


Next: We will separate the settings into two groups: profile and notifications.

Profile Settings

Now let’s define the profile settings section containing the name, weight and height. We will use binding syntax to bind the text values of the fields to the view model:

<GridLayout cssClass="field-group" columns="auto, 50, *" rows="auto, auto, auto">
     <!-- Name -->
     <Label text="Name"/>
     <Button col="1" colSpan="2" text="{{ name }}" tap="{{ promptName }}"/>
 
     <!-- Height -->
     <Label text="Height" row="1"/>
     <TextField row="1" col="1" text="{{ height }}" keyboardType="number"/>
     <Label col="2" row="1" text="cm"/>
 
     <!-- Weight -->
     <Label row="2" text="Weight"/>
     <TextField row="2" col="1" text="{{ weight }}" keyboardType="number"/>
     <Label row="2" col="2" text="kg"/>
</GridLayout>

We bound the text properties of the fields directly to the view-model. However, for the name we want to display a prompt dialog, so we used a binding for the tap event of the name button. We should define the promptName function in the view-model just before the settings are exported:

settings.promptName = function(args) {
    console.log("in promptName");
    dialogs.prompt("Enter your name:", settings.name).then(function (promptResult) {
        console.log("prompt result:" + promptResult.result);
        if (promptResult.result) {
            settings.set("name", promptResult.text);
        }
    });
}

The result (we will fix the styles later on):

profile.png

Notification Settings

Next section holds settings for notification vibration and sound. We will use a Switch and a Slider controls:

XML:

<GridLayout cssClass="field-group" columns="*, auto" rows="auto, auto, auto">
     <!-- Notifications -->
     <Label cssClass="field" text="Vibrate"/>
     <Switch col="1" cssClass="field-value" checked="{{ vibrateEnabled }}"/>
 
     <!-- Notifications -->
     <Label row="1" cssClass="field" text="Sound"/>
     <Switch row="1" col="1" cssClass="field-value"  checked="{{ soundEnabled }}"/>
     <Slider row="2" colSpan="2" maxValue="100" value="{{ soundVolume }}" isEnabled="{{ soundEnabled }}"/>
</GridLayout>

No need for additional code here. Note that the isEnabled property of the slider is bound to the soundEnabled property of the view-model. Because all bindings defined in the XML are in two-way mode, the slider will be disabled when the switch is unchecked.

The result:

 

Styling

All the functionality is in place. What is left is to add some CSS styling to the page.

Tip: If there is a CSS file with the same as the XML file with the UI declaration, it will get applied to the page automatically. Also you can create a file named app.css and define styles in it which can be used from any page inside the application.

We will need to add some cssClass attributes in the XML declaration.

...
<!-- Height -->
<Label cssClass="field" text="Height" row="1"/>
<TextField row="1" col="1" cssClass="field-value" text="{{ height }}" keyboardType="number"/>
<Label col="2" row="1" cssClass="field-unit" text="cm"/>
  
<!-- Weight -->
<Label row="2" cssClass="field" text="Weight"/>
<TextField row="2" col="1" cssClass="field-value" text="{{ weight }}" keyboardType="number"/>
<Label row="2" col="2" cssClass="field-unit" text="kg"/>
...

And the CSS(app.css):

.field, .field-value, .field-unit, .field-dialog-button {
    font-size: 16;
    vertical-align: center;
}
 
.field, .field-unit {
    color: #3c3c3c;
}
 
.field-value, .field-dialog-button {
    color: #808080;
    text-align: right;
}  

After adding header labels and a little more styling we get this:

final.png

You can check out the final application here - http://github.com/nativescript/sample-fitness

Comments


Comments are disabled in preview mode.
NativeScript is licensed under the Apache 2.0 license
© 2020 All Rights Reserved.