Webinar: Couchbase for Mobile - Introduction to Couchbase Lite
Webinar - Developing with Couchbase Lite and iOS
-
Upload
couchbase -
Category
Technology
-
view
747 -
download
4
description
Transcript of Webinar - Developing with Couchbase Lite and iOS
“Tell Them What You’re Going To Tell Them”
• InstallaFon and setup walk-‐through
• Demo of Grocery Sync sample app
• Tour of Grocery Sync code with digressions about the API
and the document model
and querying
• Beyond Grocery Sync
GeNng Up And Running
• Download Couchbase Lite
• Download Grocery Sync sample code
• Copy framework into sample app folder
• Build & Run
1. Download Couchbase Litewww.couchbase.com/download#cb-‐mobile
Download Grocery Syncgithub.com/couchbaselabs/Grocery-‐Sync-‐iOS
Plug In The Framework
option
Run & Sync
Live Demo
A Tour of the Code and the API
IniFalizaFonDemoAppDelegate.m:64
// Initialize Couchbase Lite and find/create my database: NSError* error; self.database = [[CBLManager sharedInstance] databaseNamed: kDatabaseName error: &error]; if (!self.database) [self showAlert: @"Couldn't open database" error: error fatal: YES];
CBLManager
Database “otherdb”
CBLDatabase “db”
CBLDocument “doc3”
CBLDocument “doc2”
Document “doc1”
CBLDocument “doc1” !{ “text”: “Vacuum”, “created”: “2013-10-08”, “check”: false }
thumb.jpg
Manager, Databases, Documents
Manager
• CollecFon of named databases
• Generally a singleton Unless you run on mulEple threads
• Manages database storage (local directory)
Database
• Namespace for documents
• Contains views and their indexes
• Contains validaFon funcFons
• Source and target of replicaFon
Document
• Has unique ID within its database
• Contains arbitrary* JSON object *except keys that start with “_” are reserved
There is no explicit, enforced schema
DenormalizaEon is OK — use arrays or dicEonaries
• May contain binary aaachments Data blobs, can be large, tagged with MIME type
• Versioned MulE-‐Version Concurrency Control (MVCC)
Every update creates a revision ID (based on digest of contents)
Revision ID history is stored
IniFalizaFonDemoAppDelegate.m:64
// Initialize Couchbase Lite and find/create my database: NSError* error; self.database = [[CBLManager sharedInstance] databaseNamed: kDatabaseName error: &error]; if (!self.database) [self showAlert: @"Couldn't open database" error: error fatal: YES];
CreaFng a Database ViewRootViewController.m:101
// Define a view with a map function that indexes to-‐do items by creation date: [[theDatabase viewNamed: @"byDate"] setMapBlock: MAPBLOCK({ id date = doc[@"created_at"]; if (date) emit(date, doc); }) reduceBlock: nil version: @"1.1"];
Not a UIView — a database “view” is like an index.
View “completed”
Views & Queries
CBLDatabase “db”
CBLView “byDate”
function(doc) { emit(doc.created, doc.title); }map function
key value docID
“2013-‐03-‐12” “taxes” “doc17”
“2013-‐09-‐30” “call mom” “doc62”
“2013-‐10-‐17” “cat food” “doc82”
“2013-‐10-‐17” “tea bags” “doc83”
“2013-‐10-‐22” “upgrade” “doc90”
view index
CBLQuery}
Views
• Map/Reduce mechanism Popular in other NoSQL databases
A view is similar to index in relaEonal database
• App-‐defined map funcFon Called on every document
Can emit arbitrary key/value pairs into the index
• OpFonal reduce funcFon Data aggregaEon / grouping
• FuncFons are registered as naFve blocks/callbacks Not stored as JavaScript in “design document” (as in CouchDB)
CreaFng a Database ViewRootViewController.m:101
// Define a view with a map function that indexes to-‐do items by creation date: [[theDatabase viewNamed: @"byDate"] setMapBlock: MAPBLOCK({ id date = doc[@"created_at"]; if (date) emit(date, doc); }) reduceBlock: nil version: @"1.1"];
Not a UIView — a database “view” is like an index.
Driving the Table from a View QueryRootViewController.m:69
// Create a query sorted by descending date, i.e. newest items first: CBLLiveQuery* query = [[[database viewNamed:@"byDate"] query] asLiveQuery]; query.descending = YES; ! // Plug the query into the CBLUITableSource, which will use it to drive the table. // (The CBLUITableSource uses KVO to observe the query's .rows property.) self.dataSource.query = query; self.dataSource.labelProperty = @"text";
@property(nonatomic, strong) IBOutlet UITableView *tableView; @property(nonatomic, strong) IBOutlet CBLUITableSource* dataSource;
RootViewController.h:41
Queries
• Basic feature set Key ranges, offset/limit, reverse, group by key…
No joins or fancy sorEng
but compound keys (and clever emits) allow for some tricks
• LiveQuery subclass Monitors database, pushes noEficaEons
Uses KVO on iOS, can drive a UITableView
• iOS goodies! Full-‐text indexing
Geo (bounding-‐box) queries
Live Queries & Table Data Sources
key value docID
“2013-‐09-‐30” “Pencil shavings”“doc62”
“2013-‐10-‐17” “Mangos” “doc82”
“2013-‐10-‐17” “second” “doc83”
view index CBLLiveQuery
CBLQuery}data source
CBLUI-‐TableSource
Driving the Table from a View QueryRootViewController.m:69
// Create a query sorted by descending date, i.e. newest items first: CBLLiveQuery* query = [[[database viewNamed:@"byDate"] query] asLiveQuery]; query.descending = YES; ! // Plug the query into the CBLUITableSource, which will use it to drive the table. // (The CBLUITableSource uses KVO to observe the query's .rows property.) self.dataSource.query = query; self.dataSource.labelProperty = @"text";
@property(nonatomic, strong) IBOutlet UITableView *tableView; @property(nonatomic, strong) IBOutlet CBLUITableSource* dataSource;
RootViewController.h:41
Combining CreaFng+Querying
// Returns a query for all the lists in a database. + (CBLQuery*) queryListsInDatabase: (CBLDatabase*)db { CBLView* view = [db viewNamed: @"lists"]; if (!view.mapBlock) { // Register the map function, the first time we access the view: [view setMapBlock: MAPBLOCK({ if ([doc[@"type"] isEqualToString:kListDocType]) emit(doc[@"title"], nil); }) reduceBlock: nil version: @"1"]; // bump version any time you change the MAPBLOCK body! } return [view createQuery]; }
Wiring Up The Table ViewRootViewController.xib
Displaying Table CellsRootViewController.m:131
-‐ (void)couchTableSource:(CBLUITableSource*)source willUseCell:(UITableViewCell*)cell forRow:(CBLQueryRow*)row { // Set the cell background and font: ……… // Configure the cell contents. Map function (above) copies the doc properties // into its value, so we can read them without having to load the document. NSDictionary* rowValue = row.value; BOOL checked = [rowValue[@"check"] boolValue]; if (checked) { cell.textLabel.textColor = [UIColor grayColor]; cell.imageView.image = [UIImage imageNamed:@"checked"]; } else { cell.textLabel.textColor = [UIColor blackColor]; cell.imageView.image = [UIImage imageNamed: @"unchecked"]; } // cell.textLabel.text is already set, thanks to setting up labelProperty }
Responding To TapsRootViewController.m:167
-‐ (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // Ask CBLUITableSource for the corresponding query row, and get its document: CBLQueryRow *row = [self.dataSource rowAtIndex:indexPath.row]; CBLDocument *doc = row.document; ! // Toggle the document's 'checked' property: NSMutableDictionary *docContent = [doc.properties mutableCopy]; BOOL wasChecked = [docContent[@"check"] boolValue]; docContent[@"check"] = @(!wasChecked); ! // Save changes: NSError* error; if (![doc.currentRevision putProperties: docContent error: &error]) { [self showErrorAlert: @"Failed to update item" forError: error]; } }
Adding New ItemsRootViewController.m:248
-‐(void)textFieldDidEndEditing:(UITextField *)textField { // Get the name of the item from the text field: NSString *text = addItemTextField.text; if (text.length == 0) { return; } addItemTextField.text = nil; ! // Create the new document's properties: NSDictionary *inDocument = @{ @"text": text, @"check": @NO, @"created_at": [CBLJSON JSONObjectWithDate: [NSDate date]] }; // Save the document: CBLDocument* doc = [database createDocument]; NSError* error; if (![doc putProperties: inDocument error: &error]) { [self showErrorAlert: @"Couldn't save new item" forError: error];
DeleFng ItemsRootViewController.m:189
-‐ (NSArray*)checkedDocuments { NSMutableArray* checked = [NSMutableArray array]; for (CBLQueryRow* row in self.dataSource.rows) { CBLDocument* doc = row.document; if ([doc[@"check"] boolValue]) [checked addObject: doc]; } return checked; } !!-‐ (void)deleteCheckedDocuments { NSError* error; if (![dataSource deleteDocuments: self.checkedDocuments error: &error]) { [self showErrorAlert: @"Failed to delete items" forError: error]; } }
OK, But Where’s The Sync?
CreaFng ReplicaFonsRootViewController.m:293
_pull = [self.database createPullReplication: newRemoteURL]; _push = [self.database createPushReplication: newRemoteURL]; _pull.continuous = _push.continuous = YES; // Observe replication progress changes, in both directions: NSNotificationCenter* nctr = [NSNotificationCenter defaultCenter]; [nctr addObserver: self selector: @selector(replicationProgress:) name: kCBLReplicationChangeNotification object: _pull]; [nctr addObserver: self selector: @selector(replicationProgress:) name: kCBLReplicationChangeNotification object: _push]; [_push start]; [_pull start];
ReplicaFon
Database “db”
ReplicaFonDir: push Remote: hlp://server/db Auth: <token>
ReplicaFonDir: pull Remote: hlp://server/db Auth: <token>
notifications
ReplicaFon
• Each ReplicaFon is one-‐direcFonal (push or pull)
• ReplicaFons can be one-‐shot or conFnuous One-‐shot: Stops when complete.
ConEnuous: Keeps monitoring changes Ell app quits
• Replicator runs in a background thread It detects online/offline, handles connecEon errors, retries…
You just see document-‐changed or query-‐changed noEficaEons.
• Progress is observable through KVO or NSNoFficaFon
Monitoring ReplicaFonsRootViewController.m:355
// Called in response to replication-‐change notifications. Updates the progress UI. -‐ (void) replicationProgress: (NSNotificationCenter*)n { if (_pull.status==kCBLReplicationActive || _push.status==kCBLReplicationActive) { // Sync is active -‐-‐ aggregate progress of both replications: unsigned completed = _pull.completedChangesCount + _push.completedChangesCount; unsigned total = _pull.changesCount + _push.changesCount; [self showSyncStatus]; // Update the progress bar, avoiding divide-‐by-‐zero exceptions: progress.progress = (completed / (float)MAX(total, 1u)); } else { // Sync is idle -‐-‐ hide the progress bar and show the config button: [self showSyncButton]; } ! // Check for any change in error status and display new errors: NSError* error = _pull.lastError ? _pull.lastError : _push.lastError; if (error != _syncError) { _syncError = error; if (error) [self showErrorAlert: @"Error syncing" forError: error];
Beyond Grocery Sync
Models
Task
List
Task
Task
@interface Task : CBLModel !@property NSString*title; @property NSDate* created; @property bool checked; @property List* list; !@end
@interface List : CBLModel !@property NSString* title; @property NSArray* members; !@end
Document “doc23”
Document “doc82”
Document “doc99”
Document “doc3”
Models
• Kind of like NSManagedObject, but simpler
• Map JSON to naFve @properFes Scalar types (int, bool…), String, Date, Data (blob)
References to other doc models
Arrays of the above
• ProperFes are KV-‐observable
• Models provide mutable state -‐save: writes to underlying document
• No query-‐based relaFon support (yet)
RepresenFng Document Types
• There are no tables to separate different record types! ConvenEon is to use a “type” property
• Map funcFons can pick out docs with the right type if (doc.type == “item”) emit(doc.created, doc.text);
To-‐Do List With ModelsFrom ToDoLite project
Task* task = [Task modelForDocument: row.document]; cell.textLabel.text = task.text; cell.textLabel.textColor = task.checked ? [UIColor grayColor] : [UIColor blackColor];
@interface Task : CBLModel !@property NSString*title; @property NSDate* created; @property bool checked; @property List* list; !@end
Querying With MulFple ListsFrom ToDoLite project
[view setMapBlock: MAPBLOCK({ if ([doc[@"type"] isEqualToString: kTaskDocType]) { id date = doc[@"created_at"]; NSString* listID = doc[@"list_id"]; emit(@[listID, date], doc); } }) reduceBlock: nil version: @"4"];
key docID
[“list1”, “2013-‐09-‐30”] “doc82”
[“list2”, “2013-‐06-‐02”] “doc62”
[“list2”, “2013-‐10-‐17”] “doc83”
[“list2”, “2013-‐10-‐28”] “doc90”
[“list3”, “2013-‐01-‐01”] “doc01”
Querying With MulFple ListsFrom ToDoLite project
CBLQuery* query = [view query]; query.descending = YES; NSString* myListId = self.document.documentID; query.startKey = @[myListId, @{}]; query.endKey = @[myListId];
key docID
[“list1”, “2013-‐09-‐30”] “doc82”
[“list2”, “2013-‐06-‐02”] “doc62”
[“list2”, “2013-‐10-‐17”] “doc83”
[“list2”, “2013-‐10-‐28”] “doc90”
[“list3”, “2013-‐01-‐01”] “doc01”
{
Whew!
hlp://developer.couchbase.com/mobile/develop/guides/couchbase-‐lite/index.html
hap://github.com/couchbaselabs/Grocery-‐Sync-‐iOS
haps://github.com/couchbaselabs/ToDoLite-‐iOS
Q & A