Aaron Ullal is a Freelance developer at FactoryMind, people & life lover, living in permanent beta. He is a full stack developer based in the beautiful town of Trento, Italy.
IT passionate for more than 7 years, he’s currently focused on mobile development using NativeScript and designing architectural solutions for complex platforms.
Couchbase is a great tool to persist data inside our app. If you haven’t heard of it, it’s a document object storage that allows you to save your data.
If you are using NativeScript-Angular, there are already some great tutorials on how to get started and some more advanced features. In this article we’ll focus on NativeScript with TypeScript.
What we’ll cover
In this simple article we will see how to save and retrieve some user information by:
- Installing Couchbase
- Creating a TypeScript class that provides a layer of abstraction on top of Couchbase, making it a lot more easy to use (no complex instructions for data insertion and retrieval)
- Implementing the singleton pattern on the class
By the end of the tutorial we’ll be able to use instructions such as
1 2 3 |
userSettings.name = "Frank" //boom! written in our db text=”{{ userSettings.name }}” //boom! read from our db |
without having to write extensive queries to read and write!
Also, all of the code in this article is available in this GitHub repo. Feel free to clone it and play around.
Without further ado, let’s get started!
Install Couchbase plugin
Let’s create a new NativeScript app using the TypeScript template.
1 |
tns create ns-couchbase-demo --tsc |
In the root folder of our newly created NativeScript project, let’s give the following command to add the plugin and save it to our dependencies list:
1 |
tns plugin add nativescript-couchbase |
Once the plugin is successfully installed, let’s create a class that we’ll use to store and retrieve the user information we need.
Abstraction layer class
OPTIONAL: It is usually a good idea to create an interface to define the properties we need to include.
Navigate to the app folder of our project and create a models folder. Here we will define our Models.ts file (app/models/Models.ts):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
export module Models{ export interface IUserSettings { username :string; fullname :string; email :string; } } |
Once we defined our interface, let’s go ahead and create the class that actually implements it. We’ll call the class UserSettings.
Inside this class we also need to store the information related to our Couchbase database.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
let CouchBaseModule = require("nativescript-couchbase"); import { Models } from '../models/Models'; export class UserSettings implements Models.IUserSettings{ private DATABASE_NAME = 'appname-db'; private USER_SETTINGS_DOC_ID = 'usersettings'; private _database; private _userSettingsDocument: Models.IUserSettings; private _userSettingsObj: Models.IUserSettings; private _instance :UserSettings; } |
Let’s break it down:
In the first two lines we require the Couchbase plugin we previously installed along with the Models module we will use to take advantage of TypeScript’s strong typing.
Then we define some other private properties that we’ll use later:
DATABASE_NAME: This is the name of the database. Make sure it doesn’t contain capital letters (and it’s less than 240 characters).
USER_SETTINGS_DOC_ID: Name of the Couchbase document.
database: This is the instance of the database connector.
_userSettingsDocument: Couchbase is a No document-oriented database. It does not have tables, instead it uses a primary storage entity known as Document. Basically a document is a collection of key-value pairs, where the value can be pretty much anything (string, numbers, arrays, etc.). This is what makes Couchbase the number one solution if you need to store a variety of non-relational data: simplicity and flexibility.
_userSettingsObj: This is the JavaScript object that we will use to sync the information with the Couchbase document.
We will cover why we declared the _instance variable later on when we implement the singleton pattern.
Now that we’ve got that covered, let’s see how our class constructor works:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
constructor() { this._database = new CouchBaseModule.Couchbase(this.DATABASE_NAME); this._userSettingsDocument = this._database.getDocument(this.USER_SETTINGS_DOC_ID); if (!this._userSettingsDocument) { console.log("Document does not exist yet :)"); this._userSettingsObj = { username: "", email: "", fullname: "", } this._database.createDocument(this._userSettingsObj, this.USER_SETTINGS_DOC_ID); this._userSettingsDocument = this._database.getDocument(this.USER_SETTINGS_DOC_ID); } } |
Once again, let’s see in detail what’s going on in our constructor:.
this._database = new CouchBaseModule.Couchbase(this.DATABASE_NAME);
Here we are instantiating Couchbase and telling to which database we want to connect.
this._userSettingsDocument = this._database.getDocument(this.USER_SETTINGS_DOC_ID);
This line tells Couchbase to get us our document (the collection which we use to store our info).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
if (!this._userSettingsDocument) { console.log("Document does not exist yet :)"); this._userSettingsObj = { username: "", email: "", fullname: "", } this._database.createDocument(this._userSettingsObj, this.USER_SETTINGS_DOC_ID); this._userSettingsDocument = this._database.getDocument(this.USER_SETTINGS_DOC_ID); } |
If our document doesn’t exist yet (e.g., first time), we create an empty Object of type UserSettings and we also create a new document. The second parameter we assign to the createDocument function is the id of the document. If we don’t supply this parameter Couchbase will assign the document a UUID automatically.
Fantastic! Now that our constructor is done, let’s see how we can set and retrieve information about our user.
We’ll set up some getters and setters to achieve just that.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/*====== USERNAME GETTER AND SETTER ======*/ get username(): string { this._userSettingsObj = this._database.getDocument(this.USER_SETTINGS_DOC_ID ); let username = this._userSettingsObj.username; return username; } set username(value: string) { this._userSettingsObj = this._database.getDocument(this.USER_SETTINGS_DOC_ID ); this._userSettingsObj.username = value; this._database.updateDocument(this.USER_SETTINGS_DOC_ID , this._userSettingsObj); } |
As usual, let’s break the code down.
In the getter we do three things:
1) We read data from our document into our object
2) Get the desired value (in this case the username)
3) Return it
Easy peasy :)
In the setter we do the following:
1) We get the latest version of our userSettingsObject reading it from the db
2) We set the username property to the value passed to the function
3) We update our document in the database. Contrary to the createDocument function, the first parameter is the ID of the document and the second is the object itself.
But … why do we need to getDocument first? Why can’t we just update the document with the object?
I’m glad you asked!
Basically, whenever we update a document, Couchbase keeps track of the change. Each document has a _rev property; this value gets updated by Couchbase every time a write operation on the document takes place. If we try to update a document with a an older _rev, Couchbase will reject it.
Singleton pattern
Who doesn’t love design patterns? Today we’ll go ahead and implement a very simple, yet effective one: the singleton. The singleton pattern, when correctly implemented, “restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system,” which is exactly our case.
Basically we make sure that we only have one instance of our class reading and writing to our database.
How do we do that? A simple way to achieve that is with the following two steps:
1) Making our constructor private (Introduced in ts 2.0)
2) Implementing a getInstance method which returns the current instance if it exists, or a new one if it’s the first call
STEP 1:
1 |
private constructor() {....} |
STEP 2:
Now we can go ahead and create our getInstance() method which will look something like this:
1 2 3 4 5 6 7 8 9 10 11 |
public static getInstance() :UserSettings{ if(!this._instance){ this._instance = new UserSettings(); } return this._instance; } |
Great! We have abstracted data and implemented the singleton pattern, let’s go ahead and see how we can use our shiny new UserSettings class.
We will be editing the existing main-page.xml and main-page.ts files.
First, we’ll define some basic UI layout. For this demo we’ll be using a textfield to get user input, a button to save the value to the db, and a label to display the current value in the database. As you can see, if no data is stored in the database the label will simply display “No data stored in the db.”
This is what our main-page.xml file should look like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo"> <Page.actionBar> <ActionBar title="My App" icon="" class="action-bar"> </ActionBar> </Page.actionBar> <StackLayout> <TextField hint="username" text="{{ username }}" /> <Button text="SAVE MY USERNAME" tap="onTap" class="btn btn-primary btn-active"/> <Label text="{{ dbusername || 'no data stored in the db' }}" textWrap="true"/> </StackLayout> </Page> |
Alright! Now that we’ve got a basic layout we can proceed by adding some logic to the UI.
This is how our main-page.ts file should look:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
import { Observable } from 'data/observable'; //bind data to our view import { UserSettings } from './UserSettings'; //woohoo our amazing database interaction layer import { EventData } from 'data/observable'; //use with typescript for intellisense import { Page } from 'ui/page'; //use with typescript for intellisense let userSettings = UserSettings.getInstance(); let mainPageViewModel; class MainPageViewModel extends Observable{ public username; public dbusername constructor(){ super(); this.dbusername = userSettings.username; } } export function navigatingTo(args: EventData) { let page = <Page>args.object; mainPageViewModel = new MainPageViewModel(); page.bindingContext = mainPageViewModel; } export function onTap(){ userSettings.username = mainPageViewModel.username; mainPageViewModel.set("dbusername",userSettings.username); } |
What have we done? In the first lines we just imported a bunch of stuff, checking the comments to see what the modules do. Next we have:
1 |
let userSettings = UserSettings.getInstance(); |
This is how we get a hold of our UserSettings class instance.
Moving on, we declared another small class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class MainPageViewModel extends Observable{ public username; public dbusername constructor(){ super(); this.dbusername = userSettings.username; } } |
As you can see MainPageViewModel extends the Observable class. If you’re not familiar with Observables know that they are basically JavaScript objects that trigger a notification if one of the properties change. You can find out more here.
Our view model has two properties: username (which we bound to our textfield) and dbusername (which we bound to our label). Whenever we create a new instance of our class we assign to dbusername whatever value is present in our userSettings instance, which in turn goes and reads data in our database (remember our get username() getter method?).
1 2 3 4 5 6 7 8 9 |
export function navigatingTo(args: EventData) { let page = <Page>args.object; mainPageViewModel = new MainPageViewModel(); page.bindingContext = mainPageViewModel; } |
This function gets called everytime we navigate to our page. We instantiate our MainPageViewModel class and assign it to the mainPageViewModel variable and we bind it to our page. Plain and simple.
Last, but not least, we define the behavior for the button click:
1 2 3 4 5 6 7 |
export function onTap(){ userSettings.username = mainPageViewModel.username; mainPageViewModel.set("dbusername",userSettings.username); } |
Once again, very simple: We write to the database whatever value our textfield holds (mainPageViewModel.username is bound to the textfield) and then we go and update the value of dbusername in order to update the label. An image is worth a thousand words, soooo here comes a GIF!
This post is part of the Couchbase Community Writing Program
Thanks Laura for the detailed blog.
Can we have similar blog for Nativescript-vue couchbase on how persist the data ?