couchbase mobile 101 – couchbase live new york 2015
TRANSCRIPT
©2015 Couchbase Inc.
Couchbase Mobile 101:How to Build Your First Mobile App
William Hoang | Mobile Developer Advocate |
@sweetiewill
©2015 Couchbase Inc.
Introduction to Couchbase Lite
Demo: Installing and running a Sample App
Code: Tour of Couchbase Lite’s Architecture and API
Next Steps
Overview:
Getting Started with Couchbase Mobile
Couchbase Sync Gateway…will be introduced in Couchbase Mobile 102
Couchbase Peer to Peer…will be introduced in Couchbase Mobile 103
Couchbase Lite
©2015 Couchbase Inc.
Couchbase LiteEmbedded database library for mobile appsFlexible, schemaless data model (“NoSQL”)Querying by map/reduce viewsActive change notifications, for reactive UIsFull-strength multi-master replication
6
©2015 Couchbase Inc.
Couchbase Mobile ReplicationAvoid network latency for data accessOffline support (if you want it)Control over conflict resolutionArbitrary topologies, including P2POpen protocol
7
Running Your First App
©2015 Couchbase Inc.
Running Your First iOS AppInstall Xcode 6 from Mac App StoreDownload Couchbase LiteDownload Grocery Sync sample codeCopy framework into sample app folderBuild & Run the Xcode project
9
©2015 Couchbase Inc.
2. Download Couchbase Litecouchbase.com/nosql-databases/downloads
Click “Couchbase Mobile”
10
©2015 Couchbase Inc.
3. Download Grocery Sync Source Codegithub.com/couchbaselabs/Grocery-Sync-iOS
11
©2015 Couchbase Inc.
4. Plug In The Framework
12
©2015 Couchbase Inc.
5. Open The Project
13
©2015 Couchbase Inc.
Running Your First Android AppInstall Android Studio with Android SDK 19 &
Build Tool 19Check out Grocery Sync sample code repoImport Grocery Sync gradle file into Android
StudioPress Debug or Run button
14
©2015 Couchbase Inc.
2. Download Grocery Sync Source Code
15
©2015 Couchbase Inc.
3. Import ‘build.gradle’ into Android Studio
16
Quick Demo
A Tour Of The CodeAnd The API
1. Initialization & Insertion
©2015 Couchbase Inc.
InitializationDemoAppDelegate.m:61
// 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]; return NO; }
20
©2015 Couchbase Inc.
InitializationMainActivity.java:118
manager = new Manager(new AndroidContext(this), Manager.DEFAULT_OPTIONS);
database = manager.getDatabase(DATABASE_NAME);
21
©2015 Couchbase Inc.
ManagerTop-level object, usually a singletonA collection of named databasesLocates databases in filesystem
22
©2015 Couchbase Inc.
Databases And Documents
Database
“to-do”
“doc1”
“doc3”
Document “doc2”{ “text”: “Vacuum kitchen”, “created”: “2015-09–06”, “check”: false, “tags”:[“William”,“chore”]}
thumb.jpg
23
“doc2”
“to-do”
©2015 Couchbase Inc.
DatabaseNamespace for documentsContains views and their indexesContains validation functionsSource and target of replication
24
©2015 Couchbase Inc.
Document“_id” is unique within database, immutableJSON allows nested data structures (arrays,
maps)Schemaless — Documents needn’t have the
same structureDifferent types of docs can coexist in a
database“type” property is a common convention
Documents are versionedOptimistic concurrency control (MVCC)
25
©2015 Couchbase Inc.
Adding New ItemsRootViewController.m:243
// Create the new document's properties: NSDictionary *document = @{@"text": text, @"check": @NO, @"created_at": [CBLJSON JSONObjectWithDate: [NSDate date]]}; // Save the document: CBLDocument* doc = [database createDocument]; NSError* error; if (![doc putProperties: document error: &error]) { [self showErrorAlert: @"Couldn't save new item" forError: error]; }
26
©2015 Couchbase Inc.
Adding New ItemsMainActivity.java:343
SimpleDateFormat dateFormatter = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); UUID uuid = UUID.randomUUID(); Calendar calendar = GregorianCalendar.getInstance(); long currentTime = calendar.getTimeInMillis(); String currentTimeString = dateFormatter.format(calendar.getTime()); String id = currentTime + "-" + uuid.toString();
Document document = database.createDocument(); Map<String, Object> properties = new HashMap<String, Object>(); properties.put("_id", id); properties.put("text", text); properties.put("check", Boolean.FALSE); properties.put("created_at", currentTimeString);
document.putProperties(properties); 27
2. Views & Queries
©2015 Couchbase Inc.
Creating 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, nil); }) version: @"1.1"];
Not a UIView —a database “view”is like an index.
29
©2015 Couchbase Inc.
Map/Reduce ViewsConcept from functional programming, via
GoogleApp-defined map function operates on
documentsdoc ⟼ { (key, value) …}
Its output generates an index ordered by keyIndex rows can be aggregated by a reduce
functionIndex is queried by key or key range
30
©2015 Couchbase Inc.
Map/Reduce Indexing
emit(doc.created, doc.title)
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”
index
all documents
map function
View“byDate”
31
©2015 Couchbase Inc.
Creating 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, nil); }) version: @"1.1"];
Not a UIView —a database “view”is like an index.
32
©2015 Couchbase Inc.
Creating A Database ViewMainActivity.java:122
com.couchbase.lite.View viewItemsByDate = database.getView(String.format("%s/%s", designDocName, byDateViewName)); viewItemsByDate.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { Object createdAt = document.get("created_at"); if (createdAt != null) { emitter.emit(createdAt.toString(), null); } } }, "1.0");
Not a UIView —a database “view”is like an index.
33
©2015 Couchbase Inc.
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 uses 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
34
©2015 Couchbase Inc.
Driving the Table from a View QueryRootViewController.m:101
liveQuery = view.createQuery().toLiveQuery();
liveQuery.addChangeListener(new LiveQuery.ChangeListener() { public void changed(final LiveQuery.ChangeEvent event) { runOnUiThread(new Runnable() { public void run() { grocerySyncArrayAdapter.clear(); for (Iterator<QueryRow> it = event.getRows(); it.hasNext();) { grocerySyncArrayAdapter.add(it.next()); } grocerySyncArrayAdapter.notifyDataSetChanged();
35
©2015 Couchbase Inc.
LiveQuery
Querying a View
36
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”
index
} Query QueryRowQueryRowQueryRow
.limit = 3
.offset = …
.reverse = …
.startKey = …
.endKey = …
©2015 Couchbase Inc.
LiveQuery Driving An iOS UITableView
37
LiveQueryQuery
CBLUITable-Source
UITableViewYour
Controller
data source
delegate
©2015 Couchbase Inc.
LiveQuery Driving An iOS UITableView
38
©2015 Couchbase Inc.
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} 39
Called by the CBLUITableSource when it’s told to display a cell.
©2015 Couchbase Inc.
Displaying Table CellsGrocerySyncArrayAdapter.java:33
public View getView(int position, View itemView, ViewGroup parent) {//...TextView label = ((ViewHolder)itemView.getTag()).label;QueryRow row = getItem(position);SavedRevision currentRevision = row.getDocument().getCurrentRevision();// Check boxObject check = (Object) currentRevision.getProperty("check");boolean isGroceryItemChecked = false;if (check != null && check instanceof Boolean) isGroceryItemChecked = ((Boolean)check).booleanValue();// TextString groceryItemText = (String) currentRevision.getProperty("text");label.setText(groceryItemText);
40
©2015 Couchbase Inc.
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]; }}
41
Called by the UITableView itself
©2015 Couchbase Inc.
Responding To TapsMainActivity.java:243
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) { QueryRow row = (QueryRow) adapterView.getItemAtPosition(position); Document document = row.getDocument(); Map<String, Object> newProperties = new HashMap<String, Object>(document.getProperties());
boolean checked = ((Boolean) newProperties.get("check")).booleanValue(); newProperties.put("check", !checked);
try { document.putProperties(newProperties); grocerySyncArrayAdapter.notifyDataSetChanged(); } catch (Exception e) { // ... 42
©2015 Couchbase Inc.
Change NotificationsThree types:DatabaseChanged: Any document updatedDocumentChanged: A specific document
updatedLiveQuery: Change in query result set
Enables reactive programming
Controller Model(Database)GUI View
User event Update doc
DocumentChanged
Redraw
43
Replication(pull)
3. Replication
©2015 Couchbase Inc.
Replication
46
Replication
(push)
Replication
(pull)https://exam.pl/dblocal_db
©2015 Couchbase Inc.
ReplicationReplication is asynchronousUse database notifications to detect changes
A Replication can be one-shot or continuousOne-shot runs only until it “catches up”Continuous keeps going till stopped or app
quitsContinuous handles online/offline transitions
gracefullyReplications can be filtered
47
©2015 Couchbase Inc.
Creating ReplicationsDemoAppDelegate.m:74
_pull = [database createPullReplication: serverDbURL]; _push = [database createPushReplication: serverDbURL]; _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];
48
©2015 Couchbase Inc.
Creating ReplicationsMainActivity.java:150
Replication pullReplication = database.createPullReplication(syncUrl); pullReplication.setContinuous(true);
Replication pushReplication = database.createPushReplication(syncUrl); pushReplication.setContinuous(true);
pullReplication.start(); pushReplication.start();
pullReplication.addChangeListener(this); pushReplication.addChangeListener(this);
49
©2015 Couchbase Inc.
Monitoring ReplicationsDemoAppDelegate.m:98- (void) replicationProgress: (NSNotificationCenter*)n { if (_pull.status == kCBLReplicationActive || _push.status == kCBLReplicationActive) { // Sync is active -- aggregate the progress of both replications and compute a fraction: unsigned completed = _pull.completedChangesCount + _push.completedChangesCount; unsigned total = _pull.changesCount+ _push.changesCount; NSLog(@"SYNC progress: %u / %u", completed, total); // Update the progress bar, avoiding divide-by-zero exceptions: [self.rootViewController showSyncStatus: (completed / (float)MAX(total, 1u))]; } else { // Sync is idle -- hide the progress bar and show the config button: NSLog(@"SYNC idle"); [self.rootViewController hideSyncStatus]; }
// 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 showAlert: @"Error syncing" error: error fatal: NO]; } }}
50
©2015 Couchbase Inc.
Monitoring ReplicationsMainActivity.java:381 public void changed(Replication.ChangeEvent event) { Replication replication = event.getSource(); Log.d(TAG, "Replication : " + replication + " changed."); if (!replication.isRunning()) { String msg = String.format("Replicator %s not running", replication); Log.d(TAG, msg); } else { int processed = replication.getCompletedChangesCount(); int total = replication.getChangesCount(); String msg = String.format("Replicator processed %d / %d", processed, total); Log.d(TAG, msg); } if (event.getError() != null) { showError("Sync error", event.getError()); } } 51
Next Steps
©2015 Couchbase Inc.
Getting Started: Next StepsTry connecting Grocery Sync to your own Sync
Gateway
Couchbase Mobile Blog:Blog.couchbase.com
Visit our mobile developer forums
53