Couchbase Tutorial for iOS: Getting Started
In this Couchbase tutorial for iOS, you’ll learn how to use Couchbase and Sync Gateway to persist data across apps while making Ray’s Pizzeria App. By .
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Couchbase Tutorial for iOS: Getting Started
25 mins
Introduction
Couchbase is a NoSQL data platform that extends from servers down to mobile devices. It’s a document-based data platform in use worldwide by a large variety of companies and organizations.
Couchbase Mobile is the portion of their platform built for mobile devices. It provides:
-
Couchbase Lite 2.0, an embedded database, which incorporates:
- N1QL, (“nickel”, not “n-one-q-l”) a new Query API
- Full-text search
- Automatic conflict resolution
- Database replication using the WebSocket protocol
- Sync Gateway, a secure web gateway for connecting to Couchbase Server.
In this Couchbase tutorial, you’ll develop an app to run Ray’s new pizzeria. The app will allow customers to place orders and will also let Chef Ray monitor pizza production and update his fabulous menu.
By the end of this tutorial, you’ll know how to:
- Use a prebuilt Couchbase Lite database
- Model and query data
- Sync data using Sync Gateway
- Simulate a multi-user environment using multiple simulators
Getting Started
Use the Download Materials button at the top or bottom of this tutorial to download the starter project.
Note: Because this project uses Couchbase and Fakery CocoaPods, you need to open RaysPizzeria.xcworkspace, not RaysPizzeria.xcproject.
Note: Because this project uses Couchbase and Fakery CocoaPods, you need to open RaysPizzeria.xcworkspace, not RaysPizzeria.xcproject.
Build and run the project in Xcode to see the basic UI.
The UI has two tabs: Menu and Kitchen, but not much functionality.
Select a pizza from the Menu tab and you’ll see an empty customer screen. Tap + to generate a customer, which then gets added to the UITableView
. When you select the customer cell, a UIAlertController
pops up asking you to confirm the order.
Note: The customer data is supplied using Fakery, a framework intended for unit testing.
Note: The customer data is supplied using Fakery, a framework intended for unit testing.
Go back to the menu and select another pizza. Notice that your previous order and customer are no longer there. Oh no!
Now look at the Kitchen tab. Ray and his kitchen staff have no idea what pizza to make or who placed the order. To solve this problem, Ray asks you to implement a database-driven app.
With a database-driven app, you can:
- Simplify order entry
- Track order processing
- Easily modify the menu
So what are you waiting for? It’s time to get cookin’.
Using a Pre-loaded Database
Open MenuViewController.swift and take a look around. It’s a basic UIViewController
with a UITableView
that displays the menu. In viewWillAppear(_:)
, the menu is loaded with menu = DataManager.shared.getMenu()
.
Open DataManager.swift and locate getMenu()
. This loops thru the Pizza.PizzaType
enum, creates a Pizza
object and then adds it to the menu. This is fine until you need to change the menu items and their prices. To solve this problem, you’ll load the menu from a database instead.
Near the top of the file, replace:
import Foundation
With:
import CouchbaseLiteSwift
Add the following to the properties section, just under static var shared = DataManager()
:
private let database: Database
private let databaseName = "pizza-db"
To load a pre-built menu database, add the following to init()
:
// 1
if let path = Bundle.main.path(forResource: databaseName, ofType: "cblite2"),
!Database.exists(withName: databaseName) {
do {
try Database.copy(fromPath: path, toDatabase: databaseName, withConfig: nil)
} catch {
fatalError("Could not load pre-built database")
}
}
// 2
do {
database = try Database(name: databaseName)
} catch {
fatalError("Error opening database")
}
Here’s the code breakdown:
- Get the path to the pre-loaded database from the app bundle, a read-only location, and copy it to a read-write location.
Database.copy(fromPath:toDatabase:withConfig:)
copies it to the app’s Application Support directory. - Open the pre-loaded database.
Now, replace the contents of getMenu()
with:
var menu: [Pizza] = []
// 1
let query = QueryBuilder
.select(SelectResult.all(), SelectResult.expression(Meta.id))
.from(DataSource.database(database))
.where(Expression.property("type")
.equalTo(Expression.string(DocTypes.pizza.rawValue)))
.orderBy(Ordering.property(PizzaKeys.price.rawValue))
// 2
do {
for result in try query.execute() {
guard let dict = result.dictionary(forKey: databaseName),
let name = dict.string(forKey: PizzaKeys.name.rawValue),
let id = result.string(forKey: PizzaKeys.id.rawValue) else {
continue
}
let pizza = Pizza(id: id, name: name,
price: dict.double(forKey: PizzaKeys.price.rawValue))
menu.append(pizza)
}
} catch {
fatalError("Error running the query")
}
return menu
Here’s what this does:
- Build a query that retrieves all pizza documents from the database and orders them by price.
SelectResult.all()
returns all property data from a pizza document. The document id (used in future queries) is not part of the property data, but rather is part of the document metadata, so you useSelectResult.expression(Meta.id)
to retrieve it. - Execute the query and loop thru the results. Get the
name
,price
andid
from the database and create aPizza
object. Append eachPizza
object tomenu
and returnmenu
.
Build and run.
Perfect! The menu loads from the database, but customers and their orders are not getting saved. You’ll fix that next.
Storing Customers and Orders
Still in DataManager.swift, add the following to the extension for Customer
data:
func getCustomers() -> [Customer] {
var customers: [Customer] = []
// 1
let query = QueryBuilder
.select(SelectResult.all(), SelectResult.expression(Meta.id))
.from(DataSource.database(database))
.where(Expression.property("type")
.equalTo(Expression.string(DocTypes.customer.rawValue)))
// 2
do {
for result in try query.execute() {
guard let dict = result.dictionary(forKey: databaseName),
let name = dict.string(forKey: CustomerKeys.name.rawValue),
let street = dict.string(forKey: CustomerKeys.street.rawValue),
let city = dict.string(forKey: CustomerKeys.city.rawValue),
let state = dict.string(forKey: CustomerKeys.state.rawValue),
let postalCode = dict.string(forKey: CustomerKeys.postalCode.rawValue),
let phoneNumber = dict.string(forKey: CustomerKeys.phoneNumber.rawValue),
let id = result.string(forKey: CustomerKeys.id.rawValue) else {
continue
}
// 3
let customer = Customer(
id: id,
name: name,
street: street,
city: city,
state: state,
postalCode: postalCode,
phoneNumber: phoneNumber)
customers.append(customer)
}
} catch {
fatalError("Error running the query")
}
return customers
}
Here’s what’s happening:
- Similar to
getMenu()
, you build a query to get all the customer data from the database. - Execute the query and get the data values from the result dictionary.
- Create a
Customer
object and add it tocustomers
.
Add the following to same section:
func add(customer: Customer) {
// 1
let mutableDoc = MutableDocument()
.setString(customer.name, forKey: CustomerKeys.name.rawValue)
.setString(customer.street, forKey: CustomerKeys.street.rawValue)
.setString(customer.city, forKey: CustomerKeys.city.rawValue)
.setString(customer.state, forKey: CustomerKeys.state.rawValue)
.setString(customer.postalCode, forKey: CustomerKeys.postalCode.rawValue)
.setString(customer.phoneNumber, forKey: CustomerKeys.phoneNumber.rawValue)
.setString(DocTypes.customer.rawValue, forKey: "type")
do {
// 2
try database.saveDocument(mutableDoc)
} catch {
fatalError("Error saving document")
}
}
Here’s what this does:
- Create a
MutableDocument
and store theCustomer
properties in it. - Save the document to the database, creating the document’s metadata:
id
.
Open CustomerViewController.swift, and in loadData(_:)
, add:
customers = DataManager.shared.getCustomers()
In addCustomer()
, replace:
customers?.append(customer)
With:
DataManager.shared.add(customer: customer)
customers = DataManager.shared.getCustomers()
Build and run.
Select a pizza and add a few customers. Then, go back to the Menu tab, and select the pizza again; notice your previously added customers are now there.