Upload
couchbase
View
1.788
Download
4
Embed Size (px)
DESCRIPTION
Learn how to develop with Couchbase Lite for iOS in this technical lecture led by Jens Alfke, lead Couchbase Lite iOS developer. Walkthrough developing your first iOS application using Native APIs, and hear about what is to come for Couchbase Lite iOS. In this webinar you will see: An overview of the Couchbase Lite iOS API A step-by-step demonstration on how to develop a iOS application with Couchbase Lite A quick demo of a iOS app An explanation of the future plans for Couchbase Lite iOS
Citation preview
“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/communi:es/couchbase-‐lite
Download Grocery Syncgithub.com/couchbaselabs/Grocery-‐Sync-‐iOS
Plug In The Framework
option
Live Demo
A Tour of the Code and the API
Ini;aliza;onDemoAppDelegate.m:64
// Initialize Couchbase Lite and find/create my database: NSError* error; self.database = [[CBLManager sharedInstance] createDatabaseNamed: 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
• Collec;on of named databases
• Generally a singleton Unless you run on mul:ple threads
• Manages database storage (local directory)
Database
• Namespace for documents
• Contains views and their indexes
• Contains valida;on func;ons
• Source and target of replica;on
Document
• Has unique ID within its database
• Contains arbitrary* JSON object *except keys that start with “_” are reserved
There is no explicit, enforced schema
Denormaliza:on is OK — use arrays or dic:onaries
• May contain binary aVachments Data blobs, can be large, tagged with MIME type
• Versioned Mul:-‐Version Concurrency Control (MVCC)
Every update creates a revision ID (based on digest of contents)
Revision ID history is stored
Ini;aliza;onDemoAppDelegate.m:64
// Initialize Couchbase Lite and find/create my database: NSError* error; self.database = [[CBLManager sharedInstance] createDatabaseNamed: kDatabaseName error: &error]; if (!self.database) [self showAlert: @"Couldn't open database" error: error fatal: YES];
Crea;ng 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 rela:onal database.
• App-‐defined map func;on Called on every document
Can emit arbitrary key/value pairs into the index
• Op;onal reduce func;on Data aggrega:on / grouping
• Func;ons are registered as na;ve blocks/callbacks Not stored as JavaScript in “design document” (as in CouchDB)
Crea;ng 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:101
// 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 sor:ng
but compound keys (and clever emits) allow for some tricks
• LiveQuery subclass Monitors database, pushes no:fica:ons
Uses KVO on iOS, can drive a UITableView
• Upcoming 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:101
// 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
Wiring Up The Table ViewRootViewController.xib
Displaying Table CellsDemoAppDelegate.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 TapsDemoAppDelegate.m:131
-‐ (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 ItemsDemoAppDelegate.m:247
-‐(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 untitledDocument]; NSError* error; if (![doc putProperties: inDocument error: &error]) { [self showErrorAlert: @"Couldn't save new item" forError: error];
Dele;ng ItemsDemoAppDelegate.m:191
-‐ (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?
Crea;ng Replica;onsDemoAppDelegate.m:291
// Tell the database to use this URL for bidirectional sync. // This call returns an array of the pull and push replication objects: NSArray* repls = [self.database replicateWithURL: newRemoteURL exclusively: YES]; if (repls) { _pull = repls[0]; _push = repls[1]; _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]; }
Replica;on
Database “db”
Replica;onDir: push Remote: hkp://server/db Auth: <token>
Replica;onDir: pull Remote: hkp://server/db Auth: <token>
notifications
Replica;on
• Each Replica;on is one-‐direc;onal (push or pull)
• Replica;ons can be one-‐shot, con;nuous or persistent One-‐shot: Stops when complete.
Con:nuous: Keeps monitoring changes :ll app quits
Persistent: Automa:cally starts again on next relaunch
• Replicator runs in a background thread It detects online/offline, handles connec:on errors, retries…
You just see document-‐changed or query-‐changed no:fica:ons.
• Progress is observable through KVO or NSNo;fica;on
Monitoring Replica;onsDemoAppDelegate.m:353
// Called in response to replication-‐change notifications. Updates the progress UI. -‐ (void) replicationProgress: (NSNotificationCenter*)n { if (_pull.mode== kCBLReplicationActive || _push.mode== kCBLReplicationActive) { // Sync is active -‐-‐ aggregate progress of both replications: unsigned completed = _pull.completed + _push.completed; unsigned total = _pull.total + _push.total; [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.error ? _pull.error : _push.error; 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 na;ve @proper;es Scalar types (int, bool…), String, Date, Data (blob)
References to other doc models
Arrays of the above
• Proper;es are KV-‐observable
• Models provide mutable state -‐save: writes to underlying document
• No query-‐based rela;on support (yet)
Represen;ng Document Types
• There are no tables to separate different record types! Conven:on is to use a “type” property
• Map func;ons 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 Mul;ple 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 Mul;ple 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!
hkp://couchbase.github.io/couchbase-‐lite-‐ios/docs/html/annotated.html
hkp://docs.couchbase.com/couchbase-‐lite/cbl-‐ios/
hkps://github.com/couchbaselabs/Grocery-‐Sync-‐iOS
Coming Up Next!
Tuesday, November 5, 10:00AM PST
“Developing With Android”