In this part of the learning path, you will be working with the "Audit Inventory" demo app that allows users to log in and access the developer screens in order to validate the use of a pre-build database in the demo application that is used to store warehouse and stock item documents. The warehouse and stock item documents will be used in future steps of the learning path when we need to add projects and audits to the database.
In this step of the learning path you will learn the fundamentals of:
While the demo app has a lot of functionality, this step will walk you through:
Learn Couchbase Lite with Dart and Flutter
repository from GitHub.git clone https://github.com/couchbase-examples/flutter_cbl_learning_path.git
No Data was found
message on the Projects screen.A reminder that 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 comes bundled with a collection of Document
with a "documentType" property of "warehouse". Each document represents an warehouse location that a team would visit in order to perform an audit of the inventory at that location.
An example of a document would be:
{
"warehouseId":"e1839e0b-57a0-472c-b29d-8d57e256ef32",
"name":"Santa Clara Warehouse",
"address1":"3250 Dr Olcott Street",
"address2":"",
"city":"Santa Clara",
"state":"CA",
"postalCode":"95054",
"salesTax":0.0913,
"latitude":32.3803024,
"longitude":-121.9674197,
"documentType":"warehouse",
"yearToDateBalance":0,
"shippingTo": [
"AZ",
"CA",
"HI",
"NV"
],
}
When a "warehouse" is retreived from the database it is stored within an Data Class of type Warehouse.
(explicitToJson: true)
class Warehouse {
final String warehouseId;
final String name;
final String address1;
final String? address2;
final String city;
final String state;
final String postalCode;
final double salesTax;
final double yearToDateBalance;
final double latitude;
final double longitude;
final List<String>? shippingTo;
final String documentType = "warehouse";
const Warehouse(
this.warehouseId,
this.name,
this.address1,
this.address2,
this.city,
this.state,
this.postalCode,
this.salesTax,
this.yearToDateBalance,
this.latitude,
this.longitude,
this.shippingTo);
String toString(){
return name;
}
factory Warehouse.fromJson(Map<String, dynamic> json) =>
_$WarehouseFromJson(json);
Map<String, dynamic> toJson() => _$WarehouseToJson(this);
}
The sample app comes bundled with a collection of Document
with a "documentType" property of "item". Each document represents an item in stock that a team would count in order to perform an audit of the inventory in the warehouse.
An example of a document would be:
{
"itemId":"00b66fdf-9bdb-451b-bd2a-75bdf0459958",
"name":"Bachensteiner Beard Export",
"price":24.22,
"description":"Tranquil Export with Bachensteiner flavors",
"style": "Imperial Stout",
"documentType":"item"
}
When a "item" is retreived from the database it is stored within an Data Class of type StockItem.
(explicitToJson: true)
class StockItem {
String itemId;
String name;
double price;
String description;
String style;
String documentType = "item";
StockItem(this.itemId, this.name, this.price, this.description, this.style);
factory StockItem.fromJson(Map<String, dynamic> json) =>
_$StockItemFromJson(json);
Map<String, dynamic> toJson() => _$StockItemToJson(this);
}
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 "warehouse" and "stockItem" data is separate from the Couchbase Lite instance that holds "user", "project", and "audit" 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 and to help speed up the app vs pulling down the warehoue and items when the app is first installed.
The pre-built database will be in the form of a zip file. It should be in your app project.
Note: The cblite folder will be extracted from the zip file.
All assets must be defined in the pubspec.yaml file. The pubspec.yaml file is located in the root of the project. The following lines should be added to the assets section of the pubspec.yaml file.
# To add assets to your application, add an assets section, like this:
assets:
- asset/images/couchbase.png
- asset/database/startingWarehouses.zip
initDatabases
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. Note that the currentUser is required to setup the inventory database for use which holds the user profile documents, as covered in the Key Value step of the learning path.Future<void> initDatabases({required User user}) async
DatabaseConfiguration
object and specify the path where the database would be locatedfinal dbConfig = DatabaseConfiguration(directory: cblDatabaseDirectory.path);
If the database is already present at the specified Database location, we simply open the database.
// create the warehouse database if it doesn't already exist
if (!File("$cblPreBuiltDatabasePath/$databaseFileName").existsSync()) {
await _unzipPrebuiltDatabase();
await _copyWarehouseDatabase();
}
//open the warehouse database
warehouseDatabase = await Database.openAsync(warehouseDatabaseName, dbConfig);
Note: You MUST copy the pre-built database using the Database.copy function instead of opening it directly or you will run into issues with data syncronization.
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. Indexes can slow down writes, so it's recommended adding indexes as you need them. Indexing is handled eagerly.
In the database_provider.dart file, locate the Future<void> _createDocumentTypeIndex() async
function.
We create an index on the documentType
property of the documents in the warehouseDb and inventoryDb using the databases createIndex function. The createIndex function requires the name of the index along with the expression of what to index.
Future<void> _createDocumentTypeIndex() async {
final expression = Expression.property(documentTypeAttributeName);
final valueIndexItems = {ValueIndexItem.expression(expression)};
final index = IndexBuilder.valueIndex(valueIndexItems);
//copy to local per working with nullable fields
//https://dart.dev/null-safety/understanding-null-safety#working-with-nullable-fields
var warehouseDb = warehouseDatabase;
if (warehouseDb != null) {
final indexes = await warehouseDb.indexes;
if (!(indexes.contains(documentTypeIndexName))) {
await warehouseDb.createIndex(documentTypeIndexName, index);
}
}
var inventoryDb = inventoryDatabase;
if (inventoryDb != null) {
final indexes = await inventoryDb.indexes;
if (!(indexes.contains(documentTypeIndexName))) {
await inventoryDb.createIndex(documentTypeIndexName, index);
}
}
}
When a user logs out, we close the pre-built database along with other user-specific databases
In the database_provider.dart file, locate the Future<void> closeDatabases() async
function.
Closing the databases is pretty straightforward
Future<void> closeDatabases() async {
try {
debugPrint('${DateTime.now()} [DatabaseProvider] info: closing databases');
if (inventoryDatabase != null) {
await inventoryDatabase?.close();
}
if (warehouseDatabase != null) {
await warehouseDatabase?.close();
}
debugPrint('${DateTime.now()} [DatabaseProvider] info: databases closed');
} catch (e){
debugPrint('${DateTime.now()} [DatabaseProvider] error: trying to close databases ${e.toString()}');
}
warehouseDatabase = null;
inventoryDatabase = null;
}
flutter: 2022-10-10 16:35:18.668060 [DatabaseProvider] info: initializing databases
flutter: 16:35:18.819504| [DB] info: Copying prebuilt database from /Users/labeaaa/Library/Developer/CoreSimulator/Devices/58842C04-D81E-47B8-B61C-46F56D0AAD83/data/Containers/Data/Application/17F578E6-129B-4594-8924-7FE0341F63E8/Documents/databases/startingWarehouses.cblite2 to /Users/labeaaa/Library/Developer/CoreSimulator/Devices/58842C04-D81E-47B8-B61C-46F56D0AAD83/data/Containers/Data/Application/17F578E6-129B-4594-8924-7FE0341F63E8/Documents/databases/warehouse.cblite2
The above log messages are from an iOS Simulator and indicates the location of the pre-built database as was as the database for the inventory data.
For Android Emulators, the log message indicates the location would be within the files folder.
For the iOS Simulator you can use the Finder to locate the database. Open up the folder indicated in the log message and you should see the database file.
Congratulations on completing this step of our learning path!
This step of the learning path walked you through an example of how to use a pre-built Couchbase Lite database. Check out the following links for further documenation and continue on to the next step that covers how to insert documents into the database using Batch operations.
References