In this part of our learning path, you will walk through the "Audit Inventory" demo application of using Couchbase Lite in standalone mode, which means the database does not sync information from other resources using replication. While the demo app has a lot of functionality, this step will walk you through:
In this step of the learning path, you will be working with the code that allows users to log in and make changes to their user profile information. User profile information is persisted as a Document
in the local Couchbase Lite Database
. When the user logs out and logs back in again, the profile information is loaded from the Database
.
Learn Couchbase Lite with Kotlin and Jetpack Compose
repository from GitHub.git clone https://github.com/couchbase-examples/android-kotlin-cbl-learning-path.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-ktx:3.0.0"
}
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 sample app deals with a single Document
with a "documentType" property of "user". The document ID is of the form "user::<email>".
An example of a document would be:
{
"surname":"Doe",
"givenName":"Jane",
"jobTitle":"Developer",
"team":"team1",
"email":"demo@example.com"
"documentType":"user",
"imageData":
{
"length":217527,
"content_type":"image/jpeg",
"digest":"sha1-+8y7vkRd2J95kVe/yG2WhuEFa4o=",
"@type":"blob"
},
}
Special Blob data type that is associated with the profile image.
The team property designates which team a user is a member of. The team property is a security boundary used as part of the database name, which means users in the same team will use the same database file if a user shares the same physical device or emulator. To simplify things in this learning path, the MockAuthenticationService class defines users and which teams they belong to. Security rules, team membership, and authentication would normally be handled in an OAuth provider or a custom system in an actual mobile application.
Open the UserProfileViewModel.kt ViewModel in the com.couchbase.learningpath.ui.profile directory. For the purpose of this tutorial the "user" Document
is first stored within an Any
of type HashMap<String, Any>
.
val profile = HashMap<String, Any>()
profile["givenName"] = givenName.value as Any
profile["surname"] = surname.value as Any
profile["jobTitle"] = jobTitle.value as Any
profile["email"] = emailAddress.value as Any
profile["team"] = team.value as Any
profile["documentType"] = "user" as Any
profilePic.value?.let {
val outputStream = ByteArrayOutputStream()
it.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
profile["imageData"] =
Blob("image/jpeg", outputStream.toByteArray()) as Any
}
The HashMap<String, Any>
object functions are used as a data storage mechanism between the app's UI and the backing functionality of the Couchbase Lite Document
object.
In this section, we will do a code walkthrough of the basic Database operations.
Before you can start using Couchbase Lite on Android, you would have to initialize it. Couchbase Lite needs to be initialized with the appropriate Android Application Context.
init
constructor.class InventoryDatabase private constructor (private val context: Context) {
...
init {
//setup couchbase lite
CouchbaseLite.init(context)
...
}
When a user logs in, we create an empty Couchbase Lite database if one does not exist.
initializeDatabases
function.fun initializeDatabases(currentUser: User)
NOTE: You will notice that this code has initialization for two different databases, the inventory database that we will be usign in this step, and the warehouse database. For this step you can ignore the code that initializes the warehouse database as we will cover it in a later step.
DatabaseConfiguration
within DatabaseManager.kt. Each team has their own instance of the Database that is located in a folder corresponding to the team name. Please note that the default path is platform-specific.val dbConfig = DatabaseConfigurationFactory.create(context.filesDir.toString())
// create or open a database to share between team members to store
// projects, assets, and user profiles
// calculate database name based on current logged in users team name
val teamName = (currentUser.team.filterNot { it.isWhitespace() }).lowercase()
currentInventoryDatabaseName = teamName.plus("_").plus(defaultInventoryDatabaseName)
inventoryDatabase = Database(currentInventoryDatabaseName, dbConfig)
When a user logs out or the app goes to the background, we close the Couchbase Lite database.
closeDatabases()
function.fun closeDatabases() {
inventoryDatabase?.close()
Deletion of a database is pretty straightforward and this is how you would do it.
fun deleteDatabases() {
try {
closeDatabases()
Database.delete(currentInventoryDatabaseName, context.filesDir)
Database.delete(warehouseDatabaseName, context.filesDir)
} catch (e: Exception) {
android.util.Log.e(e.message, e.stackTraceToString())
}
}
fun closeDatabases() {
try {
inventoryDatabase?.close()
locationDatabase?.close()
} catch (e: java.lang.Exception) {
android.util.Log.e(e.message, e.stackTraceToString())
}
}
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
Once the user logs in, the user is taken to the "Projects" screen. From this screen the user would tap on the Drawer menu icon (sometimes referred to the Hamburger icon) in the upper left hand corner of the screen and tap on the Update User Profile text button. 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.
getCurrentUserDocumentId
function. This document Id is constructed by prefixing the term "user::" to the email Id of the user.private fun getCurrentUserDocumentId(currentUser: String): String {
return "user::${currentUser}"
}
get
method.override suspend fun get(currentUser: String): Map<String, Any> {
Note: The
get
function is required by the interface KeyValueRepository found in the file KeyValueRepository.kt.
Document
with specified documentId
from the database.val results = HashMap<String, Any>() // <1>
results["email"] = currentUser as Any // <2>
val database = InventoryDatabase.getInstance(context).database
database?.let { db ->
val documentId = getCurrentUserDocumentId(currentUser)
val doc = db.getDocument(documentId) // <3>
if (doc != null) {
if (doc.contains("givenName")) { // <4>
results["givenName"] = doc.getString("givenName") as Any // <4>
}
if (doc.contains("surname")) { // <4>
results["surname"] = doc.getString("surname") as Any // <4>
}
if (doc.contains("jobTitle")) { // <4>
results["jobTitle"] = doc.getString("jobTitle") as Any // <4>
}
if (doc.contains("team")) { // <4>
results["team"] = doc.getString("team") as Any // <4>
}
if (doc.contains("imageData")) { // <4>
results["imageData"] = doc.getBlob("imageData") as Any // <4>
}
}
}
return@withContext results // <5>
HashMap<String, Any>
.email
property of the UserProfile with the email Id of the logged-in user. This value is not editable.getBlob
type to fetch the value of a property of type Blob
A "User Profile" Document is created for the user when the user taps the "Save" button on the "Edit Profile" view. The method below applies whether you are creating a document or updating an existing version.
save
function.override suspend fun save(data: Map<String, Any>): Boolean {
val mutableDocument = MutableDocument(documentId, data)
database?.save(mutableDocument)
}
We don't delete a Document
in this sample app. However, deletion of a document is pretty straightforward and this is how you would do it.
val document = db.getDocument(documentId)
document?.let {
db.delete(it)
result = true
}
Document
will be created.Document
will be updated this time.Congratulations on completing the first step of the learning path by reviewing the code that saves User Profiles! In this section, we walked 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 Android app. Continue on to the next step to learn how to use a pre-built database with Couchbase Lite.