A newer version of this documentation is available.

View Latest

User Profile Sample: Couchbase Lite Query Introduction

Introduction

Couchbase Lite 2.0 brings powerful querying and Full-Text-Search(FTS) capabilties to the edge. The new query interface is based on N1QL, Couchbase’s declarative query language that extends SQL for JSON. If you are familiar with SQL, you will feel right at home with the semantics of the new API. The query API is designed using the Fluent API Design Pattern, and it uses method cascading to read to like a Domain Specific Language (DSL). This makes the interface very intuitive and easy to understand.

Couchbase Lite can be used as a standalone embedded database within your mobile app.

This tutorial will walk through a simple Android app that will

  • Demonstrate how you can bundle, load and use a prebuilt instance of Couchbase Lite 2.0

  • Introduce you to the basics of the QueryBuilder interface

You can learn more about Couchbase Mobile here

Prerequisites

This tutorial assumes familiarity with building Android apps using Java and with the basics of Couchbase Lite.

  • If you are unfamiliar with the basics of Couchbase Lite, it is recommended that you walk through the Fundamentals Tutorial on using Couchbase Lite 2.0 as a standalone database

  • Android Studio 3.4 or above

  • Android device or emulator running API level 21 or above

  • Android SDK 26

  • Android Build Tools 26

  • JDK 8

  • git (Optional) This is required if you would prefer to pull the source code from GitHub repo.

App Overview

We will be working with a very simple "User Profile" app. If you had walked through the Fundamentals tutorial, you would quickly realize that this version extends the functionality introduced in the version introduced in that tutorial.

This app does the following

  • Allows users to log in and create or update his/her user profile information. You could do that in the Fundamentals tutorial

  • As part of profile information, users can now selecting a University from a list of possible options.

The list of matching univerisities is queried (using the new Query API) from a local prebuilt "University" Couchbase Lite database that is bundled in the app. The user profile information is persisted as a Document in the local Couchbase Lite database. So subsquently, when the user logs out and logs back in again, the profile information is loaded from the Database.

App Overview

Installation

Fetching App Source Code

Option 1 : Git Clone

  • Clone the query branch of the User Profile Demo solution from GitHub. Assuming that you have Git installed you can use the following command to clone the solution:

      git clone -b query https://github.com/couchbaselabs/userprofile-couchbase-mobile-android.git

Option 2 : Download .zip

  • Download the User Profile Demo solution directly from here.

Once all of the solution bits have been retrieved you can verify the installation, and run the app!

Installing Couchbase Lite

  • This sample project already contains the appropriate additions for downloading, and utilizing the Android Couchbase Lite dependency module. However, in the future, to include Couchbase Lite support within an Android app add the the following within app/build.gradle.

      dependencies {
        ...
    
        implementation 'com.couchbase.lite:couchbase-lite-android-ee:2.5.0'
    }

Try it out

  • Open build.gradle using Android Studio.

  • Build and run the project.

  • Verify that you see the login screen.

    User Profile Login Screen Image

Sample App Architecture

The sample app follows the MVP pattern, separating the internal data model, from a passive view through a presenter that handles the logic of our application and acts as the conduit between the model and the view.

MVP Architecture

In the Android Studio project, the code is structured by feature. You can select the Android option in the left navigator to view the files by package.

MVP Android Studio

Each package contains 3 different files:

  • Activity: This is where all the view logic resides.

  • Presenter: This is where all the business logic resides to fetch and persist data to a web service or the embedded Couchbase Lite database.

  • Contract: An interface that the Presenter and Activity implement.

MVP Package

Data Model

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 some special types like Date and Blob. 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.

The "User Profile" Document

The app deals with a single Document with a "type" property of "user". The document ID is of the form "user::<email>". An example of a document would be

{
    "type":"user",
    "name":"Jane Doe",
    "email":"jame.doe@earth.org",
    "address":"101 Main Street",
    "image":CBLBlob (image/jpg),
    "university":"Missouri State University"
}

UserProfile

For the purpose of this tutorial the "user" Document is first stored within an Object of type Map<String, Object>.

Map<String, Object> profile = new HashMap<>();
profile.put("name", nameInput.getText().toString());
profile.put("email", emailInput.getText().toString());
profile.put("address", addressInput.getText().toString());
profile.put("university", universityText.getText().toString());

byte[] imageViewBytes = getImageViewBytes();

if (imageViewBytes != null) {
    profile.put("imageData", new com.couchbase.lite.Blob("image/jpeg", imageViewBytes));
}

The Map<String, Object> object functions used as a data storage mechanism between the app’s UI and the backing functionality of the Couchbase Lite Document object.

The "University" Document

The app comes bundled with a collection of Documents of type "university". Each Document represents a university.

{
    "type":"university","web_pages": [
      "http://www.missouristate.edu/"
    ],
    "name": "Missouri State University",
    "alpha_two_code": "US",
    "state-province": MO,
    "domains": [
      "missouristate.edu"
    ],
    "country": "United States"
}

The University Record

When "university" Document is retrieved from the database it is stored within an Object of type Map<String, Object>.

Map<String, Object> properties = new HashMap<>(); (1)
properties.put("name", row.getDictionary("universities").getString("name")); (2)
properties.put("country", row.getDictionary("universities").getString("country")); (2)
properties.put("web_pages", row.getDictionary("universities").getArray("web_pages")); (3)

Using a Prebuilt Database

There are several reasons why you may want to bundle your app with a prebuilt database. This would be suited for data that does not change or change that often, so you can avoid the bandwidth and latency involved in fetching/syncing this data from a remote server. This also improves the overall user experience by reducing the start-up time.

In our app, the instance of Couchbase Lite that holds the pre-loaded "university" data is separate from the Couchbase Lite instance that holds "user" data hold the pre-loaded data. A separate Couchbase Lite instance is not required. However, in our case, since there can be many users potentially using the app on a given device, it makes more sense to keep it separate. This is to avoid duplication of pre-loaded data for every user.

Location of the cblite file

The pre-built database will be in the form of a cblite file. It should be be in your app project bundle

  • In the universities.zip file within the Assets folder.

    Prebuilt Database Location

    Note: The cblite folder will be extracted from the zip file.

Loading the Prebuilt Database

  • Open the DatabaseManager.java file and locate the openPrebuiltDatabase() function. The prebuilt database is common to all users of the app (on the device). So it will be loaded once and shared by all users on the device.

    public void openPrebuiltDatabase(Context context)
  • First, we create an instance of DatabaseConfiguration object and specify the path where the database would be located

    DatabaseConfiguration config = new DatabaseConfiguration(context);
    config.setDirectory(context.getFilesDir().toString());
  • Then we determine if the "universities" database already exists at the specified location. It would not be present if this is the first time we are using the app, in which case, we locate the "universities.cblite" resource in the App’s main bundle and we copy it over to the Database folder.

    If the database is already present at the specified Database location, we simply open the database.

    if (!dbFile.exists()) {
        AssetManager assetManager = context.getAssets();
        try {
            File path = new File(context.getFilesDir().toString());
    
            unzip(assetManager.open("universities.zip"), path);
    
            universityDatabase = new Database("universities", config);
            createUniversityDatabaseIndexes();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (CouchbaseLiteException e) {
            e.printStackTrace();
        }
    }
    else {
        try {
            universityDatabase = new Database("universities", config);
        } catch (CouchbaseLiteException e) {
            e.printStackTrace();
        }
    }

Indexing the Prebuilt Database

  • Creating indexes for non-FTS based queries is optional. However, in order to speed up queries, you can create indexes on the properties that you would query against. Indexing is handled eagerly.

  • In the DatabaseManager.java file, locate the createUniversityDatabaseIndexes() function. We create an index on the name and location properties of the documents in the university database.

    private void createUniversityDatabaseIndexes() {
        try {
            universityDatabase.createIndex("nameLocationIndex", IndexBuilder.valueIndex(ValueIndexItem.expression(Expression.property("name")),
                    ValueIndexItem.expression(Expression.property("location"))));
        } catch (CouchbaseLiteException e) {
            e.printStackTrace();
        }
    }

Closing the Database

When a user logs out, we close the Prebuilt Database along with other user-specific databases

  • In the DatabaseManager.java file, locate the closePrebuiltDatabase() function.

    public void closePrebuiltDatabase()
  • Closing the database is pretty straightforward

    userprofileDatabase.close();

Try It Out

  • The app should be running in the simulator

  • Log into the app with any email Id and password. Let’s use the values "demo@example.com" and "password" for user Id and password fields respectively. If this is the first time that any user is signing in to the app, the pre-built database will be loaded from the App Bundle. In addition, new user-specific Database will be created / opened.

  • Confirm that the console log output has a message similar to the one below. In my example, I am logging in with a user email Id of "demo@example.com".

    2019-06-12 13:07:12.542 24206-24206/com.couchbase.userprofile I/CB-Update: Will open Prebuilt DB  at path /data/user/0/com.couchbase.userprofile/files
  • The above log message indicate the location of the Prebuilt database as well as the Database for the user. This would be within the files folder.

Exploring the Query API

The Query API in Couchbase Lite 2.0 is extensive. In our app, we will be using the QueryBuilder API to make a simple pattern matching query using the like operator.

Fetching University Document

From the "Your Profile" screen, when the user taps on the "University" cell, a search screen is displayed where the user can enter the search criteria (name and optionally, the location) for the university. When the search criteria is entered, the local "universities" Database is queried for The "University" Document documents that match the specified search criteria.

  • Open the UniversitiesPresenter.java file and locate the fetchUniversities function.

    public void fetchUniversities(String name) {
        fetchUniversities(name, null);
    }
    
    public void fetchUniversities(String name, String country)
  • We build the Query using the QueryBuilder API that will look for Documents that match the specified criteria.

    Expression whereQueryExpression = Function.lower(Expression.property("name")).like(Expression.string("%" + name.toLowerCase() + "%")); (1)
    
    if (country != null && !country.isEmpty()) {
        Expression countryQueryExpression = Function.lower(Expression.property("country")).like(Expression.string("%" + country.toLowerCase() + "%")); (2)
    
        whereQueryExpression = whereQueryExpression.and(countryQueryExpression); (3)
    }
    
    Query query = QueryBuilder.select(SelectResult.all()) (4)
                              .from(DataSource.database(database)) (5)
                              .where(whereQueryExpression); (6)
    1 Build a QueryExpression that uses the like operator to look for the specified "name" string in the "name" property. Notice couple of things here: (a) The use of wildcard "%" operator to denote that we are looking for the presence of the string anywhere in the "name" property and (b) The use of Function.lower() to convert the search string into lowercase equivalent. Since like operator does case-senstive matching, we convert the search string and the property value to lowercase equivalents and compare the two.
    2 If the location criteria was specified in the search, then Build a QueryExpression that uses the like operator to look for the specified "location" string in the "location" property.
    3 The SelectResult.all() specifiees that we are interested in all properties in Documents that match the specified criteria
    4 The DataSource.database(db) specified the Data Source
    5 We include the where clause that is the logical ANDing of the QueryExpression in <1> and <2>
  • We run the Query by calling the execute() method on the Query that was constructed in the previous step

    try {
        rows = query.execute();
    } catch (CouchbaseLiteException e) {
        e.printStackTrace();
        return;
    }
    
    List<Map<String, Object>> data = new ArrayList<>();
    
    Result row;
    
    while((row = rows.next()) != null) {
        Map<String, Object> properties = new HashMap<>(); (1)
        properties.put("name", row.getDictionary("universities").getString("name")); (2)
        properties.put("country", row.getDictionary("universities").getString("country")); (2)
        properties.put("web_pages", row.getDictionary("universities").getArray("web_pages")); (3)
    
        data.add(properties);
    }
    1 Create an instance of [UniversityRecord] (via HashMap).
    2 Use specific type getters to fetch property values. These [UniversityRecord] instance is populated with these property values.
    3 Getters also available for array types. This returns a Couchbase Lite ArrayObject type.

Try It Out

  • You should have followed the steps discussed in the "Try It Out" section under Loading the Prebuilt Database

  • Tap on "University" table cell

  • You should see a screen show that allows you enter the search criteria for the university

  • Enter "Missouri State" for name . You can optionally enter "united states" for location

  • Confirm that you see a list of universities that match the criteria

    University List
  • Select a university

  • Press "Done" button

  • Confirm that the university you selected shows up in the University table cell

    University Selection
  • You can optionally fill in other entries in the User Profile screen

  • Tap "Done" button

  • Confirm that you see an alert message "Succesfully Updated Profile". The Document will be updated this time.

  • Tap "Log Off" and log out of the app

  • Log back into the app with the same user email Id and password that you used earlier. In my example, I used "demo@example.com" and "password". So I will log in with those credentials again.

  • Confirm that you see the profile screen with the university value that you set earlier.

    Log Off and Log Back On

Learn More

Congratulations on completing this tutorial!

This tutorial walked you through an example of how to use a pre-built Couchbase Lite database. We looked at a simple Query example. Check out the following links for further details on the Query API.