User Profile Sample: Couchbase Lite Fundamentals
Introduction
Couchbase Mobile brings the power of NoSQL to the edge. It is comprised of three components:
-
Couchbase Lite, an embedded, NoSQL JSON Document Style database for your mobile apps
-
Sync Gateway, an internet-facing synchronization mechanism that securely syncs data between mobile clients and server, and
-
Couchbase Server, a highly scalable, distributed NoSQL database platform
Couchbase Mobile supports flexible deployment models. You can deploy:
-
Couchbase Lite as a standalone embedded database within your mobile apps or,
-
Couchbase Lite enabled mobile clients with a Sync Gateway to synchronize data between your mobile clients or,
-
Couchbase Lite enabled clients with a Sync Gateway to sync data between mobile clients and the Couchbase Server, which can persist data in the cloud (public or private)
Prerequisites
This tutorial assumes familiarity with building Xamarin, more specifically Xamarin.Forms, apps using C# and XAML.
-
iOS (Xcode 12.5+)
-
Android (SDK 22+)
-
UWP (Windows 10)
-
git (Optional) This is required if you would prefer to pull the source code from GitHub repo.
-
Create a free github account if you don’t already have one
-
git can be downloaded from git-scm.org
-
App Overview
We will be working with a very simple User Profile app. It does one thing — Allow a user to log in and create or update their user profile data*
The user profile data is persisted as a Document in the local Couchbase Lite Database. So, when the user logs out and logs back in again, the profile information is loaded from the Database.
Installation
Clone the standalone branch of the User Profile Demo
solution from GitHub.
Assuming that you have
Git
installed you can do this using the following command:
git clone -b standalone https://github.com/couchbaselabs/userprofile-couchbase-mobile-xamarin.git
Next, let’s verify the installation.
Solution Overview
The User Profile demo app is a Xamarin.Forms based solution that supports iOS and Android mobile platforms.
The solution utilizes various design patterns and principles such as MVVM, IoC, and the Repository Pattern.
The solution consists of seven projects.
-
UserProfileDemo: A .NET Standard project responsible for maintaining view-level functionality.
-
UserProfileDemo.Core: A .NET Standard project responsible for maintaining viewmodel-level functionality.
-
UserProfileDemo.Models: A .NET Standard project consisting of simple data models.
-
UserProfileDemo.Repositories: A .NET Standard project consisting of repository classes responsible for Couchbase Lite database initialization, interaction, etc.
-
UserProfileDemo.iOS: A Xamarin.iOS platform project responsible for building the
.ipa
file. -
UserProfileDemo.Android: A Xamarin.Android platform project responsible for building the
.apk
file. -
UserProfileDemo.UWP: A UWP platform project responsible for building the
.exe
file.
Now that you have an understanding of the solution architecture let’s dive into the app!
Couchbase Lite Nuget
Before diving into the code for the apps, it is important to point out the Couchbase Lite dependencies within the solution. The Couchbase.Lite Nuget package is included as a reference within four projects of this solution:
-
UserProfileDemo.Repositories
-
UserProfileDemo.iOS
-
UserProfileDemo.Android
-
UserProfileDemo.UWP
The Couchbase.Lite
Nuget package contains the core functionality for Couchbase Lite.
In the following sections you will dive into the capabilities the package provides.
Getting started on Android
In order to use Couchbase Lite within a Xamarin app for Android you will need to activate it.
Open
MainActivity.cs
in the UserProfileDemo.Android
project.
Couchbase.Lite.Support.Droid.Activate(this);
Data Model
Let’s take a look at the foundations of Couchbase; data models and documents.
Couchbase Lite is a JSON Document Store.
A Document is a logical collection of named fields and values.
The values are any valid JSON types.
In addition to the standard JSON types, Couchbase Lite supports Date
and Blob
data types.
While it is not required or enforced, it is a recommended practice to include a "type" property that can serve as a namespace for related documents.
The User Profile Document
The app deals with a single Document with a "type" property of "user" as shown in Example 1. The document ID is of the form "user::demo@example.com".
{
"type":"user",
"name":"Jane Doe",
"email":"jame.doe@earth.org",
"address":"101 Main Street",
"image":CBLBlob (image/jpg) (1)
}
1 | The special 'blob' data type associated with the profile image — see: Working with Blobs |
The User Record
The "user" Document is encoded to a native struct named UserRecord as shown in Example 2
Unresolved include directive in modules/userprofile-standalone-xamarin/pages/userprofile_basic.adoc - include::example$UserProfileDemo/model/UserRecord.swift[]
Basic Database Operations
In this section, we will do a code walk-through of the basic Database operations
Create / Open a Database
When a user logs in, we create an empty Couchbase Lite database for the user if one does not exist.
-
Open the BaseRepository.cs file and locate the
Database
property. When theDatabase
property is used for the first time a Couchbase Lite database is opened, or created if it does not already exist via the instantiation of a new object.Database _database; protected Database Database { get { if (_database == null) { _database = new Database(DatabaseName, DatabaseConfig); } return _database; } private set => _database = value; }
-
We create an instance of the
DatabaseConfiguration
within UserProfileRepository.cs via anabstract
requirement from the parent class,BaseRepository
.
This is an optional step. In our case, we would like to override the default path of the database. Every user has their own instance of theDatabase
that is located in a folder corresponding to the user. Please note that the default path is platform specific.DatabaseConfiguration _databaseConfig; protected override DatabaseConfiguration DatabaseConfig { get { if (_databaseConfig == null) { if (AppInstance.User?.Username == null) { throw new Exception($"Repository Exception: A valid user is required!"); } _databaseConfig = new DatabaseConfiguration { Directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), AppInstance.User.Username) }; } return _databaseConfig; } set => _databaseConfig = value; }
-
Then we create a local Couchbase Lite database named "userprofile" for the user. If a database already exists for the user, the existing version is returned.
_database = new Database(DatabaseName, DatabaseConfig);
Listening to Database Changes
You can be asynchronously notified of any change (add, delete, update) to the Database
by registering a change listener with the Database
.
In our app, we are not doing anything special with the Database
change notification other than logging the change.
In a real world app, you would use this notification for instance, to update the UI.
-
Open the BaseRepository.cs file and locate the
Database.AddChangeListener
function usage within the constructor.DatabaseListenerToken = Database.AddChangeListener(OnDatabaseChangeEvent);
-
To register a change listener with the database we add the delegate method
OnDatabaseChangeEvent
. This is an optional step. TheAddChangeListener
method returns aListenerToken
. TheListenerToken
is required to remove the listener from the database.
The listener is a delegate method that takes two parameters of typeobject
andDatabaseChangedEventArgs
respectively.void OnDatabaseChangeEvent(object sender, DatabaseChangedEventArgs e) { foreach (var documentId in e.DocumentIDs) { var document = Database?.GetDocument(documentId); string message = $"Document (id={documentId}) was "; if (document == null) { message += "deleted"; } else { message += "added/updaAted"; } Console.WriteLine(message); } }
Close Database
When a user logs out, we close the Couchbase Lite database associated with the user, deregister any database change listeners, and free up memory allocations.
Open the
BaseRepository.cs
file and locate the Dispose
method.
In our sample Dispose
handles the removal of database listeners, removing various objects from memory, and closing the database.
Dispose
will be called when a user logs out.
public void Dispose()
{
DatabaseConfig = null;
Database.RemoveChangeListener(DatabaseListenerToken);
Database.Close();
Database = null;
}
Document Operations
Once an instance of the Couchbase Lite database is created/opened for the specific user, we can perform basic Document
functions on the database.
In this section, we will walk-through the code that describes basic Document
operations
Reading a Document
Once the user logs in, the user is taken to the "Your Profile" screen. A request is made to load The User Profile Document for the user. When the user logs in the very first time, there would be no user profile document for the user.
-
Open the UserProfileViewModel.cs file and locate the
userProfileDocId
definition. This document Id is constructed by prefixing the term "user::" to the username of the user.string UserProfileDocId => $"user::{AppInstance.User.Username}";
-
The
UserProfileViewModel
is tasked with retrieving the profile for a logged in user. It does this by using a class that implementsIUserProfileRepository
.var up = UserProfileRepository?.Get(UserProfileDocId);
-
In the UserProfileRepository.cs file, locate the
Get
function.public override UserProfile Get(string userProfileId)
-
We try to fetch the document with specified
userProfileDocId
from the database.var document = Database.GetDocument(userProfileId); if (document != null) { userProfile = new UserProfile { Id = document.Id, Name = document.GetString("Name"), Email = document.GetString("Email"), Address = document.GetString("Address"), ImageData = document.GetBlob("ImageData")?.Content }; }
1 | Fetch an immutable copy of the Document from the database. |
2 | Create an instance of The User Record object |
3 | Set the email property of the UserProfile with the username of the logged in user.Note: This value is not editable after it’s initially saved. |
4 | If the document exists and is fetched successfully, a variety of methods exist that can be used to fetch members of the Document .
Specifically, note the support of the GetBlob type to fetch the value of a property of type Blob . |
Creating / Updating a Document
A The User Profile Document is created for the user when the user taps the "Done" button on the "Profile Screen". The function below applies whether you are creating a document or updating an existing version
-
The
UserProfileViewModel
is tasked with setting values of a profile for a logged in user, and saving them to the database. It does this by using a class that implementsIUserProfileRepository
.bool? success = UserProfileRepository?.Save(userProfile);
-
Open the UserProfileRepository.cs file and locate the
Save
function.public override bool Save(UserProfile userProfile)
-
We create a mutable instance of the
Document
. By default, all APIs in Couchbase Lite deal with immutable objects, thereby making them thread-safe by design. In order to mutate an object, you must explicitly get a mutable copy of the object. Use appropriate type-setters to set the various properties of theDocument
var mutableDocument = new MutableDocument(userProfile.Id); mutableDocument.SetString("Name", userProfile.Name); mutableDocument.SetString("Email", userProfile.Email); mutableDocument.SetString("Address", userProfile.Address); mutableDocument.SetString("type", "user"); if (userProfile.ImageData != null) { mutableDocument.SetBlob("ImageData", new Blob("image/jpeg", userProfile.ImageData)); }
1 | Specifically, note the support of the SetBlob type to fetch the value of a property of type Blob .
|
Learn More
Congratulations on completing this tutorial!
This tutorial walked you through a very basic example of how to get up and running with Couchbase Lite as a local-only, standalone embedded data store in your iOS, Android, or UWP app. If you want to learn more about Couchbase Mobile, check out the following links.