Couchbase Lite 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
QueryBuilder
interfaceYou can learn more about Couchbase Mobile here.
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 Quickstart in Couchbase Lite with Android and Java Tutorial on using Couchbase Lite as a standalone database.
Android SDK installed and setup (v.31)
Android Build Tools (> v.31)
Android device or emulator running API level 22 or above
JDK 8 (now embedded into Android Studio Artic Fox)
We will be working with a very simple "User Profile" app. If you had walked through the Quickstart in Couchbase Lite with Android and Java 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 Quickstart in Couchbase Lite with Android and Java Tutorial.
As part of profile information, users can now selecting a University
from a list of possible options.
The list of matching universities 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 subsequently, when the user logs out and logs back in again, the profile information is loaded from the Database
.
User Profile Query Demo
repository from GitHub. git clone https://github.com/couchbase-examples/android-java-cblite-userprofile-query.git
allprojects {
repositories {
...
maven {
url "https://mobile.maven.couchbase.com/maven2/dev/"
}
}
}
Then add the following to the app/build.gradle file.
dependencies {
...
implementation 'com.couchbase.lite:couchbase-lite-android-ee:3.0.0'
}
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.
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.
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.
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 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"
}
Open the UserProfilePresenter.java file in the com.couchbase.userprofile.profile directory. 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 are used as a data storage mechanism between the app's UI and the backing functionality of the Couchbase Lite Document
object.
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"
}
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<>();
properties.put("name", row.getDictionary("universities").getString("name"));
properties.put("country", row.getDictionary("universities").getString("country"));
properties.put("web_pages", row.getDictionary("universities").getArray("web_pages"));
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.
The pre-built database will be in the form of a cblite
file. It should be in your app project bundle
universities.zip
file within the Assets
folder.Note: The cblite folder will be extracted from the zip file.
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 openOrCreateDatabaseForUser(Context context, String username)]
DatabaseConfiguration
object and specify the path where the database would be locatedDatabaseConfiguration config = new DatabaseConfiguration();
config.setDirectory(context.getFilesDir().toString());
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();
}
}
Creating indexes for non-FTS based queries is optional. However, 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();
}
}
When a user logs out, we close the Prebuilt Database along with other user-specific databases
closePrebuiltDatabase()
function.public void closePrebuiltDatabase()
userprofileDatabase.close();
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 indicates the location of the Prebuilt database as well as the Database for the user. This would be within the files folder.
Open the folder on your computer and verify that a Database with name "univerities" exists along with a user specific Database with name "userprofile"
The Query API in Couchbase Lite is extensive. In our app, we will be using the QueryBuilder
API to make a simple pattern matching query using the like
operator.
From the "Your Profile" screen, when the user taps on the "Select University" button, 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.
fetchUniversities
function.public void fetchUniversities(String name) {
fetchUniversities(name, null);
}
public void fetchUniversities(String name, String country)
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>
QueryExpression
that uses the like
operator to look for the specified "name" string in the "name" property. Notice a couple of things here:Function.lower()
to convert the search string into lowercase equivalent. Since the like
operator does case-senstive matching, we convert the search string and the property value to lowercase equivalents and compare the two.QueryExpression
that uses the like
operator to look for the specified "location" string in the "location" property.SelectResult.all()
specifies that we are interested in all properties in Documents that match the specified criteriaDataSource.database(db)
specified the Data Sourcewhere
clause that is the logical ANDing of the QueryExpression in <1> and <2>execute()
method on the Query that was constructed in the previous steptry {
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);
}
HashMap
).array
types. This returns a Couchbase Lite ArrayObject
type.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.