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 iOS, Android, and UWP mobile apps.
This tutorial will walk through a simple Xamarin app that will
QueryBuilder
interfaceYou can learn more about Couchbase Mobile here.
This tutorial assumes familiarity with building apps with Xamarin, more specifically Xamarin.Forms using C# and XAML.
If you are unfamiliar with the basics of Couchbase Lite, it is recommended that you walk through the Fundamentals Tutorial on using Couchbase Lite as a standalone database.
For iOS/Mac development, you will need a Mac running MacOS 11 or 12
iOS/Mac (Xcode 12/13) - Download latest version from the Mac App Store or via Xcodes
Note: If you are using an older version of Xcode, which you need to retain for other development needs, make a copy of your existing version of Xcode and install the latest Xcode version. That way you can have multiple versions of Xcode on your Mac. More information can be found in Apple's Developer Documentation. The open source Xcodes project simplifies this process.
Note: You can not edit or debug UWP projects with Visual Studio for Mac and you can't edit or debug Mac projects with Visual Studio for PC.
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 have the ability to select 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
.
git clone https://github.com/couchbase-examples/dotnet-xamarin-cblite-userprofile-query
UserProfileDemo.sln
. The project would be located at /path/to/dotnet-xamarin-cblite-userprofile-query/src
.open UserProfileDemo.sln
The User Profile demo app is a Xamarin.Forms based solution that supports iOS and Android mobile platforms along with the UWP desktop platform.
The solution utilizes various design patterns and principles such as MVVM, IoC, and the Repository Pattern.
The solution consists of seven projects.
.ipa
file..apk
file..exe
file.Now that you have an understanding of the solution architecture let's dive into the app!
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:
The Couchbase.Lite
Nuget package contains the core functionality for Couchbase Lite. In the following sections you will dive into the capabilities it the package provides.
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 app deals with a single Document
with a "type" property of "user". The document ID is of the form "user::demo@example.com"
.
An example of a document would be:
{
"type":"user",
"name":"Jane Doe",
"email":"jane.doe@earth.org",
"address":"101 Main Street",
"image":CBLBlob (image/jpg),
"university":"Missouri State University"
}
The "user" Document
is encoded to a class
named UserProfile.
public class UserProfile
{
public string type => "user";
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Address { get; set; }
public byte[] ImageData { get; set; }
public string Description { get; set; }
public string University { get; set; }
}
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" Document
is encoded to a class
named University.
public class University
{
public string Name { get; set; }
public string Country { get; set; }
}
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 be in your app project bundle:
UserProfileDemo.iOS
project, locate the universities.cblite2
folder at the root.UserProfileDemo.Android
cfrtg project, locate the universities.zip
file within the Assets
folder. Note that the cblite folder will be extracted from the zip file.UserProfileDemo.UWP
project, locate the universities.cblite2
folder at the root.GetDatabaseAsync
method. 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 async Task<Database> GetDatabaseAsync()
DatabaseConfiguration
object and specify the path where the database would be locatedvar options = new DatabaseConfiguration();
var defaultDirectory = Service
.GetInstance<IDefaultDirectoryResolver>()
.DefaultDirectory();
options.Directory = defaultDirectory;
// The path to copy the prebuilt database to
var databaseSeedService = ServiceContainer.GetInstance<IDatabaseSeedService>();
if (databaseSeedService != null)
{
await databaseSeedService.CopyDatabaseAsync(defaultDirectory);
_database = new Database(_databaseName, options);
CreateUniversitiesDatabaseIndexes();
}
If the database is already present at the specified Database location, we simply open the database.
_database = new Database(_databaseName, options);
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.cs file, locate the CreateUniversitiesDatabaseIndexes
method. We create an index on the name
and location
properties of the documents in the university database.
void CreateUniversitiesDatabaseIndexes()
{
_database.CreateIndex("NameLocationIndex",
IndexBuilder.ValueIndex(ValueIndexItem.Expression(Expression.Property("name")),
ValueIndexItem.Expression(Expression.Property("location"))));
}
When a user logs out, we close the pre-built database along with other user-specific databases
Dispose
function.public virtual void Dispose()
_database.Close();
Will open Prebuilt DB at path /Users/[user_id]/Library/Developer/CoreSimulator/Devices/[unique_device_id]/data/Containers/Data/Application/[unique_app_id]/Library/Application Support
2018-05-04 17:04:16.319360-0400 UserProfileDemo[54115:13479070] CouchbaseLite/2.0.0 (Swift; iOS 11.3; iPhone) Build/806 Commit/2f2a2097+CHANGES LiteCore/2.0.0 (806)
2018-05-04 17:04:16.319721-0400 UserProfileDemo[54115:13479070] CouchbaseLite minimum log level is Verbose
Will open/create DB at path /Users/[user_name]/Library/Developer/CoreSimulator/Devices/[unique_device_id]/data/Containers/Data/Application/[unique_app_id]/Library/Application Support/demo@example.com
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 "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.
SearchByName
method.public async Task<List<University>> SearchByName(string name, string country = null)
QueryBuilder
API that will look for Documents that match the specified criteria.var whereQueryExpression = Function
.Lower(Expression.Property("name"))
.Like(Expression.String($"%{name.ToLower()}%")); // <1>
if (!string.IsNullOrEmpty(country))
{
var countryQueryExpression = Function.Lower(Expression.Property("country")).Like(Expression.String($"%{country.ToLower()}%")); // <2>
whereQueryExpression = whereQueryExpression.And(countryQueryExpression); // <3>
}
var 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:
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(database)
specified the Data Source
<5> We include the Where
clause that is the logical ANDing of the QueryExpression in <1> and <2>
Execute()
method on the Query that was constructed in the previous stepvar results = query.Execute().AllResults();
if (results?.Count > 0)
{
universities = new List<University>(); // <1>
foreach (var result in results)
{
var dictionary = result.GetDictionary("universities");
if (dictionary != null)
{
var university = new University
{
Name = dictionary.GetString("name"), // <2>
Country = dictionary.GetString("country") // <2>
};
universities.Add(university);
}
}
}
<1> Create an instance of University type <2> Use specific type getters to fetch property values. The University instance is populated with these property values.
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.