You'll start by installing Android Studio with the Kotlin plugins in order to create and run your first Android app. You'll then go on to learn some of the basics of views and layouts in Android, how to use Android Intents, and how to use some of Kotlin's special features to write safer, more concise code. By the end of this course, you'll be able to use Android and Kotlin to build a basic app with cleaner code.
You can take our new course straight away with a subscription to Envato Elements. For a single low monthly fee, you get access not only to this course, but also to our growing library of over 1,000 video courses and industry-leading eBooks on Envato Tuts+.
Plus you now get unlimited downloads from the huge Envato Elements library of 550,000+ creative assets. Create with unique fonts, photos, graphics and templates, and deliver better projects faster.
You'll start by installing Android Studio with the Kotlin plugins in order to create and run your first Android app. You'll then go on to learn some of the basics of views and layouts in Android, how to use Android Intents, and how to use some of Kotlin's special features to write safer, more concise code. By the end of this course, you'll be able to use Android and Kotlin to build a basic app with cleaner code.
You can take our new course straight away with a subscription to Envato Elements. For a single low monthly fee, you get access not only to this course, but also to our growing library of over 1,000 video courses and industry-leading eBooks on Envato Tuts+.
Plus you now get unlimited downloads from the huge Envato Elements library of 550,000+ creative assets. Create with unique fonts, photos, graphics and templates, and deliver better projects faster.
In the previous tutorial of this series, we added the ability to add, update, and remove shopping lists. A shopping list without any items in it isn't very useful, though. In this tutorial, we'll add the ability to add, update, and remove items from a shopping list. This means that we'll be working with references and the CKReference class.
We'll also take a closer look at the data model of the shopping list application. How easy is it to make changes to the data model, and how does the application respond to changes we make in the CloudKit Dashboard?
Prerequisites
Remember that I will be using Xcode 7 and Swift 2. If you are using an older version of Xcode, then keep in mind that you are using a different version of the Swift programming language.
In this tutorial, we will continue where we left off in the second tutorial of this series. You can download or clone the project from GitHub.
1. Shopping List Details
Currently, the user can modify the name of a shopping list by tapping the detail disclosure indicator, but the user should also be able to see the contents of a shopping list by tapping one in the lists view controller. To make this work, we first need a new UIViewController subclass.
Step 1: Creating ListViewController
The ListViewController class will display the contents of a shopping list in a table view. The interface of the ListViewController class looks similar to that of the ListsViewController class. We import the CloudKit and SVProgressHUD frameworks and conform the class to the UITableViewDataSource and UITableViewDelegate protocols. Because we'll be using a table view, we declare a constant, ItemCell, that will serve as a cell reuse identifier.
import UIKit
import CloudKit
import SVProgressHUD
class ListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
static let ItemCell = "ItemCell"
@IBOutlet weak var messageLabel: UILabel!
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
var list: CKRecord!
var items = [CKRecord]()
var selection: Int?
...
}
We declare three outlets: messageLabel of type UILabel!, tableView of type UITableView!, and activityIndicatorView of type UIActivityIndicatorView!. The list view controller keeps a reference to the shopping list it is displaying in the list property, which is of type CKRecord!. The items in the shopping list are stored in the items property, which is of type [CKRecord]. Finally, we use a helper variable, selection, to keep track of which item in the shopping list the user has selected. This will become clear later in this tutorial.
Step 2: Creating the User Interface
Open Main.storyboard, add a view controller, and set its class to ListViewController in the Identity Inspector. Select the prototype cell of the lists view controller, press the Control key, and drag from the prototype cell to the list view controller. Choose Show from the menu that pops up, and set the identifier to List in the Attributes Inspector.
Add a table view, a label, and an activity indicator view to the view controller's view. Don't forget to wire up the outlets of the view controller as well as those of the table view.
Select the table view and set Prototype Cells to 1 in the Attributes Inspector. Select the prototype cell, and set Style to Right Detail, Identifier to ItemCell, and Accessory to Disclosure Indicator. This is what the view controller should look like when you're finished.
Step 3: Configuring the View Controller
Before we revisit the CloudKit framework, we need to prepare the view controller for the data it's going to receive. Start by updating the implementation of viewDidLoad. We set the view controller's title to the name of the shopping list and invoke two helper methods, setupView and fetchItems.
// MARK: -
// MARK: View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
// Set Title
title = list.objectForKey("name") as? String
setupView()
fetchItems()
}
The setupView method is identical to the one we implemented in the ListsViewController class.
While we're at it, let's also implement another familiar helper method, updateView. In updateView, we update the user interface of the view controller based on the items stored in the items property.
We're almost ready to take the application for another test run. Before we do, we need to implement the UITableViewDataSource protocol. If you've read the previous installments of this series, then the implementation will look familiar.
To tie everything together, we need to revisit the ListsViewController class. Start by implementing the tableView(_:didSelectRowAtIndexPath:) method of the UITableViewDelegate protocol.
We also need to update prepareForSegue(segue:sender:) to handle the segue we created a few moments ago. This means that we need to add a new case to the switch statement.
// MARK: -
// MARK: Segue Life Cycle
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let identifier = segue.identifier else { return }
switch identifier {
case SegueList:
// Fetch Destination View Controller
let listViewController = segue.destinationViewController as! ListViewController
// Fetch Selection
let list = lists[tableView.indexPathForSelectedRow!.row]
// Configure View Controller
listViewController.list = list
case SegueListDetail:
...
default:
break
}
}
To satisfy the compiler, we also need to declare the SegueList constant at the top of ListsViewController.swift.
import UIKit
import CloudKit
import SVProgressHUD
let RecordTypeLists = "Lists"
let SegueList = "List"
let SegueListDetail = "ListDetail"
class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddListViewControllerDelegate {
...
}
Build and run the application to see if everything is wired up correctly. Because we haven't implemented the fetchItems method yet, no items will be displayed. That's something we need to fix.
2. Fetching Items
Step 1: Create a Record Type
Before we can fetch items from the CloudKit backend, we need to create a new record type in the CloudKit Dashboard. Navigate to the CloudKit Dashboard, create a new record type, and name it Items. Each item should have a name, so create a new field, set the field name to name, and set the field type to String.
Each item should also know to which shopping list it belongs. That means each item needs a reference to its shopping list. Create a new field, set the field name to list, and set the field type to Reference. The Reference field type was designed for this exact purpose, managing relationships.
Head back to Xcode, open ListsViewController.swift, and declare a new constant at the top for the Items record type.
import UIKit
import CloudKit
import SVProgressHUD
let RecordTypeLists = "Lists"
let RecordTypeItems = "Items"
let SegueList = "List"
let SegueListDetail = "ListDetail"
class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddListViewControllerDelegate {
...
}
Step 2: Fetching Items
Open ListViewController.swift and navigate to the fetchItems method. The implementation is similar to the fetchLists method of the ListsViewController class. There is an important difference, though.
private func fetchItems() {
// Fetch Private Database
let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
// Initialize Query
let reference = CKReference(recordID: list.recordID, action: .DeleteSelf)
let query = CKQuery(recordType: RecordTypeItems, predicate: NSPredicate(format: "list == %@", reference))
// Configure Query
query.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
// Perform Query
privateDatabase.performQuery(query, inZoneWithID: nil) { (records, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// Process Response on Main Thread
self.processResponseForQuery(records, error: error)
})
}
}
The difference between fetchItems and fetchLists is the predicate we pass to the CKQuery initializer. We're not interested in every item in the user's private database. We're only interested in the items that are associated with a particular shopping list. This is reflected in the predicate of the CKQuery instance.
We create the predicate by passing in a CKReference instance, which we create by invoking init(recordID:action:). This method accepts two arguments: a CKRecordID instance that references the shopping list record and a CKReferenceAction instance that determines what happens when the shopping list is deleted.
Reference actions are very similar to delete rules in Core Data. If the referenced object (the shopping list in this example) is deleted, then the CloudKit framework inspects the reference action to determine what should happen to the records that hold a reference to the deleted record. The CKReferenceAction enum has two member values:
None: If the reference is deleted, nothing happens to the records referencing the deleted record.
DeleteSelf: If the reference is deleted, every record referencing the deleted record is also deleted.
Because no item should exist without a shopping list, we set the reference action to DeleteSelf.
The processResponseForQuery(records:error:) method contains nothing new. We process the response of the query and update the user interface accordingly.
private func processResponseForQuery(records: [CKRecord]?, error: NSError?) {
var message = ""
if let error = error {
print(error)
message = "Error Fetching Items for List"
} else if let records = records {
items = records
if items.count == 0 {
message = "No Items Found"
}
} else {
message = "No Items Found"
}
if message.isEmpty {
tableView.reloadData()
} else {
messageLabel.text = message
}
updateView()
}
Build and run the application. You won't see any items yet, but the user interface should update to reflect that the shopping list is empty.
3. Adding Items
Step 1: Creating AddItemViewController
It's time to implement the ability to add items to a shopping list. Start by creating a new UIViewController subclass, AddItemViewController. The interface of the view controller is similar to that of the AddListViewController class.
At the top, we import the CloudKit and SVProgressHUD frameworks. We declare the AddItemViewControllerDelegate protocol, which will serve the same purpose as the AddListViewControllerDelegate protocol. The protocol defines two methods, one for adding items and one for updating items.
import UIKit
import CloudKit
import SVProgressHUD
protocol AddItemViewControllerDelegate {
func controller(controller: AddItemViewController, didAddItem item: CKRecord)
func controller(controller: AddItemViewController, didUpdateItem item: CKRecord)
}
class AddItemViewController: UIViewController {
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var saveButton: UIBarButtonItem!
var delegate: AddItemViewControllerDelegate?
var newItem: Bool = true
var list: CKRecord!
var item: CKRecord?
...
}
We declare two outlets, a text field and a bar button item. We also declare a property for the delegate and a helper variable, newItem, that helps us determine whether we're creating a new item or updating an existing item. Finally, we declare a property list for referencing the shopping list to which the item will be added and a property item for the item we're creating or updating.
Before we create the user interface, let's implement two actions that we'll need in the storyboard, cancel(_:) and save(_:). We'll update the implementation of the save(_:) action later in this tutorial.
Open Main.storyboard, add a bar button item to the navigation bar of the list view controller, and set System Item to Add in the Attributes Inspector. Drag a view controller from the Object Library and set its class to AddItemViewController. Create a segue from the bar button item we just created to the add item view controller. Choose Show from the menu that pops up, and set the segue's identifier to ItemDetail.
Add two bar button items to the navigation bar of the add item view controller, a cancel button on the left and a save button on the right. Connect each bar button item to its corresponding action. Add a text field to the view controller's view and don't forget to connect the outlets of the view controller. This is what the add item view controller should look like when you're finished.
Step 3: Configuring the View Controller
The implementation of the add item view controller contains nothing that we haven't covered yet. There's one exception, though, which we'll discuss in a moment. Let's start by configuring the view controller in viewDidLoad.
We invoke setupView, a helper method, and update the value of newItem. If the item property is equal to nil, newItem is equal to true. This helps us determine whether we're creating or updating a shopping list item.
We also add the view controller as an observer for notifications of type UITextFieldTextDidChangeNotification. This means that the view controller is notified when the contents of the nameTextField change.
The setupView method invokes two helper methods, updateNameTextField and updateSaveButton. The implementation of these helper methods is straightforward. In updateNameTextField, we populate the text field. In updateSaveButton, we enable or disable the save button based on the contents of the text field.
// MARK: -
// MARK: View Methods
private func setupView() {
updateNameTextField()
updateSaveButton()
}
// MARK: -
private func updateNameTextField() {
if let name = item?.objectForKey("name") as? String {
nameTextField.text = name
}
}
// MARK: -
private func updateSaveButton() {
let text = nameTextField.text
if let name = text {
saveButton.enabled = !name.isEmpty
} else {
saveButton.enabled = false
}
}
Before we take a look at the updated implementation of the save(_:) method, we need to implement the textFieldDidChange(_:) method. All we do is invoke updateSaveButton to enable or disable the save button.
The save(_:) method is the most interesting method of the AddItemViewController class, because it shows us how to work with CloudKit references. Take a look at the implementation of the save(_:) method below.
Most of its implementation should look familiar since we covered saving records in the AddListViewController class. What interests us most is how the item keeps a reference to its shopping list. We first create a CKReference instance by invoking the designated initializer, init(recordID:action:). We covered the details of creating a CKReference instance a few minutes ago when we created the query for fetching shopping list items.
Telling the item about this reference is easy. We invoke setObjec(_:forKey:) on the item property, passing in the CKReference instance as the value and "list" as the key. The key corresponds to the field name we assigned in the CloudKit Dashboard. Saving the item to iCloud is identical to what we've covered before. That's how easy it is to work with CloudKit references.
@IBAction func save(sender: AnyObject) {
// Helpers
let name = nameTextField.text
// Fetch Private Database
let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
if item == nil {
// Create Record
item = CKRecord(recordType: RecordTypeItems)
// Initialize Reference
let listReference = CKReference(recordID: list.recordID, action: .DeleteSelf)
// Configure Record
item?.setObject(listReference, forKey: "list")
}
// Configure Record
item?.setObject(name, forKey: "name")
// Show Progress HUD
SVProgressHUD.show()
// Save Record
privateDatabase.saveRecord(item!) { (record, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// Dismiss Progress HUD
SVProgressHUD.dismiss()
// Process Response
self.processResponse(record, error: error)
})
}
}
The implementation of processResponse(record:error:) contains nothing new. We check to see if any errors popped up and, if no errors were thrown, we notify the delegate.
// MARK: -
// MARK: Helper Methods
private func processResponse(record: CKRecord?, error: NSError?) {
var message = ""
if let error = error {
print(error)
message = "We were not able to save your item."
} else if record == nil {
message = "We were not able to save your item."
}
if !message.isEmpty {
// Initialize Alert Controller
let alertController = UIAlertController(title: "Error", message: message, preferredStyle: .Alert)
// Present Alert Controller
presentViewController(alertController, animated: true, completion: nil)
} else {
// Notify Delegate
if newItem {
delegate?.controller(self, didAddItem: item!)
} else {
delegate?.controller(self, didUpdateItem: item!)
}
// Pop View Controller
navigationController?.popViewControllerAnimated(true)
}
}
Step 5: Updating ListViewController
We still have some work to do in the ListViewController class. Start by conforming the ListViewController class to the AddItemViewControllerDelegate protocol. This is also a good moment to declare a constant for the segue with the identifier ItemDetail.
import UIKit
import CloudKit
import SVProgressHUD
let SegueItemDetail = "ItemDetail"
class ListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddItemViewControllerDelegate {
...
}
Implementing the AddItemViewControllerDelegate protocol is trivial. In controller(_:didAddItem:), we add the new item to items, sort items, reload the table view, and invoke updateView.
In sortItems, we sort the array of CKRecord instances by name using the sortInPlace function, a method of the MutableCollectionType protocol.
private func sortItems() {
items.sortInPlace {
var result = false
let name0 = $0.objectForKey("name") as? String
let name1 = $1.objectForKey("name") as? String
if let itemName0 = name0, itemName1 = name1 {
result = itemName0.localizedCaseInsensitiveCompare(itemName1) == .OrderedAscending
}
return result
}
}
There are two more features we need to implement: updating and deleting shopping list items.
Step 6: Deleting Items
To delete items, we need to implement tableView(_:commitEditingStyle:forRowAtIndexPath:) of the UITableViewDataSource protocol. We fetch the shopping list item that needs to be deleted and pass it to the deleteRecord(_:) method.
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
guard editingStyle == .Delete else { return }
// Fetch Record
let item = items[indexPath.row]
// Delete Record
deleteRecord(item)
}
The implementation of deleteRecord(_:) doesn't contain anything new. We invoke deleteRecordWithID(_:completionHandler:) on the private database and process the response in the completion handler.
private func deleteRecord(item: CKRecord) {
// Fetch Private Database
let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
// Show Progress HUD
SVProgressHUD.show()
// Delete List
privateDatabase.deleteRecordWithID(item.recordID) { (recordID, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// Dismiss Progress HUD
SVProgressHUD.dismiss()
// Process Response
self.processResponseForDeleteRequest(item, recordID: recordID, error: error)
})
}
}
In processResponseForDeleteRequest(record:recordID:error:), we update the items property and the user interface. If something went wrong, we notify the user by showing an alert.
private func processResponseForDeleteRequest(record: CKRecord, recordID: CKRecordID?, error: NSError?) {
var message = ""
if let error = error {
print(error)
message = "We are unable to delete the item."
} else if recordID == nil {
message = "We are unable to delete the item."
}
if message.isEmpty {
// Calculate Row Index
let index = items.indexOf(record)
if let index = index {
// Update Data Source
items.removeAtIndex(index)
if items.count > 0 {
// Update Table View
tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: 0)], withRowAnimation: .Right)
} else {
// Update Message Label
messageLabel.text = "No Items Found"
// Update View
updateView()
}
}
} else {
// Initialize Alert Controller
let alertController = UIAlertController(title: "Error", message: message, preferredStyle: .Alert)
// Present Alert Controller
presentViewController(alertController, animated: true, completion: nil)
}
}
Step 7: Updating Items
The user can update an item by tapping the detail disclosure indicator. This means we need to implement the tableView(_:accessoryButtonTappedForRowWithIndexPath:) delegate method. In this method, we store the user's selection and manually perform the ListDetail segue. Note that nothing happens in the tableView(_:didSelectRowAtIndexPath:) method. All we do is deselect the row the user tapped.
In prepareForSegue(_:sender:), we fetch the shopping list item using the value of the selection property and configure the destination view controller, an instance of the AddItemViewController class.
// MARK: -
// MARK: Segue Life Cycle
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let identifier = segue.identifier else { return }
switch identifier {
case SegueItemDetail:
// Fetch Destination View Controller
let addItemViewController = segue.destinationViewController as! AddItemViewController
// Configure View Controller
addItemViewController.list = list
addItemViewController.delegate = self
if let selection = selection {
// Fetch Item
let item = items[selection]
// Configure View Controller
addItemViewController.item = item
}
default:
break
}
}
That's all we need to do to delete and update shopping list items. In the next section, we explore how easy it is to update the data model in the CloudKit Dashboard.
4. Updating the Data Model
If you've ever worked with Core Data, then you know that updating the data model should be done with caution. You need to make sure you don't break anything or corrupt any of the application's persistent stores. CloudKit is a bit more flexible.
The Items record type currently has two fields, name and list. I want to show you what it involves to update the data model by adding a new field. Open the CloudKit Dashboard and add a new field to the Items record. Set field name to number and set the field type to Int(64). Don't forget to save your changes.
Let's now add the ability to modify an item's number. Open AddItemViewController.swift and declare two outlets, a label and a stepper.
import UIKit
import CloudKit
import SVProgressHUD
protocol AddItemViewControllerDelegate {
func controller(controller: AddItemViewController, didAddItem item: CKRecord)
func controller(controller: AddItemViewController, didUpdateItem item: CKRecord)
}
class AddItemViewController: UIViewController {
@IBOutlet weak var numberLabel: UILabel!
@IBOutlet weak var numberStepper: UIStepper!
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var saveButton: UIBarButtonItem!
var delegate: AddItemViewControllerDelegate?
var newItem: Bool = true
var list: CKRecord!
var item: CKRecord?
...
}
We also need to add an action that is triggered when the stepper's value changes. In numberDidChange(_:), we update the contents of numberLabel.
@IBAction func numberDidChange(sender: UIStepper) {
let number = Int(sender.value)
// Update Number Label
numberLabel.text = "\(number)"
}
Open Main.storyboard and add a label and a stepper to the add item view controller. Connect the outlets of the view controller to the corresponding user interface elements and connect the numberDidChange(_:) action to the stepper for the Value Changed event.
The save(_:) action of the AddItemViewController class also changes slightly. Let's see what that looks like.
We only need to add two lines of code. At the top, we store the value of the stepper in a constant, number. When we configure item, we set number as the value for the "number" key, and that's all there is to it.
@IBAction func save(sender: AnyObject) {
// Helpers
let name = nameTextField.text
let number = Int(numberStepper.value)
// Fetch Private Database
let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
if item == nil {
// Create Record
item = CKRecord(recordType: RecordTypeItems)
// Initialize Reference
let listReference = CKReference(recordID: list.recordID, action: .DeleteSelf)
// Configure Record
item?.setObject(listReference, forKey: "list")
}
// Configure Record
item?.setObject(name, forKey: "name")
item?.setObject(number, forKey: "number")
// Show Progress HUD
SVProgressHUD.show()
print(item?.recordType)
// Save Record
privateDatabase.saveRecord(item!) { (record, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// Dismiss Progress HUD
SVProgressHUD.dismiss()
// Process Response
self.processResponse(record, error: error)
})
}
}
We also need to implement a helper method to update the user interface of the add item view controller. The updateNumberStepper method checks if the record has a field named number and updates the stepper if it does.
private func updateNumberStepper() {
if let number = item?.objectForKey("number") as? Double {
numberStepper.value = number
}
}
We invoke updateNumberStepper in the setupView method of the AddItemViewController class.
To visualize the number of each item, we need to make one change to the ListViewController. In tableView(_:cellForRowAtIndexPath:), we set the contents of the cell's right label to the value of the item's number field.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Dequeue Reusable Cell
let cell = tableView.dequeueReusableCellWithIdentifier(ListViewController.ItemCell, forIndexPath: indexPath)
// Configure Cell
cell.accessoryType = .DetailDisclosureButton
// Fetch Record
let item = items[indexPath.row]
if let itemName = item.objectForKey("name") as? String {
// Configure Cell
cell.textLabel?.text = itemName
} else {
cell.textLabel?.text = "-"
}
if let itemNumber = item.objectForKey("number") as? Int {
// Configure Cell
cell.detailTextLabel?.text = "\(itemNumber)"
} else {
cell.detailTextLabel?.text = "1"
}
return cell
}
That's all we need to do to implement the changes we made to the data model. There's no need to perform a migration or anything like that. CloudKit takes care of the nitty gritty details.
Conclusion
You should now have a proper foundation in the CloudKit framework. I hope you agree that Apple has done a great job with this framework and the CloudKit Dashboard. There's a lot that we haven't covered in this series, but by now you've learned enough to jump in and get started with CloudKit in your own projects.
If you have any questions or comments, feel free to leave them in the comments below or reach out to me on Twitter.
In the previous tutorial of this series, we added the ability to add, update, and remove shopping lists. A shopping list without any items in it isn't very useful, though. In this tutorial, we'll add the ability to add, update, and remove items from a shopping list. This means that we'll be working with references and the CKReference class.
We'll also take a closer look at the data model of the shopping list application. How easy is it to make changes to the data model, and how does the application respond to changes we make in the CloudKit Dashboard?
Prerequisites
Remember that I will be using Xcode 7 and Swift 2. If you are using an older version of Xcode, then keep in mind that you are using a different version of the Swift programming language.
In this tutorial, we will continue where we left off in the second tutorial of this series. You can download or clone the project from GitHub.
1. Shopping List Details
Currently, the user can modify the name of a shopping list by tapping the detail disclosure indicator, but the user should also be able to see the contents of a shopping list by tapping one in the lists view controller. To make this work, we first need a new UIViewController subclass.
Step 1: Creating ListViewController
The ListViewController class will display the contents of a shopping list in a table view. The interface of the ListViewController class looks similar to that of the ListsViewController class. We import the CloudKit and SVProgressHUD frameworks and conform the class to the UITableViewDataSource and UITableViewDelegate protocols. Because we'll be using a table view, we declare a constant, ItemCell, that will serve as a cell reuse identifier.
import UIKit
import CloudKit
import SVProgressHUD
class ListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
static let ItemCell = "ItemCell"
@IBOutlet weak var messageLabel: UILabel!
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
var list: CKRecord!
var items = [CKRecord]()
var selection: Int?
...
}
We declare three outlets: messageLabel of type UILabel!, tableView of type UITableView!, and activityIndicatorView of type UIActivityIndicatorView!. The list view controller keeps a reference to the shopping list it is displaying in the list property, which is of type CKRecord!. The items in the shopping list are stored in the items property, which is of type [CKRecord]. Finally, we use a helper variable, selection, to keep track of which item in the shopping list the user has selected. This will become clear later in this tutorial.
Step 2: Creating the User Interface
Open Main.storyboard, add a view controller, and set its class to ListViewController in the Identity Inspector. Select the prototype cell of the lists view controller, press the Control key, and drag from the prototype cell to the list view controller. Choose Show from the menu that pops up, and set the identifier to List in the Attributes Inspector.
Add a table view, a label, and an activity indicator view to the view controller's view. Don't forget to wire up the outlets of the view controller as well as those of the table view.
Select the table view and set Prototype Cells to 1 in the Attributes Inspector. Select the prototype cell, and set Style to Right Detail, Identifier to ItemCell, and Accessory to Disclosure Indicator. This is what the view controller should look like when you're finished.
Step 3: Configuring the View Controller
Before we revisit the CloudKit framework, we need to prepare the view controller for the data it's going to receive. Start by updating the implementation of viewDidLoad. We set the view controller's title to the name of the shopping list and invoke two helper methods, setupView and fetchItems.
// MARK: -
// MARK: View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
// Set Title
title = list.objectForKey("name") as? String
setupView()
fetchItems()
}
The setupView method is identical to the one we implemented in the ListsViewController class.
While we're at it, let's also implement another familiar helper method, updateView. In updateView, we update the user interface of the view controller based on the items stored in the items property.
We're almost ready to take the application for another test run. Before we do, we need to implement the UITableViewDataSource protocol. If you've read the previous installments of this series, then the implementation will look familiar.
To tie everything together, we need to revisit the ListsViewController class. Start by implementing the tableView(_:didSelectRowAtIndexPath:) method of the UITableViewDelegate protocol.
We also need to update prepareForSegue(segue:sender:) to handle the segue we created a few moments ago. This means that we need to add a new case to the switch statement.
// MARK: -
// MARK: Segue Life Cycle
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let identifier = segue.identifier else { return }
switch identifier {
case SegueList:
// Fetch Destination View Controller
let listViewController = segue.destinationViewController as! ListViewController
// Fetch Selection
let list = lists[tableView.indexPathForSelectedRow!.row]
// Configure View Controller
listViewController.list = list
case SegueListDetail:
...
default:
break
}
}
To satisfy the compiler, we also need to declare the SegueList constant at the top of ListsViewController.swift.
import UIKit
import CloudKit
import SVProgressHUD
let RecordTypeLists = "Lists"
let SegueList = "List"
let SegueListDetail = "ListDetail"
class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddListViewControllerDelegate {
...
}
Build and run the application to see if everything is wired up correctly. Because we haven't implemented the fetchItems method yet, no items will be displayed. That's something we need to fix.
2. Fetching Items
Step 1: Create a Record Type
Before we can fetch items from the CloudKit backend, we need to create a new record type in the CloudKit Dashboard. Navigate to the CloudKit Dashboard, create a new record type, and name it Items. Each item should have a name, so create a new field, set the field name to name, and set the field type to String.
Each item should also know to which shopping list it belongs. That means each item needs a reference to its shopping list. Create a new field, set the field name to list, and set the field type to Reference. The Reference field type was designed for this exact purpose, managing relationships.
Head back to Xcode, open ListsViewController.swift, and declare a new constant at the top for the Items record type.
import UIKit
import CloudKit
import SVProgressHUD
let RecordTypeLists = "Lists"
let RecordTypeItems = "Items"
let SegueList = "List"
let SegueListDetail = "ListDetail"
class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddListViewControllerDelegate {
...
}
Step 2: Fetching Items
Open ListViewController.swift and navigate to the fetchItems method. The implementation is similar to the fetchLists method of the ListsViewController class. There is an important difference, though.
private func fetchItems() {
// Fetch Private Database
let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
// Initialize Query
let reference = CKReference(recordID: list.recordID, action: .DeleteSelf)
let query = CKQuery(recordType: RecordTypeItems, predicate: NSPredicate(format: "list == %@", reference))
// Configure Query
query.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
// Perform Query
privateDatabase.performQuery(query, inZoneWithID: nil) { (records, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// Process Response on Main Thread
self.processResponseForQuery(records, error: error)
})
}
}
The difference between fetchItems and fetchLists is the predicate we pass to the CKQuery initializer. We're not interested in every item in the user's private database. We're only interested in the items that are associated with a particular shopping list. This is reflected in the predicate of the CKQuery instance.
We create the predicate by passing in a CKReference instance, which we create by invoking init(recordID:action:). This method accepts two arguments: a CKRecordID instance that references the shopping list record and a CKReferenceAction instance that determines what happens when the shopping list is deleted.
Reference actions are very similar to delete rules in Core Data. If the referenced object (the shopping list in this example) is deleted, then the CloudKit framework inspects the reference action to determine what should happen to the records that hold a reference to the deleted record. The CKReferenceAction enum has two member values:
None: If the reference is deleted, nothing happens to the records referencing the deleted record.
DeleteSelf: If the reference is deleted, every record referencing the deleted record is also deleted.
Because no item should exist without a shopping list, we set the reference action to DeleteSelf.
The processResponseForQuery(records:error:) method contains nothing new. We process the response of the query and update the user interface accordingly.
private func processResponseForQuery(records: [CKRecord]?, error: NSError?) {
var message = ""
if let error = error {
print(error)
message = "Error Fetching Items for List"
} else if let records = records {
items = records
if items.count == 0 {
message = "No Items Found"
}
} else {
message = "No Items Found"
}
if message.isEmpty {
tableView.reloadData()
} else {
messageLabel.text = message
}
updateView()
}
Build and run the application. You won't see any items yet, but the user interface should update to reflect that the shopping list is empty.
3. Adding Items
Step 1: Creating AddItemViewController
It's time to implement the ability to add items to a shopping list. Start by creating a new UIViewController subclass, AddItemViewController. The interface of the view controller is similar to that of the AddListViewController class.
At the top, we import the CloudKit and SVProgressHUD frameworks. We declare the AddItemViewControllerDelegate protocol, which will serve the same purpose as the AddListViewControllerDelegate protocol. The protocol defines two methods, one for adding items and one for updating items.
import UIKit
import CloudKit
import SVProgressHUD
protocol AddItemViewControllerDelegate {
func controller(controller: AddItemViewController, didAddItem item: CKRecord)
func controller(controller: AddItemViewController, didUpdateItem item: CKRecord)
}
class AddItemViewController: UIViewController {
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var saveButton: UIBarButtonItem!
var delegate: AddItemViewControllerDelegate?
var newItem: Bool = true
var list: CKRecord!
var item: CKRecord?
...
}
We declare two outlets, a text field and a bar button item. We also declare a property for the delegate and a helper variable, newItem, that helps us determine whether we're creating a new item or updating an existing item. Finally, we declare a property list for referencing the shopping list to which the item will be added and a property item for the item we're creating or updating.
Before we create the user interface, let's implement two actions that we'll need in the storyboard, cancel(_:) and save(_:). We'll update the implementation of the save(_:) action later in this tutorial.
Open Main.storyboard, add a bar button item to the navigation bar of the list view controller, and set System Item to Add in the Attributes Inspector. Drag a view controller from the Object Library and set its class to AddItemViewController. Create a segue from the bar button item we just created to the add item view controller. Choose Show from the menu that pops up, and set the segue's identifier to ItemDetail.
Add two bar button items to the navigation bar of the add item view controller, a cancel button on the left and a save button on the right. Connect each bar button item to its corresponding action. Add a text field to the view controller's view and don't forget to connect the outlets of the view controller. This is what the add item view controller should look like when you're finished.
Step 3: Configuring the View Controller
The implementation of the add item view controller contains nothing that we haven't covered yet. There's one exception, though, which we'll discuss in a moment. Let's start by configuring the view controller in viewDidLoad.
We invoke setupView, a helper method, and update the value of newItem. If the item property is equal to nil, newItem is equal to true. This helps us determine whether we're creating or updating a shopping list item.
We also add the view controller as an observer for notifications of type UITextFieldTextDidChangeNotification. This means that the view controller is notified when the contents of the nameTextField change.
The setupView method invokes two helper methods, updateNameTextField and updateSaveButton. The implementation of these helper methods is straightforward. In updateNameTextField, we populate the text field. In updateSaveButton, we enable or disable the save button based on the contents of the text field.
// MARK: -
// MARK: View Methods
private func setupView() {
updateNameTextField()
updateSaveButton()
}
// MARK: -
private func updateNameTextField() {
if let name = item?.objectForKey("name") as? String {
nameTextField.text = name
}
}
// MARK: -
private func updateSaveButton() {
let text = nameTextField.text
if let name = text {
saveButton.enabled = !name.isEmpty
} else {
saveButton.enabled = false
}
}
Before we take a look at the updated implementation of the save(_:) method, we need to implement the textFieldDidChange(_:) method. All we do is invoke updateSaveButton to enable or disable the save button.
The save(_:) method is the most interesting method of the AddItemViewController class, because it shows us how to work with CloudKit references. Take a look at the implementation of the save(_:) method below.
Most of its implementation should look familiar since we covered saving records in the AddListViewController class. What interests us most is how the item keeps a reference to its shopping list. We first create a CKReference instance by invoking the designated initializer, init(recordID:action:). We covered the details of creating a CKReference instance a few minutes ago when we created the query for fetching shopping list items.
Telling the item about this reference is easy. We invoke setObjec(_:forKey:) on the item property, passing in the CKReference instance as the value and "list" as the key. The key corresponds to the field name we assigned in the CloudKit Dashboard. Saving the item to iCloud is identical to what we've covered before. That's how easy it is to work with CloudKit references.
@IBAction func save(sender: AnyObject) {
// Helpers
let name = nameTextField.text
// Fetch Private Database
let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
if item == nil {
// Create Record
item = CKRecord(recordType: RecordTypeItems)
// Initialize Reference
let listReference = CKReference(recordID: list.recordID, action: .DeleteSelf)
// Configure Record
item?.setObject(listReference, forKey: "list")
}
// Configure Record
item?.setObject(name, forKey: "name")
// Show Progress HUD
SVProgressHUD.show()
// Save Record
privateDatabase.saveRecord(item!) { (record, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// Dismiss Progress HUD
SVProgressHUD.dismiss()
// Process Response
self.processResponse(record, error: error)
})
}
}
The implementation of processResponse(record:error:) contains nothing new. We check to see if any errors popped up and, if no errors were thrown, we notify the delegate.
// MARK: -
// MARK: Helper Methods
private func processResponse(record: CKRecord?, error: NSError?) {
var message = ""
if let error = error {
print(error)
message = "We were not able to save your item."
} else if record == nil {
message = "We were not able to save your item."
}
if !message.isEmpty {
// Initialize Alert Controller
let alertController = UIAlertController(title: "Error", message: message, preferredStyle: .Alert)
// Present Alert Controller
presentViewController(alertController, animated: true, completion: nil)
} else {
// Notify Delegate
if newItem {
delegate?.controller(self, didAddItem: item!)
} else {
delegate?.controller(self, didUpdateItem: item!)
}
// Pop View Controller
navigationController?.popViewControllerAnimated(true)
}
}
Step 5: Updating ListViewController
We still have some work to do in the ListViewController class. Start by conforming the ListViewController class to the AddItemViewControllerDelegate protocol. This is also a good moment to declare a constant for the segue with the identifier ItemDetail.
import UIKit
import CloudKit
import SVProgressHUD
let SegueItemDetail = "ItemDetail"
class ListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddItemViewControllerDelegate {
...
}
Implementing the AddItemViewControllerDelegate protocol is trivial. In controller(_:didAddItem:), we add the new item to items, sort items, reload the table view, and invoke updateView.
In sortItems, we sort the array of CKRecord instances by name using the sortInPlace function, a method of the MutableCollectionType protocol.
private func sortItems() {
items.sortInPlace {
var result = false
let name0 = $0.objectForKey("name") as? String
let name1 = $1.objectForKey("name") as? String
if let itemName0 = name0, itemName1 = name1 {
result = itemName0.localizedCaseInsensitiveCompare(itemName1) == .OrderedAscending
}
return result
}
}
There are two more features we need to implement: updating and deleting shopping list items.
Step 6: Deleting Items
To delete items, we need to implement tableView(_:commitEditingStyle:forRowAtIndexPath:) of the UITableViewDataSource protocol. We fetch the shopping list item that needs to be deleted and pass it to the deleteRecord(_:) method.
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
guard editingStyle == .Delete else { return }
// Fetch Record
let item = items[indexPath.row]
// Delete Record
deleteRecord(item)
}
The implementation of deleteRecord(_:) doesn't contain anything new. We invoke deleteRecordWithID(_:completionHandler:) on the private database and process the response in the completion handler.
private func deleteRecord(item: CKRecord) {
// Fetch Private Database
let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
// Show Progress HUD
SVProgressHUD.show()
// Delete List
privateDatabase.deleteRecordWithID(item.recordID) { (recordID, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// Dismiss Progress HUD
SVProgressHUD.dismiss()
// Process Response
self.processResponseForDeleteRequest(item, recordID: recordID, error: error)
})
}
}
In processResponseForDeleteRequest(record:recordID:error:), we update the items property and the user interface. If something went wrong, we notify the user by showing an alert.
private func processResponseForDeleteRequest(record: CKRecord, recordID: CKRecordID?, error: NSError?) {
var message = ""
if let error = error {
print(error)
message = "We are unable to delete the item."
} else if recordID == nil {
message = "We are unable to delete the item."
}
if message.isEmpty {
// Calculate Row Index
let index = items.indexOf(record)
if let index = index {
// Update Data Source
items.removeAtIndex(index)
if items.count > 0 {
// Update Table View
tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: 0)], withRowAnimation: .Right)
} else {
// Update Message Label
messageLabel.text = "No Items Found"
// Update View
updateView()
}
}
} else {
// Initialize Alert Controller
let alertController = UIAlertController(title: "Error", message: message, preferredStyle: .Alert)
// Present Alert Controller
presentViewController(alertController, animated: true, completion: nil)
}
}
Step 7: Updating Items
The user can update an item by tapping the detail disclosure indicator. This means we need to implement the tableView(_:accessoryButtonTappedForRowWithIndexPath:) delegate method. In this method, we store the user's selection and manually perform the ListDetail segue. Note that nothing happens in the tableView(_:didSelectRowAtIndexPath:) method. All we do is deselect the row the user tapped.
In prepareForSegue(_:sender:), we fetch the shopping list item using the value of the selection property and configure the destination view controller, an instance of the AddItemViewController class.
// MARK: -
// MARK: Segue Life Cycle
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let identifier = segue.identifier else { return }
switch identifier {
case SegueItemDetail:
// Fetch Destination View Controller
let addItemViewController = segue.destinationViewController as! AddItemViewController
// Configure View Controller
addItemViewController.list = list
addItemViewController.delegate = self
if let selection = selection {
// Fetch Item
let item = items[selection]
// Configure View Controller
addItemViewController.item = item
}
default:
break
}
}
That's all we need to do to delete and update shopping list items. In the next section, we explore how easy it is to update the data model in the CloudKit Dashboard.
4. Updating the Data Model
If you've ever worked with Core Data, then you know that updating the data model should be done with caution. You need to make sure you don't break anything or corrupt any of the application's persistent stores. CloudKit is a bit more flexible.
The Items record type currently has two fields, name and list. I want to show you what it involves to update the data model by adding a new field. Open the CloudKit Dashboard and add a new field to the Items record. Set field name to number and set the field type to Int(64). Don't forget to save your changes.
Let's now add the ability to modify an item's number. Open AddItemViewController.swift and declare two outlets, a label and a stepper.
import UIKit
import CloudKit
import SVProgressHUD
protocol AddItemViewControllerDelegate {
func controller(controller: AddItemViewController, didAddItem item: CKRecord)
func controller(controller: AddItemViewController, didUpdateItem item: CKRecord)
}
class AddItemViewController: UIViewController {
@IBOutlet weak var numberLabel: UILabel!
@IBOutlet weak var numberStepper: UIStepper!
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var saveButton: UIBarButtonItem!
var delegate: AddItemViewControllerDelegate?
var newItem: Bool = true
var list: CKRecord!
var item: CKRecord?
...
}
We also need to add an action that is triggered when the stepper's value changes. In numberDidChange(_:), we update the contents of numberLabel.
@IBAction func numberDidChange(sender: UIStepper) {
let number = Int(sender.value)
// Update Number Label
numberLabel.text = "\(number)"
}
Open Main.storyboard and add a label and a stepper to the add item view controller. Connect the outlets of the view controller to the corresponding user interface elements and connect the numberDidChange(_:) action to the stepper for the Value Changed event.
The save(_:) action of the AddItemViewController class also changes slightly. Let's see what that looks like.
We only need to add two lines of code. At the top, we store the value of the stepper in a constant, number. When we configure item, we set number as the value for the "number" key, and that's all there is to it.
@IBAction func save(sender: AnyObject) {
// Helpers
let name = nameTextField.text
let number = Int(numberStepper.value)
// Fetch Private Database
let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
if item == nil {
// Create Record
item = CKRecord(recordType: RecordTypeItems)
// Initialize Reference
let listReference = CKReference(recordID: list.recordID, action: .DeleteSelf)
// Configure Record
item?.setObject(listReference, forKey: "list")
}
// Configure Record
item?.setObject(name, forKey: "name")
item?.setObject(number, forKey: "number")
// Show Progress HUD
SVProgressHUD.show()
print(item?.recordType)
// Save Record
privateDatabase.saveRecord(item!) { (record, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// Dismiss Progress HUD
SVProgressHUD.dismiss()
// Process Response
self.processResponse(record, error: error)
})
}
}
We also need to implement a helper method to update the user interface of the add item view controller. The updateNumberStepper method checks if the record has a field named number and updates the stepper if it does.
private func updateNumberStepper() {
if let number = item?.objectForKey("number") as? Double {
numberStepper.value = number
}
}
We invoke updateNumberStepper in the setupView method of the AddItemViewController class.
To visualize the number of each item, we need to make one change to the ListViewController. In tableView(_:cellForRowAtIndexPath:), we set the contents of the cell's right label to the value of the item's number field.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Dequeue Reusable Cell
let cell = tableView.dequeueReusableCellWithIdentifier(ListViewController.ItemCell, forIndexPath: indexPath)
// Configure Cell
cell.accessoryType = .DetailDisclosureButton
// Fetch Record
let item = items[indexPath.row]
if let itemName = item.objectForKey("name") as? String {
// Configure Cell
cell.textLabel?.text = itemName
} else {
cell.textLabel?.text = "-"
}
if let itemNumber = item.objectForKey("number") as? Int {
// Configure Cell
cell.detailTextLabel?.text = "\(itemNumber)"
} else {
cell.detailTextLabel?.text = "1"
}
return cell
}
That's all we need to do to implement the changes we made to the data model. There's no need to perform a migration or anything like that. CloudKit takes care of the nitty gritty details.
Conclusion
You should now have a proper foundation in the CloudKit framework. I hope you agree that Apple has done a great job with this framework and the CloudKit Dashboard. There's a lot that we haven't covered in this series, but by now you've learned enough to jump in and get started with CloudKit in your own projects.
If you have any questions or comments, feel free to leave them in the comments below or reach out to me on Twitter.
In the previous post on Android user data security, we looked at encrypting data via a user-supplied passcode. This tutorial will shift the focus to credential and key storage. I'll begin by introducing account credentials and end with an example of protecting data using the KeyStore.
Often
when working with a third-party service there will be some form of
authentication required. This may be as simple as a /login endpoint
that accepts a username and password. It would seem at first that a simple
solution is to build UI that asks the user to log in, then capture and
store their login credentials. However, this isn't the best practice because
our app shouldn't need to know the credentials for a 3rd party account. Instead, we can use the
Account Manager, which delegates handling that sensitive information
for us.
Account
Manager
The Account Manager is a centralized helper
for user account credentials so that your app does not have to deal
with passwords directly. It often provides a token in place of the
real username and password that can be used to make
authenticated requests to a service. An example is when requesting an OAuth2 token. Sometimes all the required information is already
stored on the device, and other times the Account Manager will need
to call a server for a refreshed token. You may have seen the Accounts section in your device's Settings for various apps. We can get that list of available accounts like this:
Once
you have the account, a
token for
the account can
be retrieved
by calling the getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler) method. The token can then be used to make authenticated API requests to a service. This could be a RESTful API where you pass in a token parameter during an HTTPS request, without having to ever know the user's private account details.
Because
each service will have a different way of authenticating and storing
the private credentials, the Account Manager provides authenticator
modules for a 3rd party service to implement. While Android has
implementations for many popular services, it means you can write
your own authenticator to handle your app's account authentication
and credential storage. This allows you to make sure the credentials are
encrypted. Keep in mind, this also means that credentials in the
Account Manager that are used by other services may be stored in
clear text, making them visible to anyone who has rooted their
device.
Instead
of simple credentials, there are times when you will need to deal with a key
or a certificate for an individual or entity, for example, when a third
party sends you a certificate file which you need to keep. The most common scenario is when an app needs to authenticate to a private organization's server. In the
next tutorial, we will be looking at using certificates for
authentication and secure communications, but I still want to address
how to store these items in the meantime. The Keychain API was
originally built for that very specific use—installing a private
key or certificate pair from a PKCS#12 file.
The Keychain
Introduced
in Android 4.0 (API Level 14), the Keychain API deals with key
management. Specifically, it works with PrivateKey andX509Certificate objects and provides a more secure container than using your app's data storage. That's because permissions for private keys only allow for your own app
to access the keys, and only after user authorization. This means
that a lock screen must be set up on the device before you can make
use of the credential storage. Also, the objects in the keychain may be
bound to secure hardware, if available. The code to install a certificate is as follows:
Intent intent = KeyChain.createInstallIntent();
byte[] p12Bytes = //... read from file, such as example.pfx or example.p12...
intent.putExtra(KeyChain.EXTRA_PKCS12, p12Bytes);
startActivity(intent);
The
user will be prompted for a password to access the private key and an
option to name the certificate. To retrieve the key, the following code presents UI that lets the user choose from the list of installed keys.
KeyChain.choosePrivateKeyAlias(this, this, new String[]{"RSA"}, null, null, -1, null);
Once the choice is made, a string alias name is returned in the alias(final String alias) callback where you can access the private key or
certificate chain directly.
Armed with that knowledge, let's now see how we can use the credential storage to save your own sensitive data.
The KeyStore
In
the previous tutorial, we looked at protecting data via a
user-supplied passcode. This kind of setup is good, but app
requirements often steer away from having users login each time and
remember an additional passcode. That's where the KeyStore API can be
used. Since API 1, the KeyStore has been used by the system to store WIFI
and VPN credentials. As of 4.3 (API 18), it allows working with your
own app-specific asymmetric keys, and in Android M (API 23) it can store an AESsymmetric key. So while the API doesn't allow storing
sensitive strings directly, these keys can be stored, and then used to encrypt strings.
The benefit to
storing a key in the KeyStore is that it allows keys to be operated
on without exposing the secret content of that key; key data does not
enter the app space. Remember that keys are protected by permissions
so that only your app can access them, and they may additionally be
secure hardware-backed if the device is capable. This creates a
container that makes it more difficult to extract keys from a device.
Generate a New Random Key
So for this example, instead of generating an AES key from a user-supplied
passcode, we can auto-generate a random key that will be protected in
the KeyStore. We can do this by creating a KeyGenerator instance, set
to the "AndroidKeyStore" provider.
//Generate a key and store it in the KeyStore
final KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
final KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder("MyKeyAlias",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
//.setUserAuthenticationRequired(true) //requires lock screen, invalidated if lock screen is disabled
//.setUserAuthenticationValidityDurationSeconds(120) //only available x seconds from password authentication. -1 requires finger print - every time
.setRandomizedEncryptionRequired(true) //different ciphertext for same plaintext on each call
.build();
keyGenerator.init(keyGenParameterSpec);
keyGenerator.generateKey();
Important parts to look at here are the .setUserAuthenticationRequired(true) and .setUserAuthenticationValidityDurationSeconds(120) specifications. These require a lock screen to be set up and lock the key until the user has authenticated. Looking at the documentation for .setUserAuthenticationValidityDurationSeconds(), you will see that it means the key is only available a certain number of seconds from password authentication, and that passing in -1 requires finger print authentication every time you want to access the key. Enabling the requirement for authentication also has the effect of revoking the key when the user removes or changes the lock screen. Because storing an unprotected key along side the encrypted data is like putting a house key under the doormat, these options attempt to protect the key at rest in the event a device is compromised. An example might be an offline data dump of the device. Without the password being known for the device, that data is rendered useless.
The .setRandomizedEncryptionRequired(true) option enables the requirement that there is enough randomization (a new random IV each time) so that if the exact same data is encrypted a second time around, that encrypted output will still be different. This prevents an attacker from gaining clues about the ciphertext based on feeding in the same data. Another option to note is setUserAuthenticationValidWhileOnBody(boolean remainsValid), which locks the key once the device has detected it is no longer on the person.
Encrypting Data
Now
that the key is stored in the KeyStore, we can create a method that
encrypts data using the Cipher object, given theSecretKey. It will return a HashMap containing the encrypted data,
and a randomized IV that will be needed to decrypt the data. The encrypted data, along with the IV, can then be saved to a file or into the shared preferences.
private HashMap<String, byte[]> encrypt(final byte[] decryptedBytes)
{
final HashMap<String, byte[]> map = new HashMap<String, byte[]>();
try
{
//Get the key
final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
final KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry)keyStore.getEntry("MyKeyAlias", null);
final SecretKey secretKey = secretKeyEntry.getSecretKey();
//Encrypt data
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
final byte[] ivBytes = cipher.getIV();
final byte[] encryptedBytes = cipher.doFinal(decryptedBytes);
map.put("iv", ivBytes);
map.put("encrypted", encryptedBytes);
}
catch (Throwable e)
{
e.printStackTrace();
}
return map;
}
Decrypting to a Byte Array
For
decryption, the reverse is applied. The Cipher object is initialized
using the DECRYPT_MODE constant and a decrypted byte[] array is
returned.
private byte[] decrypt(final HashMap<String, byte[]> map)
{
byte[] decryptedBytes = null;
try
{
//Get the key
final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
final KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry)keyStore.getEntry("MyKeyAlias", null);
final SecretKey secretKey = secretKeyEntry.getSecretKey();
//Extract info from map
final byte[] encryptedBytes = map.get("encrypted");
final byte[] ivBytes = map.get("iv");
//Decrypt data
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
final GCMParameterSpec spec = new GCMParameterSpec(128, ivBytes);
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
decryptedBytes = cipher.doFinal(encryptedBytes);
}
catch (Throwable e)
{
e.printStackTrace();
}
return decryptedBytes;
}
Testing the Example
We can now test our example!
@TargetApi(Build.VERSION_CODES.M)
private void testEncryption()
{
try
{
//Generate a key and store it in the KeyStore
final KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
final KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder("MyKeyAlias",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
//.setUserAuthenticationRequired(true) //requires lock screen, invalidated if lock screen is disabled
//.setUserAuthenticationValidityDurationSeconds(120) //only available x seconds from password authentication. -1 requires finger print - every time
.setRandomizedEncryptionRequired(true) //different ciphertext for same plaintext on each call
.build();
keyGenerator.init(keyGenParameterSpec);
keyGenerator.generateKey();
//Test
final HashMap<String, byte[]> map = encrypt("My very sensitive string!".getBytes("UTF-8"));
final byte[] decryptedBytes = decrypt(map);
final String decryptedString = new String(decryptedBytes, "UTF-8");
Log.e("MyApp", "The decrypted string is " + decryptedString);
}
catch (Throwable e)
{
e.printStackTrace();
}
}
Using RSA Asymmetric Keys for Older Devices
This
is a good solution to store data for versions M and higher, but what
if your app supports earlier versions? While AES symmetric keys are
not supported under M, RSA asymmetric keys are. That means we can use
RSA keys and encryption to accomplish the same thing. The
main difference here is that an asymmetric keypair contains two keys,
a private and a public key, where the public key encrypts the data
and the private key decrypts it. A KeyPairGeneratorSpec is passed
into the KeyPairGenerator that is initialized with KEY_ALGORITHM_RSAand the "AndroidKeyStore" provider.
private void testPreMEncryption()
{
try
{
//Generate a keypair and store it in the KeyStore
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 10);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(this)
.setAlias("MyKeyAlias")
.setSubject(new X500Principal("CN=MyKeyName, O=Android Authority"))
.setSerialNumber(new BigInteger(1024, new Random()))
.setStartDate(start.getTime())
.setEndDate(end.getTime())
.setEncryptionRequired() //on API level 18, encrypted at rest, requires lock screen to be set up, changing lock screen removes key
.build();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
keyPairGenerator.initialize(spec);
keyPairGenerator.generateKeyPair();
//Encryption test
final byte[] encryptedBytes = rsaEncrypt("My secret string!".getBytes("UTF-8"));
final byte[] decryptedBytes = rsaDecrypt(encryptedBytes);
final String decryptedString = new String(decryptedBytes, "UTF-8");
Log.e("MyApp", "Decrypted string is " + decryptedString);
}
catch (Throwable e)
{
e.printStackTrace();
}
}
To
encrypt, we get the RSAPublicKey from the keypair and use it with theCipher object.
public byte[] rsaEncrypt(final byte[] decryptedBytes)
{
byte[] encryptedBytes = null;
try
{
final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
final KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry("MyKeyAlias", null);
final RSAPublicKey publicKey = (RSAPublicKey)privateKeyEntry.getCertificate().getPublicKey();
final Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
final CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);
cipherOutputStream.write(decryptedBytes);
cipherOutputStream.close();
encryptedBytes = outputStream.toByteArray();
}
catch (Throwable e)
{
e.printStackTrace();
}
return encryptedBytes;
}
Decryption is done using the RSAPrivateKey object.
public byte[] rsaDecrypt(final byte[] encryptedBytes)
{
byte[] decryptedBytes = null;
try
{
final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
final KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry("MyKeyAlias", null);
final RSAPrivateKey privateKey = (RSAPrivateKey)privateKeyEntry.getPrivateKey();
final Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
final CipherInputStream cipherInputStream = new CipherInputStream(new ByteArrayInputStream(encryptedBytes), cipher);
final ArrayList<Byte> arrayList = new ArrayList<>();
int nextByte;
while ( (nextByte = cipherInputStream.read()) != -1 )
{
arrayList.add((byte)nextByte);
}
decryptedBytes = new byte[arrayList.size()];
for(int i = 0; i < decryptedBytes.length; i++)
{
decryptedBytes[i] = arrayList.get(i);
}
}
catch (Throwable e)
{
e.printStackTrace();
}
return decryptedBytes;
}
One
thing about RSA is that encryption is slower than it is in AES. This is
usually fine for small amounts of information such as when you're securing
shared preference strings. If you find there is a performance problem
encrypting large amounts of data, however, you can instead use this example to
encrypt and store just an AES key. Then, use that faster AES
encryption that was discussed in theprevious tutorialfor the rest of your data. You can generate a new AES key and convert it to abyte[] array that is compatible with this example.
SecretKey key = new SecretKeySpec(keyBytes, 0, keyBytes.length, "AES");
That
was a lot of code! To keep all of the examples simple, I have
omitted thorough exception handling. But remember that for your production
code, it's not recommended to simply catch all Throwable cases
in one catch statement.
Conclusion
This
completes the tutorial on working with credentials and keys. Much of
the confusion around keys and storage has to
do with the evolution of the Android OS, but you can choose which
solution to use given the API level your app supports.
Now that we
have covered the best practices for securing data at rest, the next
tutorial will focus on securing data in transit.
2018-06-11T12:27:26.000Z2018-06-11T12:27:26.000ZCollin Stuart
In the previous post on Android user data security, we looked at encrypting data via a user-supplied passcode. This tutorial will shift the focus to credential and key storage. I'll begin by introducing account credentials and end with an example of protecting data using the KeyStore.
Often, when working with a third-party service, there will be some form of
authentication required. This may be as simple as a /login endpoint
that accepts a username and password.
It would seem at first that a simple
solution is to build a UI that asks the user to log in, and then capture and
store their login credentials. However, this isn't the best practice because
our app shouldn't need to know the credentials for a third-party account. Instead, we can use the
Account Manager, which delegates handling that sensitive information
for us.
Account
Manager
The Account Manager is a centralized helper
for user account credentials so that your app does not have to deal
with passwords directly. It often provides a token in place of the
real username and password that can be used to make
authenticated requests to a service. An example is when requesting an OAuth2 token.
Sometimes, all the required information is already
stored on the device, and other times the Account Manager will need
to call a server for a refreshed token. You may have seen the Accounts section in your device's Settings for various apps. We can get that list of available accounts like this:
Once
you have the account, a
token for
the account can
be retrieved
by calling the getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler) method. The token can then be used to make authenticated API requests to a service. This could be a RESTful API where you pass in a token parameter during an HTTPS request, without ever having to know the user's private account details.
Because
each service will have a different way of authenticating and storing
the private credentials, the Account Manager provides authenticator
modules for a third-party service to implement. While Android has
implementations for many popular services, it means you can write
your own authenticator to handle your app's account authentication
and credential storage. This allows you to make sure the credentials are
encrypted. Keep in mind, this also means that credentials in the
Account Manager that are used by other services may be stored in
clear text, making them visible to anyone who has rooted their
device.
Instead
of simple credentials, there are times when you will need to deal with a key
or a certificate for an individual or entity—for example, when a third
party sends you a certificate file which you need to keep. The most common scenario is when an app needs to authenticate to a private organization's server.
In the
next tutorial, we will be looking at using certificates for
authentication and secure communications, but I still want to address
how to store these items in the meantime. The Keychain API was
originally built for that very specific use—installing a private
key or certificate pair from a PKCS#12 file.
The Keychain
Introduced
in Android 4.0 (API Level 14), the Keychain API deals with key
management. Specifically, it works with PrivateKey andX509Certificate objects and provides a more secure container than using your app's data storage. That's because permissions for private keys only allow for your own app
to access the keys, and only after user authorization. This means
that a lock screen must be set up on the device before you can make
use of the credential storage. Also, the objects in the keychain may be
bound to secure hardware, if available.
The code to install a certificate is as follows:
Intent intent = KeyChain.createInstallIntent();
byte[] p12Bytes = //... read from file, such as example.pfx or example.p12...
intent.putExtra(KeyChain.EXTRA_PKCS12, p12Bytes);
startActivity(intent);
The
user will be prompted for a password to access the private key and an
option to name the certificate. To retrieve the key, the following code presents a UI that lets the user choose from the list of installed keys.
KeyChain.choosePrivateKeyAlias(this, this, new String[]{"RSA"}, null, null, -1, null);
Once the choice is made, a string alias name is returned in the alias(final String alias) callback where you can access the private key or
certificate chain directly.
Armed with that knowledge, let's now see how we can use the credential storage to save your own sensitive data.
The KeyStore
In
the previous tutorial, we looked at protecting data via a
user-supplied passcode. This kind of setup is good, but app
requirements often steer away from having users log in each time and
remember an additional passcode.
That's where the KeyStore API can be
used. Since API 1, the KeyStore has been used by the system to store WiFi and VPN credentials. As of 4.3 (API 18), it allows you to work with your
own app-specific asymmetric keys, and in Android M (API 23) it can store an AESsymmetric key. So while the API doesn't allow storing
sensitive strings directly, these keys can be stored and then used to encrypt strings.
The benefit to
storing a key in the KeyStore is that it allows keys to be operated
on without exposing the secret content of that key; key data does not
enter the app space. Remember that keys are protected by permissions
so that only your app can access them, and they may additionally be
secure hardware-backed if the device is capable. This creates a
container that makes it more difficult to extract keys from a device.
Generate a New Random Key
For this example, instead of generating an AES key from a user-supplied
passcode, we can auto-generate a random key that will be protected in
the KeyStore. We can do this by creating a KeyGenerator instance, set
to the "AndroidKeyStore" provider.
//Generate a key and store it in the KeyStore
final KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
final KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder("MyKeyAlias",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
//.setUserAuthenticationRequired(true) //requires lock screen, invalidated if lock screen is disabled
//.setUserAuthenticationValidityDurationSeconds(120) //only available x seconds from password authentication. -1 requires finger print - every time
.setRandomizedEncryptionRequired(true) //different ciphertext for same plaintext on each call
.build();
keyGenerator.init(keyGenParameterSpec);
keyGenerator.generateKey();
Important parts to look at here are the .setUserAuthenticationRequired(true) and .setUserAuthenticationValidityDurationSeconds(120) specifications. These require a lock screen to be set up and the key to be locked until the user has authenticated.
Looking at the documentation for .setUserAuthenticationValidityDurationSeconds(), you will see that it means the key is only available a certain number of seconds from password authentication, and that passing in -1 requires fingerprint authentication every time you want to access the key. Enabling the requirement for authentication also has the effect of revoking the key when the user removes or changes the lock screen.
Because storing an unprotected key alongside the encrypted data is like putting a house key under the doormat, these options attempt to protect the key at rest in the event a device is compromised. An example might be an offline data dump of the device. Without the password being known for the device, that data is rendered useless.
The .setRandomizedEncryptionRequired(true) option enables the requirement that there is enough randomization (a new random IV each time) so that if the same data is encrypted a second time around, that encrypted output will still be different. This prevents an attacker from gaining clues about the ciphertext based on feeding in the same data.
Now
that the key is stored in the KeyStore, we can create a method that
encrypts data using the Cipher object, given theSecretKey. It will return a HashMap containing the encrypted data and a randomized IV that will be needed to decrypt the data. The encrypted data, along with the IV, can then be saved to a file or into the shared preferences.
private HashMap<String, byte[]> encrypt(final byte[] decryptedBytes)
{
final HashMap<String, byte[]> map = new HashMap<String, byte[]>();
try
{
//Get the key
final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
final KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry)keyStore.getEntry("MyKeyAlias", null);
final SecretKey secretKey = secretKeyEntry.getSecretKey();
//Encrypt data
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
final byte[] ivBytes = cipher.getIV();
final byte[] encryptedBytes = cipher.doFinal(decryptedBytes);
map.put("iv", ivBytes);
map.put("encrypted", encryptedBytes);
}
catch (Throwable e)
{
e.printStackTrace();
}
return map;
}
Decrypting to a Byte Array
For
decryption, the reverse is applied. The Cipher object is initialized
using the DECRYPT_MODE constant, and a decrypted byte[] array is
returned.
private byte[] decrypt(final HashMap<String, byte[]> map)
{
byte[] decryptedBytes = null;
try
{
//Get the key
final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
final KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry)keyStore.getEntry("MyKeyAlias", null);
final SecretKey secretKey = secretKeyEntry.getSecretKey();
//Extract info from map
final byte[] encryptedBytes = map.get("encrypted");
final byte[] ivBytes = map.get("iv");
//Decrypt data
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
final GCMParameterSpec spec = new GCMParameterSpec(128, ivBytes);
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
decryptedBytes = cipher.doFinal(encryptedBytes);
}
catch (Throwable e)
{
e.printStackTrace();
}
return decryptedBytes;
}
Testing the Example
We can now test our example!
@TargetApi(Build.VERSION_CODES.M)
private void testEncryption()
{
try
{
//Generate a key and store it in the KeyStore
final KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
final KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder("MyKeyAlias",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
//.setUserAuthenticationRequired(true) //requires lock screen, invalidated if lock screen is disabled
//.setUserAuthenticationValidityDurationSeconds(120) //only available x seconds from password authentication. -1 requires finger print - every time
.setRandomizedEncryptionRequired(true) //different ciphertext for same plaintext on each call
.build();
keyGenerator.init(keyGenParameterSpec);
keyGenerator.generateKey();
//Test
final HashMap<String, byte[]> map = encrypt("My very sensitive string!".getBytes("UTF-8"));
final byte[] decryptedBytes = decrypt(map);
final String decryptedString = new String(decryptedBytes, "UTF-8");
Log.e("MyApp", "The decrypted string is " + decryptedString);
}
catch (Throwable e)
{
e.printStackTrace();
}
}
Using RSA Asymmetric Keys for Older Devices
This
is a good solution to store data for versions M and higher, but what
if your app supports earlier versions? While AES symmetric keys are
not supported under M, RSA asymmetric keys are. That means we can use
RSA keys and encryption to accomplish the same thing.
The
main difference here is that an asymmetric keypair contains two keys,
a private and a public key, where the public key encrypts the data
and the private key decrypts it. A KeyPairGeneratorSpec is passed
into the KeyPairGenerator that is initialized with KEY_ALGORITHM_RSAand the "AndroidKeyStore" provider.
private void testPreMEncryption()
{
try
{
//Generate a keypair and store it in the KeyStore
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 10);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(this)
.setAlias("MyKeyAlias")
.setSubject(new X500Principal("CN=MyKeyName, O=Android Authority"))
.setSerialNumber(new BigInteger(1024, new Random()))
.setStartDate(start.getTime())
.setEndDate(end.getTime())
.setEncryptionRequired() //on API level 18, encrypted at rest, requires lock screen to be set up, changing lock screen removes key
.build();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
keyPairGenerator.initialize(spec);
keyPairGenerator.generateKeyPair();
//Encryption test
final byte[] encryptedBytes = rsaEncrypt("My secret string!".getBytes("UTF-8"));
final byte[] decryptedBytes = rsaDecrypt(encryptedBytes);
final String decryptedString = new String(decryptedBytes, "UTF-8");
Log.e("MyApp", "Decrypted string is " + decryptedString);
}
catch (Throwable e)
{
e.printStackTrace();
}
}
To
encrypt, we get the RSAPublicKey from the keypair and use it with theCipher object.
public byte[] rsaEncrypt(final byte[] decryptedBytes)
{
byte[] encryptedBytes = null;
try
{
final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
final KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry("MyKeyAlias", null);
final RSAPublicKey publicKey = (RSAPublicKey)privateKeyEntry.getCertificate().getPublicKey();
final Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
final CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);
cipherOutputStream.write(decryptedBytes);
cipherOutputStream.close();
encryptedBytes = outputStream.toByteArray();
}
catch (Throwable e)
{
e.printStackTrace();
}
return encryptedBytes;
}
Decryption is done using the RSAPrivateKey object.
public byte[] rsaDecrypt(final byte[] encryptedBytes)
{
byte[] decryptedBytes = null;
try
{
final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
final KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry("MyKeyAlias", null);
final RSAPrivateKey privateKey = (RSAPrivateKey)privateKeyEntry.getPrivateKey();
final Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
final CipherInputStream cipherInputStream = new CipherInputStream(new ByteArrayInputStream(encryptedBytes), cipher);
final ArrayList<Byte> arrayList = new ArrayList<>();
int nextByte;
while ( (nextByte = cipherInputStream.read()) != -1 )
{
arrayList.add((byte)nextByte);
}
decryptedBytes = new byte[arrayList.size()];
for(int i = 0; i < decryptedBytes.length; i++)
{
decryptedBytes[i] = arrayList.get(i);
}
}
catch (Throwable e)
{
e.printStackTrace();
}
return decryptedBytes;
}
One
thing about RSA is that encryption is slower than it is in AES. This is
usually fine for small amounts of information, such as when you're securing
shared preference strings. If you find there is a performance problem
encrypting large amounts of data, however, you can instead use this example to
encrypt and store just an AES key. Then, use that faster AES
encryption that was discussed in theprevious tutorial for the rest of your data. You can generate a new AES key and convert it to abyte[] array that is compatible with this example.
SecretKey key = new SecretKeySpec(keyBytes, 0, keyBytes.length, "AES");
That
was a lot of code! To keep all of the examples simple, I have
omitted thorough exception handling. But remember that for your production
code, it's not recommended to simply catch all Throwable cases
in one catch statement.
Conclusion
This
completes the tutorial on working with credentials and keys. Much of
the confusion around keys and storage has to
do with the evolution of the Android OS, but you can choose which
solution to use given the API level your app supports.
Now that we
have covered the best practices for securing data at rest, the next
tutorial will focus on securing data in transit.
2018-06-11T12:27:26.000Z2018-06-11T12:27:26.000ZCollin Stuart
There is a huge demand these days for Android apps that offer immersive virtual reality or augmented reality experiences. As a developer, there are many different frameworks you can use to create such apps.
But, unless you are also a skilled 3D artist, how are you going to create the 3D objects you would be displaying in those apps? Are you ready to spend months learning how to work with 3D modeling programs such as Blender or Maya? If you are not, you should consider using Google Poly, an online repository containing thousands of 3D assets that come with Creative Commons licenses.
Most of the assets you can find on Poly today are low poly with simple materials. This is because the average mobile GPU is yet to become powerful enough to display 3D objects with high polygon counts in real time.
In this tutorial, I'll introduce you to the Poly API. I'll also show you how to use Processing for Android to render the 3D assets you download.
All the HTTP requests you make to the Poly API must be accompanied by an API key that belongs to you. To acquire the key, start by logging in to the Google Cloud console and navigating to the APIs dashboard.
Next, press the Enable APIs and services button, expand the Other category, and select Poly API.
You can now press the Enable button to enable the Poly API.
Once the API is enabled, a set of API keys are generated for it automatically. You can open the Credentials tab to take a look at them.
For this tutorial, you'll be needing only the Android key. Make a note of it so you can use it later.
2. Project Setup
Because Poly currently doesn't have an official toolkit for the Android platform, you'll have to work with the Poly API directly using its REST interface. By using the Fuel networking library, which is optimized for the Kotlin language, you can save a lot of time and effort. So add the following implementation dependency in the app module's build.gradle file:
To be able to display the 3D assets you download from the Poly repository, you'll also need a rendering engine. Processing for Android comes with one, so add it as another dependency.
To be able to download a Poly asset, you must know its unique ID. By using a browser, one that supports WebGL, you can easily determine the ID of any asset. It's right in the address bar.
However, if you want to allow your users to dynamically, at run time, decide which assets they want to use, you can use the assets.list REST method to determine the IDs of those assets. The method allows you to look for assets using a variety of parameters, such as keywords, categories, and 3D file formats.
For the sake of a realistic example, let's now try to find the IDs of a few assets that belong to the animals category. You are, of course, free to choose any other valid category, such as architecture, food, or people.
Before you compose your HTTP request, it's a good idea to declare your API key and the base URL of the Poly API as constants inside your activity.
companion object {
const val key = "Abcdefghabcdefgh1234567810"
const val baseURL = "https://poly.googleapis.com/v1"
}
Using the base URL, you can build the URL of the assets.list REST method as shown below:
val listURL = "$baseURL/assets"
At this point, you can create a valid HTTP GET request by calling the httpGet() method and passing your API key and the desired category as query parameters to it. Optionally, you can use the format query parameter to specify the desired format of the assets. Poly supports OBJ, FBX, TILT, and several other popular 3D formats.
Because the method runs asynchronously and its result is a JSON document, you must attach an event handler to it by using the responseJSON() method. The following code shows you how:
listURL.httpGet(listOf(
"category" to "animals",
"key" to key,
"format" to "OBJ"
)).responseJson { _, _, result ->
// More code here
}
Inside the event handler, if your request was successful, you'll have access to a list of Asset objects. The name field of each such object specifies its ID.
Additionally, each object will have fields such as displayName, license, and authorName, which you might find useful. For now, let's simply print the name and displayName of all the objects. The following code shows you how:
result.fold({
// Get assets array
val assets = it.obj().getJSONArray("assets")
// Loop through array
for(i in 0 until assets.length()) {
// Get id and displayName
val id = assets.getJSONObject(i).getString("name")
val displayName =
assets.getJSONObject(i).getString("displayName")
// Print id and displayName
Log.d("POLY", "(ID: $id) -- (NAME: $displayName)")
}
}, {
// In case of an error
Log.e("POLY", "An error occurred")
})
If you run your app now, you should be able to see the following output in the Logcat window of Android Studio.
4. Downloading Assets
Once you have the unique ID of an asset, you can directly append it to the base URL of the Poly API to create an asset URL.
// some asset id
val assetID = "assets/3yiIERrKNQr"
// its url
val assetURL = "$baseURL/$assetID"
When you make an HTTP GET request to the asset URL using the httpGet() method again, you'll get a JSON document containing just one Asset object.
assetURL.httpGet(listOf("key" to key))
.responseJson { _, _, result ->
result.fold({
val asset = it.obj()
// More code here
}, {
Log.e("POLY", "An error occurred")
})
}
As you may have noticed in the above code, this request too must have a query parameter mentioning your API key.
You've already learned how to use some of the fields present in the Asset object in the previous step. Now, all you need to do is use the formats array present in the object to determine the URLs and names of the files associated with the asset. Each item in the array will have three important fields:
formatType, which lets you determine the type of the asset
root, which contains the name and URL of the primary file associated with the asset
resources, which contains details about all the secondary files associated with the asset, such as materials and textures
If you're working with the OBJ format, the primary file will be a .obj file containing vertices and faces data, and the secondary files will usually be .mtl files containing data about the materials used. The following code shows you how to determine the URLs of both the primary and secondary files:
var objFileURL:String? = null
var mtlFileURL:String? = null
var mtlFileName:String? = null
val formats = asset.getJSONArray("formats")
// Loop through all formats
for(i in 0 until formats.length()) {
val currentFormat = formats.getJSONObject(i)
// Check if current format is OBJ
if(currentFormat.getString("formatType") == "OBJ") {
// Get .obj file details
objFileURL = currentFormat.getJSONObject("root")
.getString("url")
// Get the first .mtl file details
mtlFileURL = currentFormat.getJSONArray("resources")
.getJSONObject(0)
.getString("url")
mtlFileName = currentFormat.getJSONArray("resources")
.getJSONObject(0)
.getString("relativePath")
break
}
}
In the above code, in addition to the URL of the .mtl file, we're also determining its name using the relativePath field. Doing so is important because the name is hard coded into the mtllib element of the .obj file and should not be changed.
Once you have the URLs of both the files, you can use the httpDownload() method of the Fuel library to download them. Here's how you can download them to your app's private storage directory, whose absolute path can be determined using the filesDir property:
// download and store obj file as asset.obj
objFileURL!!.httpDownload().destination { _, _ ->
File(filesDir, "asset.obj")
}.response { _, _, result ->
result.fold({}, {
Log.e("POLY", "An error occurred")
})
}
// download and store mtl file without
// changing its name
mtlFileURL!!.httpDownload().destination { _, _ ->
File(filesDir, mtlFileName)
}.response { _, _, result ->
result.fold({}, {
Log.e("POLY", "An error occurred")
})
}
5. Displaying Assets
You'll need a 3D canvas to draw the Poly asset you downloaded. To create one, you must extend the PApplet class offered by the Processing for Android library. However, a canvas created this way will, by default, support only 2D shapes. To configure it to draw 3D shapes as well, override the settings() method and pass P3D as an argument to the fullScreen() method, which also makes the canvas as large as the user's screen.
val canvas = object : PApplet() {
override fun settings() {
fullScreen(PConstants.P3D)
}
// More code here
}
Next, create a new property inside the class to represent the Poly asset as a PShape object.
var myPolyAsset: PShape? = null
To initialize the property, override the setup() method and call the loadShape() method, passing the absolute path of the .obj file you downloaded as an argument to it.
override fun setup() {
myPolyAsset = loadShape(File(filesDir, "asset.obj").absolutePath)
}
You can now start drawing on the canvas by overriding the draw() method. Inside the method, the first thing you need to do is call the background() method to ensure that you are always drawing on a blank canvas.
override fun draw() {
background(0)
// More code here
}
When drawn directly, most Poly assets look very small and inverted. You can fix this either by using a custom camera or by using canvas transformations. To keep this tutorial simple and intuitive, let's use canvas transformations.
To increase the size of the asset, use the scale() method and pass a large negative value to it. The value must be negative to make sure that the asset is flipped vertically. Optionally, you can use the translate() method to adjust its position along the X and Y axes. The following code shows you how:
scale(-50f)
translate(-4f,-14f)
You can now go ahead and draw the asset by calling the shape() method.
shape(myPolyAsset)
The canvas is currently not a part of your activity's view hierarchy. Therefore, if you try running your app now, you won't be able to see the asset. To fix this, first add a new FrameLayout widget to the activity's layout XML file.
Then, create a new PFragment instance using the canvas and point it to the FrameLayout widget.
val fragment = PFragment(canvas)
fragment.setView(canvas_holder, this)
At this point, you can run the app again to see the asset.
Conclusion
You now know how to use the Poly API to search for and download 3D assets. In this tutorial, you also learned how to render and manipulate those assets using Processing for Android.
It's worth noting that many of the assets available on Poly were created using Google Blocks, an application available for users of HTC Vive and Oculus Rift. If you own those VR headsets, do consider creating and submitting your own models.
To learn more about the Poly API, you can refer to the official documentation.
There is a huge demand these days for Android apps that offer immersive virtual reality or augmented reality experiences. As a developer, there are many different frameworks you can use to create such apps.
But, unless you are also a skilled 3D artist, how are you going to create the 3D objects you would be displaying in those apps? Are you ready to spend months learning how to work with 3D modeling programs such as Blender or Maya? If you are not, you should consider using Google Poly, an online repository containing thousands of 3D assets that come with Creative Commons licenses.
Most of the assets you can find on Poly today are low poly with simple materials. This is because the average mobile GPU is yet to become powerful enough to display 3D objects with high polygon counts in real time.
In this tutorial, I'll introduce you to the Poly API. I'll also show you how to use Processing for Android to render the 3D assets you download.
All the HTTP requests you make to the Poly API must be accompanied by an API key that belongs to you. To acquire the key, start by logging in to the Google Cloud console and navigating to the APIs dashboard.
Next, press the Enable APIs and services button, expand the Other category, and select Poly API.
You can now press the Enable button to enable the Poly API.
Once the API is enabled, a set of API keys are generated for it automatically. You can open the Credentials tab to take a look at them.
For this tutorial, you'll be needing only the Android key. Make a note of it so you can use it later.
2. Project Setup
Because Poly currently doesn't have an official toolkit for the Android platform, you'll have to work with the Poly API directly using its REST interface. By using the Fuel networking library, which is optimized for the Kotlin language, you can save a lot of time and effort. So add the following implementation dependency in the app module's build.gradle file:
To be able to display the 3D assets you download from the Poly repository, you'll also need a rendering engine. Processing for Android comes with one, so add it as another dependency.
To be able to download a Poly asset, you must know its unique ID. By using a browser, one that supports WebGL, you can easily determine the ID of any asset. It's right in the address bar.
However, if you want to allow your users to dynamically, at run time, decide which assets they want to use, you can use the assets.list REST method to determine the IDs of those assets. The method allows you to look for assets using a variety of parameters, such as keywords, categories, and 3D file formats.
For the sake of a realistic example, let's now try to find the IDs of a few assets that belong to the animals category. You are, of course, free to choose any other valid category, such as architecture, food, or people.
Before you compose your HTTP request, it's a good idea to declare your API key and the base URL of the Poly API as constants inside your activity.
companion object {
const val key = "Abcdefghabcdefgh1234567810"
const val baseURL = "https://poly.googleapis.com/v1"
}
Using the base URL, you can build the URL of the assets.list REST method as shown below:
val listURL = "$baseURL/assets"
At this point, you can create a valid HTTP GET request by calling the httpGet() method and passing your API key and the desired category as query parameters to it. Optionally, you can use the format query parameter to specify the desired format of the assets. Poly supports OBJ, FBX, TILT, and several other popular 3D formats.
Because the method runs asynchronously and its result is a JSON document, you must attach an event handler to it by using the responseJSON() method. The following code shows you how:
listURL.httpGet(listOf(
"category" to "animals",
"key" to key,
"format" to "OBJ"
)).responseJson { _, _, result ->
// More code here
}
Inside the event handler, if your request was successful, you'll have access to a list of Asset objects. The name field of each such object specifies its ID.
Additionally, each object will have fields such as displayName, license, and authorName, which you might find useful. For now, let's simply print the name and displayName of all the objects. The following code shows you how:
result.fold({
// Get assets array
val assets = it.obj().getJSONArray("assets")
// Loop through array
for(i in 0 until assets.length()) {
// Get id and displayName
val id = assets.getJSONObject(i).getString("name")
val displayName =
assets.getJSONObject(i).getString("displayName")
// Print id and displayName
Log.d("POLY", "(ID: $id) -- (NAME: $displayName)")
}
}, {
// In case of an error
Log.e("POLY", "An error occurred")
})
If you run your app now, you should be able to see the following output in the Logcat window of Android Studio.
4. Downloading Assets
Once you have the unique ID of an asset, you can directly append it to the base URL of the Poly API to create an asset URL.
// some asset id
val assetID = "assets/3yiIERrKNQr"
// its url
val assetURL = "$baseURL/$assetID"
When you make an HTTP GET request to the asset URL using the httpGet() method again, you'll get a JSON document containing just one Asset object.
assetURL.httpGet(listOf("key" to key))
.responseJson { _, _, result ->
result.fold({
val asset = it.obj()
// More code here
}, {
Log.e("POLY", "An error occurred")
})
}
As you may have noticed in the above code, this request too must have a query parameter mentioning your API key.
You've already learned how to use some of the fields present in the Asset object in the previous step. Now, all you need to do is use the formats array present in the object to determine the URLs and names of the files associated with the asset. Each item in the array will have three important fields:
formatType, which lets you determine the type of the asset
root, which contains the name and URL of the primary file associated with the asset
resources, which contains details about all the secondary files associated with the asset, such as materials and textures
If you're working with the OBJ format, the primary file will be a .obj file containing vertices and faces data, and the secondary files will usually be .mtl files containing data about the materials used. The following code shows you how to determine the URLs of both the primary and secondary files:
var objFileURL:String? = null
var mtlFileURL:String? = null
var mtlFileName:String? = null
val formats = asset.getJSONArray("formats")
// Loop through all formats
for(i in 0 until formats.length()) {
val currentFormat = formats.getJSONObject(i)
// Check if current format is OBJ
if(currentFormat.getString("formatType") == "OBJ") {
// Get .obj file details
objFileURL = currentFormat.getJSONObject("root")
.getString("url")
// Get the first .mtl file details
mtlFileURL = currentFormat.getJSONArray("resources")
.getJSONObject(0)
.getString("url")
mtlFileName = currentFormat.getJSONArray("resources")
.getJSONObject(0)
.getString("relativePath")
break
}
}
In the above code, in addition to the URL of the .mtl file, we're also determining its name using the relativePath field. Doing so is important because the name is hard coded into the mtllib element of the .obj file and should not be changed.
Once you have the URLs of both the files, you can use the httpDownload() method of the Fuel library to download them. Here's how you can download them to your app's private storage directory, whose absolute path can be determined using the filesDir property:
// download and store obj file as asset.obj
objFileURL!!.httpDownload().destination { _, _ ->
File(filesDir, "asset.obj")
}.response { _, _, result ->
result.fold({}, {
Log.e("POLY", "An error occurred")
})
}
// download and store mtl file without
// changing its name
mtlFileURL!!.httpDownload().destination { _, _ ->
File(filesDir, mtlFileName)
}.response { _, _, result ->
result.fold({}, {
Log.e("POLY", "An error occurred")
})
}
5. Displaying Assets
You'll need a 3D canvas to draw the Poly asset you downloaded. To create one, you must extend the PApplet class offered by the Processing for Android library. However, a canvas created this way will, by default, support only 2D shapes. To configure it to draw 3D shapes as well, override the settings() method and pass P3D as an argument to the fullScreen() method, which also makes the canvas as large as the user's screen.
val canvas = object : PApplet() {
override fun settings() {
fullScreen(PConstants.P3D)
}
// More code here
}
Next, create a new property inside the class to represent the Poly asset as a PShape object.
var myPolyAsset: PShape? = null
To initialize the property, override the setup() method and call the loadShape() method, passing the absolute path of the .obj file you downloaded as an argument to it.
override fun setup() {
myPolyAsset = loadShape(File(filesDir, "asset.obj").absolutePath)
}
You can now start drawing on the canvas by overriding the draw() method. Inside the method, the first thing you need to do is call the background() method to ensure that you are always drawing on a blank canvas.
override fun draw() {
background(0)
// More code here
}
When drawn directly, most Poly assets look very small and inverted. You can fix this either by using a custom camera or by using canvas transformations. To keep this tutorial simple and intuitive, let's use canvas transformations.
To increase the size of the asset, use the scale() method and pass a large negative value to it. The value must be negative to make sure that the asset is flipped vertically. Optionally, you can use the translate() method to adjust its position along the X and Y axes. The following code shows you how:
scale(-50f)
translate(-4f,-14f)
You can now go ahead and draw the asset by calling the shape() method.
shape(myPolyAsset)
The canvas is currently not a part of your activity's view hierarchy. Therefore, if you try running your app now, you won't be able to see the asset. To fix this, first add a new FrameLayout widget to the activity's layout XML file.
Then, create a new PFragment instance using the canvas and point it to the FrameLayout widget.
val fragment = PFragment(canvas)
fragment.setView(canvas_holder, this)
At this point, you can run the app again to see the asset.
Conclusion
You now know how to use the Poly API to search for and download 3D assets. In this tutorial, you also learned how to render and manipulate those assets using Processing for Android.
It's worth noting that many of the assets available on Poly were created using Google Blocks, an application available for users of HTC Vive and Oculus Rift. If you own those VR headsets, do consider creating and submitting your own models.
To learn more about the Poly API, you can refer to the official documentation.
Ionic 3 is the latest version of the popular HTML5 mobile app development framework targeted at building hybrid mobile apps.
Unlike native apps designed to work either with iOS or Android systems, a hybrid app is a small website running in a browser shell within an app that has access to the phone’s native system. Hybrid apps have many benefits over pure native apps in terms of platform support, speed of development, and access to 3rd party code.
Today I take a look at the 10 best Ionic 3 app templates available at CodeCanyon.
Developers of Ionic 3 UI Theme promise that you can make just about any app with their UI app template. With over 70 layouts and hundreds of HTML5 UI components they might just be right. Apart from offering maximum flexibility and easy customisation the app offers Google Analytics so you can track your user’s behaviour, MailChimp integration, Google Maps integration and so much more.
User Aligorkem says:
“I believe this is one of the nicest Ionic3 template in the market. The developer is also very responsive to support emails.”
Car Pooling IONIC 3 app template targets developers interested in creating a carpooling app for clients. The premise of the app is to allow car owners to offer rides to people who use the same route as them, allowing them to earn cash while driving their normal routes to work, school, etc.
The app allows car owners to add the start and end location of their route, and the days and time they drive their route. Potential passengers then send a request for a ride which the car owner can decline or accept. There’s also a chat feature which allows car owners and potential passengers to chat directly to resolve questions.
With Ionic 3 App for WooCommerce you can create your own WooCommerce mobile app for Android, iOS and Windows platforms. The app will allow users to connect to their app stores and sync categories and products in real-time and promises that your customers a easy and hassle-free shopping experience.
The app template supports 99% of payment methods out there, automatically loads shipping methods, allows customers to search products globally on the home page as well as search products within categories and much more.
User Darklink000 says:
“All the promised functions are present and work correctly, I was able to publish my app on Android and App store without major problems, clear documentation. Excellent product.”
One of the newest apps on CodeCanyon, Yoga Guide app template is perfect for developers working in the health and fitness market. The app is designed to help end-users practice yoga at their own pace and in the comfort of their own home or office. The app is easy to customise and has a clean and modern design which will allows end-users to navigate around the app easily.
Some of the templates great features include a sign-up and sign-in screen, profile page, list of yoga poses with explanations, ability to set reminder alarms, ability to save poses to favourites category and more.
If you’re looking for an Ionic 3 app template to build a social media app for yourself or a client, look no further. Firetask is a social media app template that features such advanced functions as social login, offline sync with Firebase, account management, and more.
User Rdrpart says:
"Great template to start an application, well modulated, and the code is well documented and the support is 10 Stars."
IonFullApp is a multipurpose app template which offers more than 34 useful components. The template comes in three different versions which offer various functions and screens to suit different needs.
Built with Ionic 3, Angular 5, Typescript and SASS, the Ionic 3 Restaurant app template is designed to help you build apps for restaurants, bakery, coffee shop and any other food or commerce related venture.
The app provides a good range of homepage, registration and login page layouts as well as other features, like categories, an orders page, an offers page, etc.
User Hoorida says:
“I used this starter template in some of my projects and found it to be simply one of the best Ionic templates available for any e-commerce store. Another thing I love is their technical support. Their team is very experience in these technologies and helped us to customise this app for multi-restaurant app.
Ionic 3 Toolkit app template offers developers two advantages. First it lives up to its promise of being a toolkit with its wide range of features and its modular structure, which allow users to build whatever kind of app they need quickly and easily.
Secondly, it allows you to collect data from your WordPress, Drupal, YouTube, Vimeo, Instagram and other social media accounts and add them to the content of your app as needed. Furthermore, the template makes it easy to customise the template’s default styles by changing the pre-defined colours or the default value of the SASS.
Another recent addition to CodeCanyon’s mobile app section, Hotel Room Reservation app template is created in the vein of popular hotel booking services like Booking and AirBnB.
The template helps developers to create an app that will allow end-users to search, view and book hotels in their targeted location. The app is integrated with Google Maps and PayPal for end-user convenience.
Owing to the popularity of e-commerce apps, I'll round out my list with another e-commerce app template: WooCommerce Mobile App template. In spite of being on the market for just a few months, this template has been selling steadily and is one of the best rated templates in this category.
The app allows clients to connects to their Woo Commerce store and sync categories and products in real-time. Once customers register and login, they can shop, pay for items, view order status and order history and manage their account. Useful features include a featured products page, categories, a powerful search and filter, list and grid views, ratings and reviews, etc.
User Sheen_An says:
“I love the product - very comprehensive and easy to use. More than that - the support is good with fast responses.”
Conclusion
These 10 best Ionic 3 app templates are just a small selection of the hundreds of Ionic 3 app templates we have available at CodeCanyon, so if none of them quite fits your needs, there are plenty of other great options to choose from.
And if you want to improve your skills building Ionic apps and templates, then check out some of the ever-so-useful Ionic tutorials we have on offer!
Ionic 3 is the latest version of the popular HTML5 mobile app development framework targeted at building hybrid mobile apps.
Unlike native apps designed to work either with iOS or Android systems, a hybrid app is a small website running in a browser shell within an app that has access to the phone’s native system. Hybrid apps have many benefits over pure native apps in terms of platform support, speed of development, and access to third-party code.
Today I take a look at the ten best Ionic 3 app templates available at CodeCanyon.
The developers of Ionic 3 UI Theme promise that you can make just about any app with their UI app template. With over 70 layouts and hundreds of HTML5 UI components, they might just be right. Apart from offering maximum flexibility and easy customisation, the app offers Google Analytics so you can track your user’s behaviour, MailChimp integration, Google Maps integration, and so much more.
User Aligorkem says:
“I believe this is one of the nicest Ionic3 template in the market. The developer is also very responsive to support emails.”
Car Pooling IONIC 3 app template targets developers interested in creating a carpooling app for clients. The premise of the app is to allow car owners to offer rides to people who use the same route as them, allowing them to earn cash while driving their normal routes to work, school, etc.
The app allows car owners to add the start and end location of their route, and the days and time they drive their route. Potential passengers then send a request for a ride, which the car owner can decline or accept. There’s also a chat feature which allows car owners and potential passengers to chat directly to resolve questions.
With Ionic 3 App for WooCommerce, you can create your own WooCommerce mobile app for Android, iOS and Windows platforms. The app will allow users to connect to their app stores and sync categories and products in real time, and it promises your customers an easy and hassle-free shopping experience.
The app template supports 99% of payment methods out there, automatically loads shipping methods, allows customers to search for products globally on the home page or within categories, and much more.
User Darklink000 says:
“All the promised functions are present and work correctly, I was able to publish my app on Android and App store without major problems, clear documentation. Excellent product.”
One of the newest apps on CodeCanyon, the Yoga Guide app template is perfect for developers working in the health and fitness market. The app is designed to help end users practice yoga at their own pace and in the comfort of their own home or office. The app is easy to customise and has a clean and modern design which allows end users to navigate around the app easily.
Some of the template's great features include a sign-up and sign-in screen, profile page, list of yoga poses with explanations, ability to set reminder alarms, ability to save poses to a favourites category, and more.
If you’re looking for an Ionic 3 app template to build a social media app for yourself or a client, look no further. Firetask is a social media app template that features such advanced functions as social login, offline sync with Firebase, account management, and more.
User Rdrpart says:
"Great template to start an application, well modulated, and the code is well documented and the support is 10 Stars."
IonFullApp is a multipurpose app template which offers more than 34 useful components. The template comes in three different versions which offer various functions and screens to suit different needs.
Built with Ionic 3, Angular 5, Typescript and Sass, the Ionic 3 Restaurant app template is designed to help you build apps for restaurants, bakeries, coffee shops, and any other food or commerce related venture.
The app provides a good range of homepage, registration and login page layouts as well as other features, like categories, an orders page, an offers page, etc.
User Hoorida says:
“I used this starter template in some of my projects and found it to be simply one of the best Ionic templates available for any e-commerce store. Another thing I love is their technical support. Their team is very experience in these technologies and helped us to customise this app for multi-restaurant app.
The Ionic 3 Toolkit app template offers developers two advantages. First, it lives up to its promise of being a toolkit with its wide range of features and its modular structure, which allow users to build whatever kind of app they need quickly and easily.
Secondly, it allows you to collect data from your WordPress, Drupal, YouTube, Vimeo, Instagram and other social media accounts and add them to the content of your app as needed. Furthermore, the template makes it easy to customise the template’s default styles by changing the pre-defined colours or the default value of the Sass.
Another recent addition to CodeCanyon’s mobile app section, the Hotel Room Reservation app template is created in the vein of popular hotel booking services like Booking and AirBnB.
The template helps developers to create an app that will allow end users to search, view and book hotels in their targeted location. The app is integrated with Google Maps and PayPal for user convenience.
Owing to the popularity of e-commerce apps, I'll round out my list with another e-commerce app template: WooCommerce Mobile App template. In spite of being on the market for just a few months, this template has been selling steadily and is one of the best-rated templates in this category.
The app allows clients to connect to their WooCommerce store and sync categories and products in real time. Once customers register and log in, they can shop, pay for items, view order status and order history, and manage their account. Useful features include a featured products page, categories, a powerful search and filter, list and grid views, ratings and reviews, etc.
User Sheen_An says:
“I love the product - very comprehensive and easy to use. More than that - the support is good with fast responses.”
Conclusion
These ten best Ionic 3 app templates are just a small selection of the hundreds of Ionic 3 app templates we have available at CodeCanyon, so if none of them quite fits your needs, there are plenty of other great options to choose from.
And if you want to improve your skills in building Ionic apps and templates, then check out some of the ever-so-useful Ionic tutorials we have on offer!
If you run a business you’re always looking for tools that will enable increased productivity and maximise time and without adding to energy commitments. Happily there are a number of iOS app templates available at CodeCanyon that will help businesses do just that.
App templates are ideal for businesses and developers targeting the business market because templates provide core functionality and make it easier to create one’s own specialised apps.
Many beginners use app templates as a learning tool to improve their coding skills, while others choose to upload their new app creations to iTunes for approval and inclusion in the app store.
Today we’ll look at the 10 best iOS app templates for business available at CodeCanyon.
This is the kind of app that is useful for any business person who has ever been in a meeting and wanted to share information on their mobile phone easily. This is the kind of app that users will pay good money to own!
Built with Swift, as all the other apps here are, the Wireless Device Connection app template allows developers to create an app that uses Apple’s Multipeer Connectivity Framework to connect with up to 10 iOS devices at a time. The app lets users share files, images, videos, speeches and text. This makes it a great tool not only for impressing your business colleagues with your tech-savvy, but also for sharing information easily and efficiently.
The My Business App template will appeal to small businesses which have a limited technology budget but want to use their website content to create their own app.
The beauty of this template is that it requires no coding knowledge. Using just their website’s URL, in just a few clicks users can convert their website content into an app. The template provides a number of colour themes to match your company’s brand, offers push notification and will work equally well on iPhones and iPads.
Topics is a social media app template that helps developers create a forum type app. This is great for businesses because it allows them to create their own space for customers and clients to chat about issues regarding their products or services.
Clients can browse topics or search for them, join existing conversations by posting comments or can create a new topic if it doesn’t already exist. The template also provides report features for inappropriate topics, comments and/or users. This functionality is needed for this type of app in order to pass the submissions review for Apples App Store.
Need to create your own chart on the spot in a meeting? KST Chart app template provides an easy to use and customise iOS chart library that business owners will find a handy on-the-go tool.
The template allows developers to build an app that enables the end-user to create a wide variety of charts including bar, column, line charts etc. quickly and easily.
There is a reason organiser apps are popular in the business category. They help busy people with endless demands on their time organise and prioritise tasks and keep on top of their duties large and small.
For businesses who want to create their own organiser app with features and events relevant to their specific company, there is the TODO App template which comes with a number of great features like colour themes to match the company’s brand and various viewing options like a Today task page and a Calendar page. In addition, the app is so easy to set up and customise that it doesn't requires knowledge of coding to get started.
The Secure Notes app template allows developers to create a notes app that works with Touch ID and PIN technology to ensure that notes the end-user makes on their device are private.
This is ideal for business people dealing with sensitive information who may want to make notes on the go but who also want to ensure that what they write stays private. Features include ability to organise notes by category and to add rich text in the notes.
The Eventio app template allows developers to create an event app. End-users can create, edit, and delete events and can also create custom categories to organise events.
Events can then be sorted by time, or category. The app provides an animated countdown to events which can be customised with a company’s brand.
The Video Survey & Review Tool app template is ideal for business that want to create a customer survey app that allows customers to give feedback about a product or service.
Companies are able to create their survey questions using the app and their clients then answer the questionnaire through video via the app. The app template is easy to customise and features include detailed analytics report and full integration with a cloud service of your choice.
Today many business interact with people across the globe, and for those times when you’re confronted with a speaker of a language you aren’t fluent in, a good translator app is a necessity.
The Multi-language Translator app template allows developers to create an app that is capable of translating over 200 languages. Aside from the usual language translator, it allows the end-user to translate speech to text and text to speech. Which makes conversation between speakers of different languages not only possible but far more fluid than before.
Customer service is one of those aspects of business that can definitely make or break a company and the AskIt app template aims at helping companies make it when it comes to supporting their customers.
The template allows developers to create a specialised question and answer messaging app where clients and customers can message their queries and designated employees can message an answer. What’s more, other customers have access to these questions and answers which helps reduce duplication and saves time.
Some of the great features available with this app are the ability to search questions using keywords, the ability for users to like questions and answers and the ability to report abusive contents.
Conclusion
These 10 best iOS app templates for business are just a small selection of the hundreds of iOS app templates we have available at CodeCanyon, so if none of them quite fits your needs, there are plenty of other great options to choose from.
And if you want to improve your skills building iOS apps and templates, then check out some of the ever-so-useful iOS tutorials we have on offer!
If you run a business, you’re always looking for tools that will enable increased productivity and maximise time, without adding to energy commitments. Happily there are a number of iOS app templates available at CodeCanyon that will help businesses do just that.
App templates are ideal for businesses and developers targeting the business market because the templates provide core functionality and make it easier to create your own specialised apps.
Many beginners use app templates as a learning tool to improve their coding skills, while others choose to upload their new app creations to iTunes for approval and inclusion in the app store.
Today we’ll look at the 10 best iOS app templates for business available at CodeCanyon.
This is the kind of app that is useful for any business person who has ever been in a meeting and wanted to share information on their mobile phone easily. This is the kind of app that users will pay good money to own!
Built with Swift, as all the other apps here are, the Wireless Device Connection app template allows developers to create an app that uses Apple’s Multipeer Connectivity Framework to connect with up to 10 iOS devices at a time. The app lets users share files, images, videos, speeches, and text. This makes it a great tool not only for impressing your business colleagues with your tech savvy, but also for sharing information easily and efficiently.
The My Business App template will appeal to small businesses which have a limited technology budget but want to use their website content to create their own app.
The beauty of this template is that it requires no coding knowledge. Using just their website’s URL, in just a few clicks users can convert their website content into an app. The template provides a number of colour themes to match your company’s brand, offers push notifications, and will work equally well on iPhones and iPads.
Topics is a social media app template that helps developers create a forum type app. This is great for businesses because it allows them to create their own space for customers and clients to chat about issues regarding their products or services.
Clients can browse topics or search for them, join existing conversations by posting comments, or create a new topic if it doesn’t already exist. The template also provides report features for inappropriate topics, comments, and/or users. This functionality is needed for this type of app in order to pass the submissions review for Apple's App Store.
Need to create your own chart on the spot in a meeting? KST Chart app template provides an easy-to-use, highly customisable iOS chart library that business owners will find a handy on-the-go tool.
The template allows developers to build an app that enables the end user to create a wide variety of charts such as bar, column, and line charts quickly and easily.
There is a reason organiser apps are popular in the business category. They help busy people with endless demands on their time organise and prioritise tasks and keep on top of their duties large and small.
For businesses that want to create their own organiser app with features and events relevant to their specific company, there is the TODO App template, which comes with a number of great features like colour themes to match the company’s brand and various viewing options like a Today task page and a Calendar page. In addition, the app is so easy to set up and customise that it doesn't require knowledge of coding to get started.
The Secure Notes app template allows developers to create a notes app that works with Touch ID and PIN technology to ensure that notes the end user makes on their device are private.
This is ideal for business people dealing with sensitive information who may want to make notes on the go but who also want to ensure that what they write stays private. Features include the ability to organise notes by category and to add rich text in the notes.
The Eventio app template allows developers to create an event app. End users can create, edit, and delete events and can also create custom categories to organise events.
Events can then be sorted by time or category. The app provides an animated countdown to events which can be customised with a company’s brand.
The Video Survey & Review Tool app template is ideal for businesses that want to create a customer survey app allowing customers to give feedback about a product or service.
Companies are able to create their survey questions using the app, and their clients then answer the questionnaire through video via the app. The app template is easy to customise, and its features include a detailed analytics report and full integration with a cloud service of your choice.
Today, many businesses interact with people across the globe, and for those times when you’re confronted with a speaker of a language you aren’t fluent in, a good translator app is a necessity.
The Multi-Language Translator app template allows developers to create an app that is capable of translating over 200 languages. Aside from the usual language translator, it allows the end user to translate speech to text and text to speech. This makes conversation between speakers of different languages not only possible but far more fluid than before.
Customer service is one of those aspects of business that can definitely make or break a company, and the AskIt app template aims at helping companies make it when it comes to supporting their customers.
The template allows developers to create a specialised question-and-answer messaging app where clients and customers can message their queries and designated employees can message an answer. What’s more, other customers have access to these questions and answers, which helps to reduce duplication and save time.
Some of the great features available with this app are the ability to search questions using keywords, the ability for users to like questions and answers, and the ability to report abusive contents.
Conclusion
These 10 best iOS app templates for business are just a small selection of the hundreds of iOS app templates we have available at CodeCanyon, so if none of them quite fits your needs, there are plenty of other great options to choose from.
And if you want to improve your skills building iOS apps and templates, then check out some of the ever-so-useful iOS tutorials we have on offer!
In the previous tutorial of this series, you added the ability to create, update and remove shopping lists from on your iCloud-powered shopping-list app. In the final tutorial of the series, you'll make use of CKShare to share a specific shopping list item with another user.
In this series, you have worked with private databases, as well as learning about public databases. However, until WWDC 2016, when Apple introduced CKShare, there was no proper way for apps to share data. Private databases are only available to users who are logged in, whereas public databases are designed for public content and allow anyone to view records. When using some of Apple’s very own iCloud apps, however, such as Pages, Keynote or Notes, you may have noticed by selecting the share button, the ability to invite other users to access your data.
In this post, I'll show you how to share content selectively.
In this tutorial, you'll give your app the same ability, so users can collaborate with you on a shared shopping list item.
Prerequisites
Remember that I will be using Xcode 9 and Swift 3. If you are using an older version of Xcode, then keep in mind that you are using a different version of the Swift programming language.
In this tutorial, we continue where we left off in the third tutorial of this series. You can download or clone the project from GitHub.
About Databases
Apple provides three types of databases: public, private, and shared. A public database is where you make available content that everyone can access, all within the default zone. Users don’t need even to be authenticated with iCloud to view the content, but they can't write changes.
A private database you should be familiar with already, as the shopping app has been leveraging the private database to store records that the public cannot access. Right now, all of your shopping lists are private lists.
A shared database is Apple’s shared database window into your private database, which grants users access to read or write via CKShare.
It's important to note that the shared database resides on the invited user’s account and is a portal to the owner’s private database, with the CKShare being a particular type of CKRecord that is attached to the CKRecord.
Adding the UICloudSharingController
You will first implement the UICloudSharingController and its associated UICloudSharingControllerDelegate. By far the easiest approach to sharing is to use UICloudSharingController, which takes care of providing and presenting the screens for sharing your record, inviting users, and setting permissions to your record, all with a few lines of code. According to Apple’s SDK, UICloudSharingController effortlessly enables you to:
invite people to view or collaborate on a shared record
set the access rights determining who can access the shared record (only people invited or anyone with the share link)
set general or individual permissions (read-only or read/write)
remove access from one or more participants
stop participating (if the user is a participant)
stop sharing with all participants (if the user is the owner of the shared record)
In your ListViewController.swift file, include the UICloudSharingControllerDelegate delegate:
class ListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource,
UICloudSharingControllerDelegate{
Next, add the TableView’s didSelectRowAt: method so that when a user selects a cell, it summons the sharing sheet.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = items[indexPath.row]
let share = CKShare(rootRecord: item)
if let itemName = item.object(forKey: "name") as? String {
self.itemName = item.object(forKey: "name") as? String
share[CKShareTitleKey] = "Sharing \(itemName)" as CKRecordValue?
} else {
share[CKShareTitleKey] = "" as CKRecordValue?
self.itemName = "item"
}
share[CKShareTypeKey] = "com.doronkatz.List" as CKRecordValue
prepareToShare(share: share, record: item)
}
In the method above, you create a CKShare, associating the itemCKRecord as the root record. The method then sets the share[CKShareTitleKey] and share[CKShareTypeKey] properties in preparation for displaying the shared record. The method finally calls prepareToShare, passing in the original record as well as the just-created shared record, which you will implement next:
This method creates the UICloudSharingController within a block handler, by first saving the records using the CKModifyRecordsOperation into your private cloud database, before notifying the completion handler. You also set the available permissions of the view controller, before finally presenting it to your user.
Implementing the UICloudSharingController Delegate Methods
You also need to implement the two mandatory delegate methods, itemTitleForCloudSharingController and failedToSaveShareWithError. There are other optional delegate methods that you may also opt to implement.
func cloudSharingControllerDidSaveShare(_ csc: UICloudSharingController) {
print("saved successfully")
}
func cloudSharingController(_ csc: UICloudSharingController, failedToSaveShareWithError error: Error) {
print("failed to save: \(error.localizedDescription)")
}
func itemThumbnailData(for csc: UICloudSharingController) -> Data? {
return nil //You can set a hero image in your share sheet. Nil uses the default.
}
func itemTitle(for csc: UICloudSharingController) -> String? {
return self.itemName
}
The itemTitleForCloudSharingController method matches the CKShareTitleKey property you set earlier, requesting the title to display on the invitation list. failedToSaveShareWithError notifies the delegate to handle failed sharing requests. Go ahead and build and run your app, create a record, and then select an individual record to trigger the sharing sheet.
This looks good, but your job is still not complete! You need to handle how the other users can receive and display your shared record.
Receive and Handle CKShare Records
Finally, you need to set up your app to be able to fetch and display shared records. Open your AppDelegate.swift file and add the userDidAcceptCloudKitShareWith delegate method:
UserDidAcceptCloudKitShareWith: notifies the delegate that your app has access to shared information, giving you the opportunity to handle the new content. It notifies you when your app has successfully shared an item, as well as notifying you of issues during the sharing process, through the CKAcceptSharesOperation.
In the method, you create a new instance of the AddItemViewController, calling in a new function that you create, fetchShare, along with the metadata of interest. The method finally adds the CKAcceptSharesOperation to your container. To display the record, open the AddItemViewController.swift file, and implement the fetchShare method:
func fetchShare(_ cloudKitShareMetadata: CKShareMetadata){
let op = CKFetchRecordsOperation(
recordIDs: [cloudKitShareMetadata.rootRecordID])
op.perRecordCompletionBlock = { record, _, error in
guard error == nil, record != nil else{
print(“error \(error?.localizedDescription ?? “”)”)
return
}
DispatchQueue.main.async {
self.item = record
}
}
op.fetchRecordsCompletionBlock = { _, error in
guard error != nil else{
print(“error \(error?.localizedDescription ?? “”)”)
return
}
}
CKContainer.default().sharedCloudDatabase.add(op)
}
In this final method, you are fetching the shared record and assigning it to your global variable self.item, before finally persisting the operation on your user’s own sharedCloudDatabase database.
Conclusion
In this tutorial, you learned how easy it is, with just a few lines of code, to add the ability to share a subset of your private records with other users and have them collaborate on that very same data. Through the sheer power and simplicity of CKShare, you can select a shopping list item, invite a user to access your item via the UICloudSharingController sheet, and manage user access and permissions.
In addition to sharing your records with users, you also learned how to receive and display data within their apps, as well as storing the record in their shared database.
As you can see, CloudKit provides a lot of convenience and robustness, all within Apple’s ecosystem. Authenticating is virtually invisible, without the need for users to log in with an email or password, and synchronizing is real-time. Beyond this series, I encourage you to explore more advanced topics, such as synchronizing CloudKit with Core Data or creating your caching logic to manage offline data marshaling.
In the previous tutorial of this series, you added the ability to create, update and remove shopping lists from on your iCloud-powered shopping-list app. In the final tutorial of the series, you'll make use of CKShare to share a specific shopping list item with another user.
In this series, you have worked with private databases, as well as learning about public databases. However, until WWDC 2016, when Apple introduced CKShare, there was no proper way for apps to share data. Private databases are only available to users who are logged in, whereas public databases are designed for public content and allow anyone to view records. When using some of Apple’s very own iCloud apps, however, such as Pages, Keynote or Notes, you may have noticed by selecting the share button, the ability to invite other users to access your data.
In this post, I'll show you how to share content selectively.
In this tutorial, you'll give your app the same ability, so users can collaborate with you on a shared shopping list item.
Prerequisites
Remember that I will be using Xcode 9 and Swift 3. If you are using an older version of Xcode, then keep in mind that you are using a different version of the Swift programming language.
In this tutorial, we continue where we left off in the third tutorial of this series. You can download or clone the project from GitHub.
About Databases
Apple provides three types of databases: public, private, and shared. A public database is where you make available content that everyone can access, all within the default zone. Users don’t need even to be authenticated with iCloud to view the content, but they can't write changes.
A private database you should be familiar with already, as the shopping app has been leveraging the private database to store records that the public cannot access. Right now, all of your shopping lists are private lists.
A shared database is Apple’s shared database window into your private database, which grants users access to read or write via CKShare.
It's important to note that the shared database resides on the invited user’s account and is a portal to the owner’s private database, with the CKShare being a particular type of CKRecord that is attached to the CKRecord.
Adding the UICloudSharingController
You will first implement the UICloudSharingController and its associated UICloudSharingControllerDelegate. By far the easiest approach to sharing is to use UICloudSharingController, which takes care of providing and presenting the screens for sharing your record, inviting users, and setting permissions to your record, all with a few lines of code. According to Apple’s SDK, UICloudSharingController effortlessly enables you to:
invite people to view or collaborate on a shared record
set the access rights determining who can access the shared record (only people invited or anyone with the share link)
set general or individual permissions (read-only or read/write)
remove access from one or more participants
stop participating (if the user is a participant)
stop sharing with all participants (if the user is the owner of the shared record)
In your ListViewController.swift file, include the UICloudSharingControllerDelegate delegate:
class ListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource,
UICloudSharingControllerDelegate{
Next, add the TableView’s didSelectRowAt: method so that when a user selects a cell, it summons the sharing sheet.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = items[indexPath.row]
let share = CKShare(rootRecord: item)
if let itemName = item.object(forKey: "name") as? String {
self.itemName = item.object(forKey: "name") as? String
share[CKShareTitleKey] = "Sharing \(itemName)" as CKRecordValue?
} else {
share[CKShareTitleKey] = "" as CKRecordValue?
self.itemName = "item"
}
share[CKShareTypeKey] = "com.doronkatz.List" as CKRecordValue
prepareToShare(share: share, record: item)
}
In the method above, you create a CKShare, associating the itemCKRecord as the root record. The method then sets the share[CKShareTitleKey] and share[CKShareTypeKey] properties in preparation for displaying the shared record. The method finally calls prepareToShare, passing in the original record as well as the just-created shared record, which you will implement next:
This method creates the UICloudSharingController within a block handler, by first saving the records using the CKModifyRecordsOperation into your private cloud database, before notifying the completion handler. You also set the available permissions of the view controller, before finally presenting it to your user.
Implementing the UICloudSharingController Delegate Methods
You also need to implement the two mandatory delegate methods, itemTitleForCloudSharingController and failedToSaveShareWithError. There are other optional delegate methods that you may also opt to implement.
func cloudSharingControllerDidSaveShare(_ csc: UICloudSharingController) {
print("saved successfully")
}
func cloudSharingController(_ csc: UICloudSharingController, failedToSaveShareWithError error: Error) {
print("failed to save: \(error.localizedDescription)")
}
func itemThumbnailData(for csc: UICloudSharingController) -> Data? {
return nil //You can set a hero image in your share sheet. Nil uses the default.
}
func itemTitle(for csc: UICloudSharingController) -> String? {
return self.itemName
}
The itemTitleForCloudSharingController method matches the CKShareTitleKey property you set earlier, requesting the title to display on the invitation list. failedToSaveShareWithError notifies the delegate to handle failed sharing requests. Go ahead and build and run your app, create a record, and then select an individual record to trigger the sharing sheet.
This looks good, but your job is still not complete! You need to handle how the other users can receive and display your shared record.
Receive and Handle CKShare Records
Finally, you need to set up your app to be able to fetch and display shared records. Open your AppDelegate.swift file and add the userDidAcceptCloudKitShareWith delegate method:
UserDidAcceptCloudKitShareWith: notifies the delegate that your app has access to shared information, giving you the opportunity to handle the new content. It notifies you when your app has successfully shared an item, as well as notifying you of issues during the sharing process, through the CKAcceptSharesOperation.
In the method, you create a new instance of the AddItemViewController, calling in a new function that you create, fetchShare, along with the metadata of interest. The method finally adds the CKAcceptSharesOperation to your container. To display the record, open the AddItemViewController.swift file, and implement the fetchShare method:
func fetchShare(_ cloudKitShareMetadata: CKShareMetadata){
let op = CKFetchRecordsOperation(
recordIDs: [cloudKitShareMetadata.rootRecordID])
op.perRecordCompletionBlock = { record, _, error in
guard error == nil, record != nil else{
print(“error \(error?.localizedDescription ?? “”)”)
return
}
DispatchQueue.main.async {
self.item = record
}
}
op.fetchRecordsCompletionBlock = { _, error in
guard error != nil else{
print(“error \(error?.localizedDescription ?? “”)”)
return
}
}
CKContainer.default().sharedCloudDatabase.add(op)
}
In this final method, you are fetching the shared record and assigning it to your global variable self.item, before finally persisting the operation on your user’s own sharedCloudDatabase database.
Conclusion
In this tutorial, you learned how easy it is, with just a few lines of code, to add the ability to share a subset of your private records with other users and have them collaborate on that very same data. Through the sheer power and simplicity of CKShare, you can select a shopping list item, invite a user to access your item via the UICloudSharingController sheet, and manage user access and permissions.
In addition to sharing your records with users, you also learned how to receive and display data within their apps, as well as storing the record in their shared database.
As you can see, CloudKit provides a lot of convenience and robustness, all within Apple’s ecosystem. Authenticating is virtually invisible, without the need for users to log in with an email or password, and synchronizing is real-time. Beyond this series, I encourage you to explore more advanced topics, such as synchronizing CloudKit with Core Data or creating your caching logic to manage offline data marshaling.
If you want to develop Android apps, you need to understand how to create and use Activities. Our new course, Android Fundamentals: Activities, is the perfect introduction to this essential Android concept.
What You’ll Learn
In this course, Annapurna Agrawal will give you a thorough introduction to Android Activities, with all the basic concepts explained.
You'll learn to navigate between Activities, passing data from one to the other. You will also learn about the Activity lifecycle and how to handle configuration changes in case of screen rotation.
Watch the Introduction
Take the Course
You can take our new course straight away with a subscription to Envato Elements. For a single low monthly fee, you get access not only to this course, but also to our growing library of over 1,000 video courses and industry-leading eBooks on Envato Tuts+.
Plus you now get unlimited downloads from the huge Envato Elements library of 650,000+ creative assets. Create with unique fonts, photos, graphics and templates, and deliver better projects faster.
If you want to develop Android apps, you need to understand how to create and use Activities. Our new course, Android Fundamentals: Activities, is the perfect introduction to this essential Android concept.
What You’ll Learn
In this course, Annapurna Agrawal will give you a thorough introduction to Android Activities, with all the basic concepts explained.
You'll learn to navigate between Activities, passing data from one to the other. You will also learn about the Activity lifecycle and how to handle configuration changes in case of screen rotation.
Watch the Introduction
Take the Course
You can take our new course straight away with a subscription to Envato Elements. For a single low monthly fee, you get access not only to this course, but also to our growing library of over 1,000 video courses and industry-leading eBooks on Envato Tuts+.
Plus you now get unlimited downloads from the huge Envato Elements library of 650,000+ creative assets. Create with unique fonts, photos, graphics and templates, and deliver better projects faster.
Any sort of app that involves getting your users from one place to another needs good map integration. Here are the 10 best iOS app templates with map integration to be found at CodeCanyon.
Whether you’re interested in building a store, restaurant or city guide app or creating a booking app, take a look below to see some of the best iOS templates with map integration currently available.
The Doctor Finder app template helps developers to create an app that will make it easier for users to find medical service providers like doctors, hospitals and pharmacies in any location. Users can sort doctors by speciality, by name, city, or proximity. They can also check a doctor’s, hospital’s or pharmacy’s profiles and ratings.
The Store Finder IOS app template allows you to create an app to help users find stores near them. The app also lets store owners add their store to the app’s listing. Once users identify the store they want, they can go directly to the store’s details for more information like phone and email address, reviews, and ratings.
Other great features:
nearest results by default
store image gallery
different categories of stores
displays available ratings and reviews for stores
and more
User hayasuliman says:
“Great team to help you if you have any problem with app.”
The ideal app for travellers, the appyMap app template helps developers create their own "points of interest" app by entering the name, coordinates and description of the location and selecting an accompanying photo. These points of interest can be grouped by categories, and app owners can lock certain sections of the app and make them available only via in-app purchase.
Other great features:
auto layout
AdMob
easy configuration
directions and estimated time of arrival at location
and more
User esito says:
“I’ve never found such clean and clear code in any other template I’ve bought.”
The Food Delivery app template allows end users to find a restaurant of their choice nearby using the integrated map. They can then check details like whether the restaurant is open or closed and what kind of cuisine is on offer, read the menu and item prices, order a meal, and have it delivered to their door.
Other great features:
restaurants listed by user rating
ability to sort restaurant list by radius or by city
Want to create an app similar to the hit Pokémon app? If so, Catch The Monsters app template may be perfect for you. Users of the app search for the monsters around their area. When they get close enough, they can trace the route to the monsters using the integrated map feature. When they find and catch the monsters, they earn points with the goal of getting onto the top 10 leaderboard.
Other great features:
AdMob banners
push notifications
stats and caught monster list
and more
User TylerDeviGrey says:
“Great code quality. Easy to edit and customer support is very fast and effective.”
AroundMe is a universal app template that allows users to enlist the help of other app users to find popular nearby restaurants, stores, cafes, etc. Users can post recommendations, comment on recommendations, ask questions, and mark suggestions as their favourite.
Other great features:
post questions with image and geolocation attachments
push notifications for favourites, comments, and thank yous
AdForest is a classified ads app template for developers or business owners who want to create an app to manage product listings for an ad posting business. The template includes a multi-currency feature and radius search, and the Google Maps integration allows users to get directions to the seller.
Other great features:
bad word filter
push notifications
AdMob integrated
Google Analytics
and more
User everettbfp says:
“The support, design, everything about this item and the scripts bundle team, are all 5 STAR.”
Imagine an app that gives users instant access to all the popular spots in a city: cinemas, museums, stores, restaurants, post office, etc. That's exactly what the app you can build with the City Directory template will allow users to do. Not only will users get access to detail about the places on the app like phone number, email, address, website and opening times, but with just a few clicks they will also be able to access user reviews and ratings.
Restaurants Finder app template is one of the highest rated apps in this category on CodeCanyon, and as its name suggests, it enables developers to create a database of restaurants which end users of the app can use to find a restaurant of their choice. The nearest restaurants are displayed by default, and map integration provides directions from the user’s location to the restaurant.
Other great features:
map automatically zooms in or out to display all pins
Our final app template is City App, a native swift iOS application template for developers who want to create a city guide showing interesting places around a city. The main difference between City App and the City Directory featured above is that this template uses augmented reality technology to search a place from the user’s location. The app is also integrated with Firebase so it can work in offline mode.
Other great features:
integrated with Firebase authentication, database, storage, and crash reporting
AdMob supported
Google Maps integrated
and more
User vivo231 says:
“Amazing developer! Clean code! Easy to follow and was super quick to respond on issues.”
Conclusion
These top 10 iOS map app templates are just a small selection of the app templates with map integration that we have available at CodeCanyon, so if none of them quite fits your needs, there are plenty of other great options to choose from.
And if you want to improve your skills building iOS apps and templates, then check out some of our other posts on iOS app development!
Any sort of app that involves getting your users from one place to another needs good map integration. Here are the 10 best iOS app templates with map integration to be found at CodeCanyon.
Whether you’re interested in building a store, restaurant or city guide app or creating a booking app, take a look below to see some of the best iOS templates with map integration currently available.
The Doctor Finder app template helps developers to create an app that will make it easier for users to find medical service providers like doctors, hospitals and pharmacies in any location. Users can sort doctors by speciality, by name, city, or proximity. They can also check a doctor’s, hospital’s or pharmacy’s profiles and ratings.
The Store Finder IOS app template allows you to create an app to help users find stores near them. The app also lets store owners add their store to the app’s listing. Once users identify the store they want, they can go directly to the store’s details for more information like phone and email address, reviews, and ratings.
Other great features:
nearest results by default
store image gallery
different categories of stores
displays available ratings and reviews for stores
and more
User hayasuliman says:
“Great team to help you if you have any problem with app.”
The ideal app for travellers, the appyMap app template helps developers create their own "points of interest" app by entering the name, coordinates and description of the location and selecting an accompanying photo. These points of interest can be grouped by categories, and app owners can lock certain sections of the app and make them available only via in-app purchase.
Other great features:
auto layout
AdMob
easy configuration
directions and estimated time of arrival at location
and more
User esito says:
“I’ve never found such clean and clear code in any other template I’ve bought.”
The Food Delivery app template allows end users to find a restaurant of their choice nearby using the integrated map. They can then check details like whether the restaurant is open or closed and what kind of cuisine is on offer, read the menu and item prices, order a meal, and have it delivered to their door.
Other great features:
restaurants listed by user rating
ability to sort restaurant list by radius or by city
Want to create an app similar to the hit Pokémon app? If so, Catch The Monsters app template may be perfect for you. Users of the app search for the monsters around their area. When they get close enough, they can trace the route to the monsters using the integrated map feature. When they find and catch the monsters, they earn points with the goal of getting onto the top 10 leaderboard.
Other great features:
AdMob banners
push notifications
stats and caught monster list
and more
User TylerDeviGrey says:
“Great code quality. Easy to edit and customer support is very fast and effective.”
AroundMe is a universal app template that allows users to enlist the help of other app users to find popular nearby restaurants, stores, cafes, etc. Users can post recommendations, comment on recommendations, ask questions, and mark suggestions as their favourite.
Other great features:
post questions with image and geolocation attachments
push notifications for favourites, comments, and thank yous
AdForest is a classified ads app template for developers or business owners who want to create an app to manage product listings for an ad posting business. The template includes a multi-currency feature and radius search, and the Google Maps integration allows users to get directions to the seller.
Other great features:
bad word filter
push notifications
AdMob integrated
Google Analytics
and more
User everettbfp says:
“The support, design, everything about this item and the scripts bundle team, are all 5 STAR.”
Imagine an app that gives users instant access to all the popular spots in a city: cinemas, museums, stores, restaurants, post office, etc. That's exactly what the app you can build with the City Directory template will allow users to do. Not only will users get access to detail about the places on the app like phone number, email, address, website and opening times, but with just a few clicks they will also be able to access user reviews and ratings.
Restaurants Finder app template is one of the highest rated apps in this category on CodeCanyon, and as its name suggests, it enables developers to create a database of restaurants which end users of the app can use to find a restaurant of their choice. The nearest restaurants are displayed by default, and map integration provides directions from the user’s location to the restaurant.
Other great features:
map automatically zooms in or out to display all pins
Our final app template is City App, a native swift iOS application template for developers who want to create a city guide showing interesting places around a city. The main difference between City App and the City Directory featured above is that this template uses augmented reality technology to search a place from the user’s location. The app is also integrated with Firebase so it can work in offline mode.
Other great features:
integrated with Firebase authentication, database, storage, and crash reporting
AdMob supported
Google Maps integrated
and more
User vivo231 says:
“Amazing developer! Clean code! Easy to follow and was super quick to respond on issues.”
Conclusion
These top 10 iOS map app templates are just a small selection of the app templates with map integration that we have available at CodeCanyon, so if none of them quite fits your needs, there are plenty of other great options to choose from.
And if you want to improve your skills building iOS apps and templates, then check out some of our other posts on iOS app development!
Over the past several years, we have seen HTML5 used to create many great online solutions. We have also seen it used to create some great fun! With the vanishing of Flash, HTML5 quickly became the universal platform to create browser-based games for both desktop and mobile devices.
Here are the 20 best HTML5 game templates and engines of 2018. Whether you already have a game concept to build or would like a fun way to learn more about making mobile games, download the source code today and get started making something awesome.
Built with Construct 2, it supports both desktop and mobile devices, and it is easy to change out the elements and edit the source code to make your own game.
With five different smilies to choose from, the Smiley Ball adventure will take you through 20 levels full of spikes and obstacles to avoid before reaching your goal.
You're given a clue and a jumbled mess of letters. With the clue in mind, rearrange the letters until you've got the word right as the timer counts down.
That's how the Rearrange Letters HTML 5 game works.
“Rearrange Letters is an HTML5 game where you can arrange the letters and make the right word with the given description as a clue.”
Features include:
customize the text, game mode, and timer
social media high score sharer
mouse and touch control
1024x768 resolution
and more
With plenty of customizations and built using CreateJS, you can reimagine Rearrange Letters any way you like.
The Rolling Cheese physics game has a tiny hero: help this little hungry mouse get the cheese by rolling it through obstacle courses to reach the mouse.
Features include:
fully customizable with included source code
optimized for both mobile and desktop
developed in HTML5, JavaScript, and CreateJS
and more
Rolling Cheese is one of the few games in this list not designed using Construct or a similar framework.
Going through this list may inspire you to reskin, tweak, or build something completely new—using one of these HTML5 games as a template towards success. Whether you publish a game arcade on your website, earn revenue with advertising, or would like to dig deeper in HTML5 using game templates, you're sure to find something you're looking for from the Envato Market.
Over the past several years, we have seen HTML5 used to create many great online solutions. We have also seen it used to create some great fun! With the vanishing of Flash, HTML5 quickly became the universal platform to create browser-based games for both desktop and mobile devices.
Here are the 20 best HTML5 game templates and engines of 2018. Whether you already have a game concept to build or would like a fun way to learn more about making mobile games, download the source code today and get started making something awesome.
Built with Construct 2, it supports both desktop and mobile devices, and it is easy to change out the elements and edit the source code to make your own game.
With five different smilies to choose from, the Smiley Ball adventure will take you through 20 levels full of spikes and obstacles to avoid before reaching your goal.
You're given a clue and a jumbled mess of letters. With the clue in mind, rearrange the letters until you've got the word right as the timer counts down.
That's how the Rearrange Letters HTML 5 game works.
“Rearrange Letters is an HTML5 game where you can arrange the letters and make the right word with the given description as a clue.”
Features include:
customize the text, game mode, and timer
social media high score sharer
mouse and touch control
1024x768 resolution
and more
With plenty of customizations and built using CreateJS, you can reimagine Rearrange Letters any way you like.
The Rolling Cheese physics game has a tiny hero: help this little hungry mouse get the cheese by rolling it through obstacle courses to reach the mouse.
Features include:
fully customizable with included source code
optimized for both mobile and desktop
developed in HTML5, JavaScript, and CreateJS
and more
Rolling Cheese is one of the few games in this list not designed using Construct or a similar framework.
Going through this list may inspire you to reskin, tweak, or build something completely new—using one of these HTML5 games as a template towards success. Whether you publish a game arcade on your website, earn revenue with advertising, or would like to dig deeper in HTML5 using game templates, you're sure to find something you're looking for from the Envato Market.