Mobile users have come to expect the UI consistency and performance that can only come from native apps. Building a feature-rich iOS app with an elegant user interface can be challenging, though. Fortunately, by using an app template, you can save substantial amounts of time and effort.
CodeCanyon has a huge collection of Swift language iOS app templates. If you don't know what an app template is, it is basically a pre-built application with a lot of the core functionality already implemented for you. You can then easily customise and add to the template's code to create the final app you want.
Depending on the license that you purchase for a template, you can either use it as a learning tool or even turn it into a real product on the App Store. No matter what your app's requirements are, there's a good chance that CodeCanyon has a template for it.
In this article, I'll show you 15 of my favorite Swift app templates available on CodeCanyon.
This template gives you an app which displays various recipes based on categories, including a user-customisable Favourites category. The screen for viewing the details of a recipe supports multiple images, sharing, and smooth transitions.
All the recipe data is stored in an XML file which can be easily edited or replaced with data loaded from a server. This template also includes quite a few extra features, including a shopping list, Google AdMob integration, push notifications, and a sliding menu on the left side of the app.
appyMap is an excellent app for browsing different locations and points of interest near the user's current location. The template allows you to split up points of interest into various groups which, if you want, can easily be locked behind an in-app purchase. appyMap also lets you choose between using either Apple's CloudKit or a local plist file for your data. Additionally, this template features AdMob integration if you want to use it.
appyQuote is a template made by the same developer as appyMap, but it groups and displays quotes instead of locations. This template contains all of its quotes in a single plist which you can easily edit with your own content. appyQuote also comes with AdMob already implemented, a 12-page user guide, and a Sketch file with 24 icons.
SuperView is a template designed to let web developers easily create a native iOS container for their website. It provides some basic functionality, including a toolbar with back, forward and refresh buttons, but it keeps your website front and centre.
This template also adds a lot of extra features you can take advantage of in your app, including Firebase or OneSignal push notifications, GPS support, social network login, Google AdMob, and support for right-to-left languages such as Arabic.
FIVES is a word game where you need to make as many words as you can from a set of five letters before running out of time. It features support for multiple languages, Game Center leaderboards, a PDF user guide, and a Photoshop PSD file for graphics.
This template is for an endless runner game built with Apple's SpriteKit framework. Jetpack Challenge includes not only the mechanics for the game, but also varying backgrounds, difficulty levels, and enemy types. This template also includes AdMob, leaderboards, and push notifications integration.
As the name implies, Spotimusic is an app template which offers very similar functionality to Spotify and Apple Music. In addition to basic playlist creation and music playback, Spotimusic offers more advanced features, including background playback, an audio equaliser, and the ability to download tracks for offline usage.
The other major selling point of this template is that it includes a fully functional and ready-to-deploy server back-end for user account management and storing tracks. All you need to do is set up a server hosting method and deploy the back-end template.
This template is actually a cloned version of a popular game in the App Store, 2 Cars. The aim of the game is to make it as far as possible while catching circles and avoiding squares with the cars. The game has been built with SpriteKit and includes extra features such as Game Center integration as well as Facebook and Twitter sharing.
2 Cars Lane is also very easily customisable, with a lot of properties being contained within a single Constants.swift code file.
iOS Mobile Shop is, as its name suggests, a template for a mobile store application. This app defines all of its products in a local XML file which you can easily customise or replace with a file loaded from a server. The app organises products into categories, allows the user to search for products, and takes care of managing the user's cart.
The template is currently set up with push notifications through OneSignal and will also send an email directly to you when an order is made. From here, it is up to you to organise payment, shipping, etc.
Flappy Wigs is a completely ready-to-go Flappy Bird clone which you can purchase. In addition to all the gameplay elements, this template includes interstitial and banner ads through Google AdMob, Game Center leaderboards, and Google Analytics. The game is also easily customisable and has the option for an in-app purchase to remove all ads.
Cool Voice Recorder is a unique app template which allows users to record and manipulate their voice. The template includes many preset effects, but you can also make your own by adjusting the pitch and speed of the audio recording.
In addition to all the voice effects, Cool Voice Recorder includes extra features such as AdMob integration and sharing of recordings via email as well as instructions on how to easily reskin the app.
SpeedoMap is an app template with three main screens:
Speedometer: uses the device's GPS to determine its current speed in miles per hour, kilometres per hour, or knots. This screen also shows the user's current location coordinates and the street they're on.
Tracker: draws the path the user takes onto a map.
Weather: displays all relevant information for the user's current location, including temperature, air pressure, and humidity.
This template is easily customisable and also supports Spanish and German in addition to English.
Dot2Dot is a universal game template with an interesting gameplay mechanic. The aim of the game is to match falling dots with circles at the bottom of the screen to try to get the highest score possible. Dot2Dot also includes many extra features, including AdMob integration, Game Center leaderboards, and social network sharing of high scores.
Gif Factory is a universal app template which converts photos and videos into animated GIF images. In addition to just doing the GIF conversion, the app includes more advanced editing features such as frame reordering, loop count, and overall dimensions. Gif Factory also includes AdMob integration, excellent in-app animations, and easy sharing of the created GIFs.
30s Math is a template for an Apple Watch game. In this game, you pick an operator to work with (+, -, ×, or ÷) and then answer as many problems as you can using this operator within a time limit. This template is particularly interesting because the game runs entirely on the Apple Watch and has no dependency on an iPhone.
Conclusion
These 15 templates are just some of the many available on CodeCanyon. There are a lot more great templates that weren't included in this article, so I encourage you to have a look and see what else you can find.
If you want to learn more about iOS app development or templates, check out some of our other posts here on Envato Tuts+!
Mobile users have come to expect the UI consistency and performance that can only come from native apps. Building a feature-rich iOS app with an elegant user interface can be challenging, though. Fortunately, by using an app template, you can save substantial amounts of time and effort.
CodeCanyon has a huge collection of Swift language iOS app templates. If you don't know what an app template is, it is basically a pre-built application with a lot of the core functionality already implemented for you. You can then easily customise and add to the template's code to create the final app you want.
Depending on the license that you purchase for a template, you can either use it as a learning tool or even turn it into a real product on the App Store. No matter what your app's requirements are, there's a good chance that CodeCanyon has a template for it.
In this article, I'll show you 15 of my favorite Swift app templates available on CodeCanyon.
This template gives you an app which displays various recipes based on categories, including a user-customisable Favourites category. The screen for viewing the details of a recipe supports multiple images, sharing, and smooth transitions.
All the recipe data is stored in an XML file which can be easily edited or replaced with data loaded from a server. This template also includes quite a few extra features, including a shopping list, Google AdMob integration, push notifications, and a sliding menu on the left side of the app.
appyMap is an excellent app for browsing different locations and points of interest near the user's current location. The template allows you to split up points of interest into various groups which, if you want, can easily be locked behind an in-app purchase. appyMap also lets you choose between using either Apple's CloudKit or a local plist file for your data. Additionally, this template features AdMob integration if you want to use it.
appyQuote is a template made by the same developer as appyMap, but it groups and displays quotes instead of locations. This template contains all of its quotes in a single plist which you can easily edit with your own content. appyQuote also comes with AdMob already implemented, a 12-page user guide, and a Sketch file with 24 icons.
SuperView is a template designed to let web developers easily create a native iOS container for their website. It provides some basic functionality, including a toolbar with back, forward and refresh buttons, but it keeps your website front and centre.
This template also adds a lot of extra features you can take advantage of in your app, including Firebase or OneSignal push notifications, GPS support, social network login, Google AdMob, and support for right-to-left languages such as Arabic.
FIVES is a word game where you need to make as many words as you can from a set of five letters before running out of time. It features support for multiple languages, Game Center leaderboards, a PDF user guide, and a Photoshop PSD file for graphics.
This template is for an endless runner game built with Apple's SpriteKit framework. Jetpack Challenge includes not only the mechanics for the game, but also varying backgrounds, difficulty levels, and enemy types. This template also includes AdMob, leaderboards, and push notifications integration.
As the name implies, Spotimusic is an app template which offers very similar functionality to Spotify and Apple Music. In addition to basic playlist creation and music playback, Spotimusic offers more advanced features, including background playback, an audio equaliser, and the ability to download tracks for offline usage.
The other major selling point of this template is that it includes a fully functional and ready-to-deploy server back-end for user account management and storing tracks. All you need to do is set up a server hosting method and deploy the back-end template.
This template is actually a cloned version of a popular game in the App Store, 2 Cars. The aim of the game is to make it as far as possible while catching circles and avoiding squares with the cars. The game has been built with SpriteKit and includes extra features such as Game Center integration as well as Facebook and Twitter sharing.
2 Cars Lane is also very easily customisable, with a lot of properties being contained within a single Constants.swift code file.
iOS Mobile Shop is, as its name suggests, a template for a mobile store application. This app defines all of its products in a local XML file which you can easily customise or replace with a file loaded from a server. The app organises products into categories, allows the user to search for products, and takes care of managing the user's cart.
The template is currently set up with push notifications through OneSignal and will also send an email directly to you when an order is made. From here, it is up to you to organise payment, shipping, etc.
Flappy Wigs is a completely ready-to-go Flappy Bird clone which you can purchase. In addition to all the gameplay elements, this template includes interstitial and banner ads through Google AdMob, Game Center leaderboards, and Google Analytics. The game is also easily customisable and has the option for an in-app purchase to remove all ads.
Cool Voice Recorder is a unique app template which allows users to record and manipulate their voice. The template includes many preset effects, but you can also make your own by adjusting the pitch and speed of the audio recording.
In addition to all the voice effects, Cool Voice Recorder includes extra features such as AdMob integration and sharing of recordings via email as well as instructions on how to easily reskin the app.
SpeedoMap is an app template with three main screens:
Speedometer: uses the device's GPS to determine its current speed in miles per hour, kilometres per hour, or knots. This screen also shows the user's current location coordinates and the street they're on.
Tracker: draws the path the user takes onto a map.
Weather: displays all relevant information for the user's current location, including temperature, air pressure, and humidity.
This template is easily customisable and also supports Spanish and German in addition to English.
Dot2Dot is a universal game template with an interesting gameplay mechanic. The aim of the game is to match falling dots with circles at the bottom of the screen to try to get the highest score possible. Dot2Dot also includes many extra features, including AdMob integration, Game Center leaderboards, and social network sharing of high scores.
Gif Factory is a universal app template which converts photos and videos into animated GIF images. In addition to just doing the GIF conversion, the app includes more advanced editing features such as frame reordering, loop count, and overall dimensions. Gif Factory also includes AdMob integration, excellent in-app animations, and easy sharing of the created GIFs.
30s Math is a template for an Apple Watch game. In this game, you pick an operator to work with (+, -, ×, or ÷) and then answer as many problems as you can using this operator within a time limit. This template is particularly interesting because the game runs entirely on the Apple Watch and has no dependency on an iPhone.
Conclusion
These 15 templates are just some of the many available on CodeCanyon. There are a lot more great templates that weren't included in this article, so I encourage you to have a look and see what else you can find.
If you want to learn more about iOS app development or templates, check out some of our other posts here on Envato Tuts+!
In the previous lesson, you created a simple Swift project in Xcode, a basic to-do application. In this installment of Swift From Scratch, we're going to add the ability to create to-do items. Along the way, you learn about actions, delegation, and properties.
Prerequisites
If you'd like to follow along with me, then make sure that you have Xcode 8.3.2 or higher installed on your machine. You can download Xcode 8.3.2 from Apple's App Store.
1. Adding Items
At the end of this lesson, the user will be able to add new to-do items by tapping a button in the navigation bar, presenting a view with a text field and a button. Let's start by creating the view controller that will handle adding new to-do items, the AddItemViewController class.
Step 1: Create AddItemViewController
Choose New > File... from Xcode's File menu and select the Cocoa Touch Class template from the list of iOS > Source templates.
Name the class AddItemViewController and make sure it inherits from UIViewController. Double-check that Language is set to Swift and that Also create XIB file is unchecked.
Tell Xcode where you'd like to save the file for the AddItemViewController class and click Create.
Step 2: Add Outlets and Actions
Before we create the user interface of the AddItemViewController class, we need to create an outlet for the text field and two actions, one for a cancel button in the navigation bar and another one for the create button below the text field.
Adding an outlet should be familiar by now. Create an outlet in the AddItemViewController class and name it textField as shown below.
Creating an action is very similar to creating an instance method. In fact, the @IBAction attribute is nothing more than a hint for Interface Builder. By prefixing a method with the @IBAction attribute, we ensure that Interface Builder is aware of the method, which enables us to connect it in the storyboard. We'll leave the bodies of both actions empty for now.
Open Main.storyboard in the Project Navigator and drag a View Controller from the Object Library on the right. With the view controller selected, open the Identity Inspector on the right and set Custom Class > Class to AddItemViewController.
To add a navigation bar to the add item view, select the Add Item View Controller and choose Embed In > Navigation Controller from the Editor menu. This will make the Add Item View Controller the root view controller of a navigation controller.
The next step is to add a bar button item to the navigation bar of the View Controller—not the Add Item View Controller—and set its Identifier to Add in the Attributes Inspector.
When the user taps the Add button, the Add Item View Controller should be presented modally. To accomplish this, press the Control key and drag from the Add button to the Navigation Controller, selecting Present Modally from the menu that pops up. This will create a segue from the Add Item View Controller to the new Navigation Controller.
Drag a text field and a button from the Object Library and add them to the Add Item View Controller scene. Select the Add Item View Controller and connect the textField outlet with the text field and the create(_:) action with the button. The create(_:) action should be triggered when the Touch Up Inside event is fired. Change the title of the button to Create and add the necessary layout constraints to the text field and button.
To finish the user interface, add a bar button item to the top left of the navigation bar of the Add Item View Controller and set its Identifier to Cancel. With the Add Item View Controller selected, open the Connections Inspector and connect the cancel(_:) action to the Cancel button.
Build and run the application by pressing Command-R to verify that everything is hooked up correctly.
2. Implementing a Delegate Protocol
When the user taps the Create button to add a to-do item, the add item view controller needs to notify the view controller. Delegation is a perfect solution for this scenario. The process is pretty simple.
We create a delegate protocol the ViewController class conforms to. When the AddItemViewController instance is created—when the user taps the Add button—the ViewController object is set as the delegate of the AddItemViewController instance, enabling the latter to notify the ViewController instance when a new to-do item is created. Let's break it down to better understand this process.
Step 1: Declare the AddItemViewControllerDelegate Protocol
Open AddItemViewController.swift and declare the AddItemViewControllerDelegate protocol below the import statement at the top. The protocol declaration looks similar to a class declaration. The protocol keyword is followed by the name of the protocol.
The concept is very similar to protocols in Objective-C. The name of the protocol is AddItemViewControllerDelegate and it defines one method, controller(_:didAddItem:).
Step 2: Declare the delegate Property
The object that needs to implement the delegate protocol is the delegate of AddItemViewController. We first need to create a property for the delegate as shown in the snippet below.
class AddItemViewController: UIViewController {
@IBOutlet var textField: UITextField!
var delegate: AddItemViewControllerDelegate?
...
}
The delegate property is of type AddItemViewControllerDelegate?, an optional type, since we cannot be certain that the delegate property isn't nil. Note that the name of the protocol isn't wrapped in angle brackets as in Objective-C.
Step 3: Implement Actions
The delegate method, controller(_:didAddItem:), will be invoked in the create(_:) action as shown below. To keep the example simple, we don't do any validation on the user's input.
We use optional chaining to invoke the delegate method on the delegate object, which means the delegate method is only invoked if the delegate property is set. The value of the text field is temporarily stored in a constant, item.
@IBAction func create(_ sender: Any) {
if let item = textField.text {
delegate?.controller(self, didAddItem: item)
}
}
The implementation of the cancel(_:) action is easy. All we do is dismiss the AddItemViewController instance.
There's one piece of the puzzle missing though. The delegate property of the AddItemViewController instance is not being set at the moment. We can resolve this by implementing the prepare(for:sender:) method in the ViewController class. First, however, we need to revisit the storyboard.
Open Main.storyboard and select the segue connecting the Add button with the Navigation Controller. Open the Attributes Inspector and set the segue's Identifier toAddItemViewController.
Next, implement the prepare(for:sender:) method in the ViewController class as shown below. Note the override keyword prefixing the method. This should be familiar by now.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "AddItemViewController" {
let navigationController = segue.destination as? UINavigationController
let addItemViewController = navigationController?.topViewController as? AddItemViewController
if let viewController = addItemViewController {
viewController.delegate = self
}
}
}
We start by checking the identifier of the segue, making sure we're preparing for the correct segue. We then ask the segue for its destination view controller. You may expect this to be the AddItemViewController instance, but remember that we made the view controller the root view controller of a navigation controller. This means that we need to ask the navigation controller, the segue's destination view controller, for its top view controller.
The addItemViewController constant is of type AddItemViewController? because of the use of the as? keyword. In other words, by using as? we downcast the value of the topViewController property to an optional type.
In the if statement, we unwrap the optional and set the delegate property to the ViewController instance.
I'm sure you've noticed the use of several optionals in the implementation of the prepare(for:sender:) method. When interacting with Objective-C APIs, it's always better to play it safe. While sending messages to nil is perfectly fine in Objective-C, it isn't in Swift. Because of this key difference, you always need to be careful when interacting with Objective-C APIs in Swift. The above example illustrates this well.
Step 5: Implement the AddItemViewControllerDelegate Protocol
Implementing the AddItemViewControllerDelegate protocol is similar to the implementation of the UITableViewDataSource protocol. We start by conforming the ViewController class to the protocol as shown below.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddItemViewControllerDelegate {
...
}
Next, we implement the methods of the AddItemViewControllerDelegate protocol, which boils down to implementing the controller(_:didAddItem:) method. We add the new item to the view controller's items property, reload the table view, and dismiss the add item view controller.
Build and run the application to test if you can add new items to the to-do list. We currently don't validate the user's input. As an exercise, show an alert view to the user if they tap the Create button and the text field is empty. Adding a blank to-do isn't very useful. Right?
Conclusion
In this lesson, you learned how to declare and implement a custom protocol. You also learned how to create actions and hook them up in Interface Builder. In the next lesson, we're going to complete our to-do application by adding the ability to delete to-do items, and we'll also improve the application's user experience.
In the meantime, check out some of our other courses and tutorials about Swift language iOS development!
In the previous lesson, you created a simple Swift project in Xcode, a basic to-do application. In this installment of Swift From Scratch, we're going to add the ability to create to-do items. Along the way, you learn about actions, delegation, and properties.
Prerequisites
If you'd like to follow along with me, then make sure that you have Xcode 8.3.2 or higher installed on your machine. You can download Xcode 8.3.2 from Apple's App Store.
1. Adding Items
At the end of this lesson, the user will be able to add new to-do items by tapping a button in the navigation bar, presenting a view with a text field and a button. Let's start by creating the view controller that will handle adding new to-do items, the AddItemViewController class.
Step 1: Create AddItemViewController
Choose New > File... from Xcode's File menu and select the Cocoa Touch Class template from the list of iOS > Source templates.
Name the class AddItemViewController and make sure it inherits from UIViewController. Double-check that Language is set to Swift and that Also create XIB file is unchecked.
Tell Xcode where you'd like to save the file for the AddItemViewController class and click Create.
Step 2: Add Outlets and Actions
Before we create the user interface of the AddItemViewController class, we need to create an outlet for the text field and two actions, one for a cancel button in the navigation bar and another one for the create button below the text field.
Adding an outlet should be familiar by now. Create an outlet in the AddItemViewController class and name it textField as shown below.
Creating an action is very similar to creating an instance method. In fact, the @IBAction attribute is nothing more than a hint for Interface Builder. By prefixing a method with the @IBAction attribute, we ensure that Interface Builder is aware of the method, which enables us to connect it in the storyboard. We'll leave the bodies of both actions empty for now.
Open Main.storyboard in the Project Navigator and drag a View Controller from the Object Library on the right. With the view controller selected, open the Identity Inspector on the right and set Custom Class > Class to AddItemViewController.
To add a navigation bar to the add item view, select the Add Item View Controller and choose Embed In > Navigation Controller from the Editor menu. This will make the Add Item View Controller the root view controller of a navigation controller.
The next step is to add a bar button item to the navigation bar of the View Controller—not the Add Item View Controller—and set its Identifier to Add in the Attributes Inspector.
When the user taps the Add button, the Add Item View Controller should be presented modally. To accomplish this, press the Control key and drag from the Add button to the Navigation Controller, selecting Present Modally from the menu that pops up. This will create a segue from the Add Item View Controller to the new Navigation Controller.
Drag a text field and a button from the Object Library and add them to the Add Item View Controller scene. Select the Add Item View Controller and connect the textField outlet with the text field and the create(_:) action with the button. The create(_:) action should be triggered when the Touch Up Inside event is fired. Change the title of the button to Create and add the necessary layout constraints to the text field and button.
To finish the user interface, add a bar button item to the top left of the navigation bar of the Add Item View Controller and set its Identifier to Cancel. With the Add Item View Controller selected, open the Connections Inspector and connect the cancel(_:) action to the Cancel button.
Build and run the application by pressing Command-R to verify that everything is hooked up correctly.
2. Implementing a Delegate Protocol
When the user taps the Create button to add a to-do item, the add item view controller needs to notify the view controller. Delegation is a perfect solution for this scenario. The process is pretty simple.
We create a delegate protocol the ViewController class conforms to. When the AddItemViewController instance is created—when the user taps the Add button—the ViewController object is set as the delegate of the AddItemViewController instance, enabling the latter to notify the ViewController instance when a new to-do item is created. Let's break it down to better understand this process.
Step 1: Declare the AddItemViewControllerDelegate Protocol
Open AddItemViewController.swift and declare the AddItemViewControllerDelegate protocol below the import statement at the top. The protocol declaration looks similar to a class declaration. The protocol keyword is followed by the name of the protocol.
The concept is very similar to protocols in Objective-C. The name of the protocol is AddItemViewControllerDelegate and it defines one method, controller(_:didAddItem:).
Step 2: Declare the delegate Property
The object that needs to implement the delegate protocol is the delegate of AddItemViewController. We first need to create a property for the delegate as shown in the snippet below.
class AddItemViewController: UIViewController {
@IBOutlet var textField: UITextField!
var delegate: AddItemViewControllerDelegate?
...
}
The delegate property is of type AddItemViewControllerDelegate?, an optional type, since we cannot be certain that the delegate property isn't nil. Note that the name of the protocol isn't wrapped in angle brackets as in Objective-C.
Step 3: Implement Actions
The delegate method, controller(_:didAddItem:), will be invoked in the create(_:) action as shown below. To keep the example simple, we don't do any validation on the user's input.
We use optional chaining to invoke the delegate method on the delegate object, which means the delegate method is only invoked if the delegate property is set. The value of the text field is temporarily stored in a constant, item.
@IBAction func create(_ sender: Any) {
if let item = textField.text {
delegate?.controller(self, didAddItem: item)
}
}
The implementation of the cancel(_:) action is easy. All we do is dismiss the AddItemViewController instance.
There's one piece of the puzzle missing though. The delegate property of the AddItemViewController instance is not being set at the moment. We can resolve this by implementing the prepare(for:sender:) method in the ViewController class. First, however, we need to revisit the storyboard.
Open Main.storyboard and select the segue connecting the Add button with the Navigation Controller. Open the Attributes Inspector and set the segue's Identifier toAddItemViewController.
Next, implement the prepare(for:sender:) method in the ViewController class as shown below. Note the override keyword prefixing the method. This should be familiar by now.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "AddItemViewController" {
let navigationController = segue.destination as? UINavigationController
let addItemViewController = navigationController?.topViewController as? AddItemViewController
if let viewController = addItemViewController {
viewController.delegate = self
}
}
}
We start by checking the identifier of the segue, making sure we're preparing for the correct segue. We then ask the segue for its destination view controller. You may expect this to be the AddItemViewController instance, but remember that we made the view controller the root view controller of a navigation controller. This means that we need to ask the navigation controller, the segue's destination view controller, for its top view controller.
The addItemViewController constant is of type AddItemViewController? because of the use of the as? keyword. In other words, by using as? we downcast the value of the topViewController property to an optional type.
In the if statement, we unwrap the optional and set the delegate property to the ViewController instance.
I'm sure you've noticed the use of several optionals in the implementation of the prepare(for:sender:) method. When interacting with Objective-C APIs, it's always better to play it safe. While sending messages to nil is perfectly fine in Objective-C, it isn't in Swift. Because of this key difference, you always need to be careful when interacting with Objective-C APIs in Swift. The above example illustrates this well.
Step 5: Implement the AddItemViewControllerDelegate Protocol
Implementing the AddItemViewControllerDelegate protocol is similar to the implementation of the UITableViewDataSource protocol. We start by conforming the ViewController class to the protocol as shown below.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddItemViewControllerDelegate {
...
}
Next, we implement the methods of the AddItemViewControllerDelegate protocol, which boils down to implementing the controller(_:didAddItem:) method. We add the new item to the view controller's items property, reload the table view, and dismiss the add item view controller.
Build and run the application to test if you can add new items to the to-do list. We currently don't validate the user's input. As an exercise, show an alert view to the user if they tap the Create button and the text field is empty. Adding a blank to-do isn't very useful. Right?
Conclusion
In this lesson, you learned how to declare and implement a custom protocol. You also learned how to create actions and hook them up in Interface Builder. In the next lesson, we're going to complete our to-do application by adding the ability to delete to-do items, and we'll also improve the application's user experience.
In the meantime, check out some of our other courses and tutorials about Swift language iOS development!
News these days is full of buzzwords such as automation, artificial intelligence, and machine learning. That's probably why more and more smartphone users are starting to look for smarter apps. As a regular Android app developer, however, you probably lack the resources needed to create such apps from scratch.
Fortunately, Google recently launched a Cloud Machine Learning platform, which offers neural networks that have been pre-trained to perform a variety of tasks. Most of its models are not only highly accurate, but also constantly improving. And guess what? You can use them by simply making a few REST API calls!
In this tutorial, I'll introduce you to the Cloud Machine Learning platform and show you how to use it to create a smart Android app that can recognize real-world objects and name them in multiple languages.
Prerequisites
To make the most of this tutorial, all you need is:
To be able to use Google's machine learning services in your Android app, you need an API key. You can get one by creating a new project in the Google Cloud Platform console.
Start by logging in to the console and pressing the Create New Project button. In the dialog that pops up, give a meaningful name to the project.
Once the project has been created, go to API Manager > Dashboard and press the Enable API button.
In the next screen, under the Google Cloud Machine Learning heading, you'll be able to see all the different machine learning APIs available to you. In this tutorial, we'll be using only the Vision and Translation APIs.
To enable the Vision API, click on its link and press the Enable button.
Similarly, to enable the Translation API, click on its link and press the Enable button.
You'll need just one key to use both the APIs. To get it, go to the Credentials tab, press the Create credentials button, and select API key.
You should now see a popup containing your secret API key.
2. Creating a New Android Project
Fire up Android Studio and create a new project with an empty activity. I suggest you choose at least API level 19 for the minimum supported SDK.
Although you don't have to, it's always a good idea to use a robust networking library to communicate with the Google Cloud Machine Learning platform. In this tutorial, we'll be using one such library called Fuel. Add it as a compile dependency in the app module's build.gradle file:
Next, our app will need the INTERNET permission to communicate with Google's servers. Therefore, add the following line inside the manifest file of the project:
The Vision API helps you create apps that can see and make sense of the user's environment. Face detection, emotion detection, optical character recognition, and image annotation are some of its many features. For now, we'll be focusing only on the powerful image annotation feature, also called label detection—which, in my opinion, is very useful.
As you might expect, the API expects a picture as one of its inputs. Therefore, let us now create a screen where the user can take pictures using the device's camera.
Step 1: Create a Layout
Our screen's layout shall have a Button widget the user can press to take a picture, an ImageView widget to display the picture, and a TextView widget to display the labels, or annotations, generated by the API. Accordingly, add the following code to your activity's layout XML file:
Note that we've associated an onClick handler with the button. We'll define it in the next step.
Step 2: Create an Intent
By creating a new intent with the ACTION_IMAGE_CAPTURE action and passing it to the startActivityForResult() method, you can ask the default camera app of the user's device to take pictures and pass them on to your app. Therefore, add the following code to your Activity class:
public final static int MY_REQUEST_CODE = 1;
public void takePicture(View view) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, MY_REQUEST_CODE);
}
In order to receive the images captured by the default camera app, you must override the onActivityResult() method of your Activity class. Inside the method, you'll have access to a Bundle object containing all the image data. You can render the image data by simply converting it into a Bitmap and passing it to the ImageView widget.
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if(requestCode == MY_REQUEST_CODE && resultCode == RESULT_OK) {
// Convert image data to bitmap
Bitmap picture = (Bitmap)data.getExtras().get("data");
// Set the bitmap as the source of the ImageView
((ImageView)findViewById(R.id.previewImage))
.setImageBitmap(picture);
// More code goes here
}
}
If you run the app now and take a picture, you'll see that the size of the picture is quite small. That's all right, though—the Cloud Vision API is very accurate even with image thumbnails.
Step 3: Encode the Image
The Vision API cannot use Bitmap objects directly. Instead, it expects a Base64-encoded string of compressed image data.
To compress the image data, you can use the compress() method of the Bitmap class. As its arguments, the method expects the compression format to use, the output quality desired, and a ByteArrayOutputStream object. The following code compresses the bitmap using the JPEG format and also makes sure that the quality of the resulting image is sufficiently high:
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
picture.compress(Bitmap.CompressFormat.JPEG, 90, byteStream);
You can now generate the Base64 string using the encodeToString() method of the Base64 class.
After all that hard work, you have everything you need to interact with the Vision API. Start by creating a string that contains both the URL of the API and your API key:
To send data to the API, you must create an HTTP POST request. The body of the request must be a JSON document containing the Base64-encoded image data. For label detection, the document must also have a features array containing the value LABEL_DETECTION. Here's the format of the JSON document:
Although it is possible to hand-code the JSON document, creating it programmatically is less error-prone. The following code shows you how to do so using the JSONObject and JSONArray classes:
// Create an array containing
// the LABEL_DETECTION feature
JSONArray features = new JSONArray();
JSONObject feature = new JSONObject();
feature.put("type", "LABEL_DETECTION");
features.put(feature);
// Create an object containing
// the Base64-encoded image data
JSONObject imageContent = new JSONObject();
imageContent.put("content", base64Data);
// Put the array and object into a single request
// and then put the request into an array of requests
JSONArray requests = new JSONArray();
JSONObject request = new JSONObject();
request.put("image", imageContent);
request.put("features", features);
requests.put(request);
JSONObject postData = new JSONObject();
postData.put("requests", requests);
// Convert the JSON into a
// string
String body = postData.toString();
At this point, we can use the post() method of the Fuel class to make the HTTP POST request. As its only argument, the method expects the URL of the API. You must also include the content-length and content-type headers in the request. To do so, use the header() method. Similarly, in order to add the JSON document to the body of the POST request, use the post() method.
Lastly, by calling the responseString() method and passing a new instance of the Handler class to it, you can asynchronously get the response of the request as a string. Accordingly, add the following code:
Fuel.post(requestURL)
.header(
new Pair<String, Object>("content-length", body.length()),
new Pair<String, Object>("content-type", "application/json")
)
.body(body.getBytes())
.responseString(new Handler<String>() {
@Override
public void success(@NotNull Request request,
@NotNull Response response,
String data) {
// More code goes here
}
@Override
public void failure(@NotNull Request request,
@NotNull Response response,
@NotNull FuelError fuelError) {}
});
When you use the label detection feature, the Vision API returns a JSON document containing labels. Along with each label, you also get a score specifying how accurate the label is. The document looks like this:
For now, let's just loop through all the objects in the labelAnnotations array and add the value of each description key to the TextView widget of our layout. Here's how you can do that inside the success() method of the Handler class:
// Access the labelAnnotations arrays
JSONArray labels = new JSONObject(data)
.getJSONArray("responses")
.getJSONObject(0)
.getJSONArray("labelAnnotations");
String results = "";
// Loop through the array and extract the
// description key for each item
for(int i=0;i<labels.length();i++) {
results = results +
labels.getJSONObject(i).getString("description") +
"\n";
}
// Display the annotations inside the TextView
((TextView)findViewById(R.id.resultsText)).setText(results);
You can now run the app, take a picture of any object nearby, and see the labels the Vision API generates for it.
4. Using the Translation API
The Cloud Translation API, as its name suggests, can translate text to and from over 100 languages. By using it effectively, you can create smart apps that can communicate with your users in their own languages.
In the previous step, you saw that the labels our app generates are in English. Let us now add a button to it to translate those labels to German.
Step 1: Update the Layout
Add a Button widget towards the end of your activity's layout using the following code:
Notice that this button too has an onClick event handler that must be defined in your Activity class.
Step 2: Translate the Labels
Using the Translation API is far easier than using the Vision API, primarily because you don't have to create a JSON document to define your request. Instead, you can simply pass parameters to it in a query string.
Create the translateToGerman() method and, inside it, create a string containing the URL of the Translation API.
public void translateToGerman(View view) {
String requestURL =
"https://translation.googleapis.com/language/translate/v2";
// More code goes here
}
To add fields to the query string of the above URL, we can use a List of Pair objects. The following fields are important:
key, specifying your secret API key
source, specifying the language you want to translate from
target, specifying the language you want to translate to
q, specifying the text you want to translate
The following code shows you how to configure the API to translate from English to German:
List<Pair<String, String>> params = new ArrayList<>();
// Add API key
params.add(new Pair<String, String>("key",
getResources().getString(R.string.mykey)));
// Set source and target languages
params.add(new Pair<String, String>("source", "en"));
params.add(new Pair<String, String>("target", "de"));
Because the query string can contain multiple q fields, we'll be adding one for every label that's present inside our layout's TextView widget. Here's how:
Finally, you can call the get() method of the Fuel class to make an HTTP GET request to the Translation API. This time too, you can use the responseString() method to asynchronously get the response as a string.
Fuel.get(requestURL, params).responseString(new Handler<String>() {
@Override
public void success(@NotNull Request request,
@NotNull Response response,
String data) {
// More code here
}
@Override
public void failure(@NotNull Request request,
@NotNull Response response,
@NotNull FuelError fuelError) { }
});
The response of the Translation API is a JSON document containing an array of translations. It has the following format:
For now, inside the success() method of the Handler class, let us simply loop through the translations array of the above JSON document, and update the contents of the TextView widget using the values associated with the translatedText keys.
// Access the translations array
JSONArray translations = new JSONObject(data)
.getJSONObject("data")
.getJSONArray("translations");
// Loop through the array and extract the translatedText
// key for each item
String result = "";
for(int i=0;i<translations.length();i++) {
result += translations.getJSONObject(i)
.getString("translatedText") + "\n";
}
// Update the contents of the TextView widget
((TextView)findViewById(R.id.resultsText)).setText(result);
If you run the app now, generate labels for a picture, and press the second button, you should be able to see the labels in German.
Conclusion
In this tutorial, you learned how to use the Cloud Vision and Cloud Translation APIs, which are part of the Google Cloud Machine Learning platform, in an Android app. There are many more such APIs offered by the platform. You can learn more about them by referring to the official documentation.
While you're here, check out some of our other tutorials on how to use machine learning and cloud services in your Android apps!
You may have noticed that there is a craze for iMessage stickers since they were introduced with iOS 10. Today, you can now create and sell your very own digital stickers, instead of using the pre-installed emojis.
iMessage stickers are a cool and fun way to express your ideas and thoughts because you can peel, place, and stick them into your chats!!
If you've ever wondered what goes into making a sticker pack for iOS, in this tutorial, we will work from a sketch to design simple iMessage stickers in Adobe Illustrator using lines, shapes, and colors. Then will briefly shift to iTunes Connect and Xcode to launch our own iMessage sticker pack onto the App Store.
1. Come Up with a Concept and Prepare Your Sticker File in Adobe Illustrator
Step 1
I've been missing home for a while. No matter how much I enjoy the wonders of travel, my belly is
always filled with the scent of "home" food. So I decided to base my theme on "Lebanese food". There is absolutely nothing
as rich and abundant with variety as Lebanese cuisine.
So let's start by sketching out our food concept on paper or digitally.
I've sketched out 12 food "stickers" from the Lebanese table, and labelled their names in Lebanese, just for reference.
Generally it's good to have about 24 designs for a sticker pack, but for the sake of this tutorial we are working on just 12.
Start by sketching out 12 items in a 4 x 3 grid form, as below.
After you have finished sketching, move the sketch into Adobe Illustrator.
Step 2
Launch Adobe Illustrator CC.
Open a New Document: File> New (Command-N) and set up your Custom file:
Name: "Lebanese_Food_iMessage_Stickers".
Number of Artboards: 12 (respective to the number of stickers, as each sticker should have its own artboard)
You have three choices of sticker sizes to create.
Small: 300 x 300 pixels
Medium: 408 x 408 pixels
Large: 618 x 618 pixels
I personally prefer working on 618 x 618. So adjust the New Document Settings:
Columns: 4
Width:
618 px
Height:
618 px
Units: Pixels
Color Mode:
RGB
Click Create Document
Step 3
Use the Place shortcut (Command-Shift-P) to place the sketch onto the first Artboard. Drag the image to fill all the artboards, so your sketches fill all the artboards created.
Next, we need to create a layer for our line work.
Open the Layers panel, Window > Layers.
Click
on the sub-menu of the Layers panel:
Name: Sketch
Select Lock
Dim Images to:20%
OK
Afterlocking the "Sketch" layer, Create New Layer, on top of the 'Sketch', and name it 'Linework'.
File > Save (Command-S), and save your Illustrator file.
2. How to Draw the Line Work
Step
1
Let us prepare to draw in our line work.
First, we need to create a "pressure" Calligraphic art brush. Open the Brushes panel: Window > Brushes.
Select the Ellipse Tool (L), with a black Fill, null Stroke, and draw in a small ellipse the size of the brush you want to draw with.
Step 2
With the ellipse selected, drag and drop it into the Brushes panel.
A New Brushdialog box will pop open. SelectArt Brush, and OK.
The Art Brush Options dialog
box will open.
Note: If you have a digital pen tablet, it's perfect to create an art brush that reacts to the pen pressure.
To create a brush to move with the pressure of the pen, in the Art Brush Options dialog
box, select:
Width: Pressure
Brush Scale Options: Stretch to Fit
Stroke Length
Colorization Method: Tints; this will enable us to customize the colors of our custom brush strokes later.
Click OK.
Once the art brush is created, Delete the created ellipse.
Now we have a pressure-oriented art brush ready!
Step 3
Let's start tracing the sketch.
Open the Stroke panel: Windows > Stroke.
From the Brushes panel, select the art brush we just created.
Choose the PaintbrushTool (B), from the Tools panel.
In the Stroke panel, select a blackStroke of 0.5 pt, and Fill set to none.
Make sure your brush is not very smooth when you trace, so that it looks as if it's hand drawn. To smooth out the art brush, double click the PaintbrushTool (B), from the Tools panel, and the Paintbrush Tool Options dialog box will open.
Drop the Fidelity down a notch or two.
Step 4
Let's start tracing with the PaintbrushTool (B).
Follow the sketch and trace in the line work. It does not have to be perfect, as we want to give it a simple hand-drawn doodle effect.
Once all the line work is put in, Lock your “Linework” layer from the Layers panel.
Create New Layer, under 'Linework", and name it "Color", so we can apply color below the line work.
Always Save your file. File > Save (Command-S).
3. How to Quickly Color the Artwork
Step 1
Let's color!
From the Tools panel, select the Blob Brush Tool (Shift-B). The Blob Brush is similar to a marker, and it's fun to use.
Just select the Color Stroke swatch of your choice, with null fill,
and color in the food icons.
A quick tip: To manage the Blob Brush Tool size, hold the left square bracket key ([) to decrease the
brush size and the right square bracket (]) to enlarge.
Along with the Blob Brush Tool (Shift-B), use the Eraser Tool (Shift-E), to erase any blob shapes you want to clean out.
The coloring doesn't have to be perfect, as we need it to have an organic feel.
Start with the hummus.
As you see, you can achieve a lot with the Blob Brush Tool just by changing its size and colors. Plus it's fast to use.
So have fun and color in the rest of the artwork!
Step 2
You need to arrange the blob shapes as you color along, so to Arrange objects, use these shortcuts to speed up the workflow:
Arrange > Bring to Front (Command-Shift-])
Arrange > Bring Forward (Command-])
Arrange > Send Backward (Command-[)
Arrange > Send to Back (Command-Shift-[)
Once you have arranged everything, you should have a nice colorful set of icons.
Step 3
Now let's clean up our artboards.
Unlock the "Linework" layer from the Layers panel.
Delete the “Sketch” layer, by dragging
it from the Layers panel to the
trash icon on that panel.
Select the Selection Tool (V), and for each food icon, select the respective line work and color.
Group (Command-G) it together. Automatically, once you group the two layers, the new group moves into the "Linework" layer.
Center each icon to the center of the artboard by simply eyeing it.
6. Scale the grouped objects to fit within the artboard dimensions, and keeping a small space along the borders. To
maintain the object’s proportions, hold down Shift as you drag the bounding box handle until the object is the desired size.
7. Once all the food icons have been scaled, delete the "Color" layer, as it's empty.
File > Save (Command-S).
4. How to Export and Create Our App Icons
Step 1
Export the art files, using the Export for Screens feature (File > Export > Export for Screens).
Export as PNG.
Click the Advanced Settings icon, select which location to export your files to, and set Scale to 1x.
Set Background Color: Transparent.
Export Artboard.
Step 2
You have two choices. Either choose to use a secondary app Marketplace to launch the stickers, like LINE Creators Market, or build it yourself using the Apple Developers Account.
For this tutorial I will briefly guide you through opening an Apple Developers Account,which costs $100 a year. The application process is quite straightforward, and acceptance is quick. However, then you need to sign in to iTunes Connect and set up the administrative stuff like the Tax and Banking,if you're planning to sell your stickers (This is not required for free sticker packs).
Download the latest version of Xcode 8 direct from the Mac App Storeand Install.
Step 4
Uploading the stickers to Xcode is easy; just follow the steps:
Open Xcode.
Make sure you are signed in to Xcode with your Apple ID.
Preference > Account > Select + > Sign in.
Create a New Xcode Project.
Select Sticker Pack Application.
Click Next.
Give the sticker bundle a Product Name to identify it: 'Lebanese Food'.
Then fill out the Team, Organization Name, and Identifier (com.yourcompany.ProductName), according to your details.
Click Next
Select Stickers.xcstickers > Sticker Pack folder, and upload the stickers, by dragging and dropping the images.
Step 5
Next upload the iMessage App Icons.
The folder is above the 'Sticker Pack' folder.
Chose one image from the food set that you want to use for the App Icon, and convert it into a JPG in Adobe Photoshop, so it has a white background. Rename the App Icon image: "Lebanese_Food_App_Icon.jpg".
There are 12
icon images in total that you will need for Xcode, and 1 for iTunes Connect.
To generate the app icons easily, visit MakeAppIcon.
Upload the selected food icon onto the website, enter your Email, and it will generate all the various app icon file sizes for you.
You can Download them direct from your email.
Once you have downloaded the generated images, Upload into Xcode by dragging and dropping.
It should look something like this once you've uploaded to Xcode.
5. Configure and Register the Stickers
Step 1
Once you have uploaded the stickers, you need to 'Test the Build' from Xcode, to see if the stickers in
action are good. You can do this through the
Xcode Simulator, or
you can run it on your own device. All you need to do is:
Select your mobile device, under Set the Active Scheme.
Click the Run button.
If a notification error is showing, make the requested changes, export and upload the corrected icons again.
Then test the stickers on the Xcode Simulator by clicking the Run icon. Looks fab!
If you need to adjust the display size of your stickers on iMessage, you can do so in the right panel, as seen below.
The sizes "Medium" or "Small" work well.
Once it's done, we need to register an App ID for the stickers.
Now we can finally Export & Upload our "build" to iTunes Connect!
Go back to Xcode.
Select “Generic iOS device” under Set the Active Scheme.
Then select the 'Stickers.xcstickers' folder, and from the top main menu click Product > Archive, to Archive the build.
A new window should appear that will show the archived "Build". You will see the stickers in the left panel.
Click Validate to see if all stickers are good to go. If there are
issues, Xcode will display a notification in red on the top menu bar.
If everything goes well with the "Build", then you need to upload it to iTunes Connect for Review.
Click on Upload to App Store. This will upload the stickers to iTunes Connect. After it’s done uploading, it should appear on your iTunes Connect account. Give it a few minutes.
Step 4
Time to submit the stickers to the App Store!
In iTunes Connect:
Select My Apps.
Select + > New App, and select the'Build' we just uploaded for submission.
Select iOS platform.
Name:Lebanese Food.
Select Language and Bundle ID.
Create a unique SKU "eg.location.com.bundle_title".
Create!
On the left menu panel, fill out the App information and Pricing & Availability. Here you can write the app description, language, categories, and price of the app. We will not get into all the details, as it's quite straightforward, but it should look similar to this:
Step 5
In Adobe Photoshop, create some screenshots, using the Xcode Simulator, and upload them onto iTunes Connect, to create iMessage preview images for the screenshot displays. This might take some time. Create 5 screenshot dimensions: iPhone 5.5 inch display (1242 x 2208 px). The system will generate the rest of the sizes for you.
Once done, click Submit for Review.
And
that’s it!
Now all you've got to do is wait for the review process for a few days, to get approved.
While your stickers are in review, you can send out Testinvitations to your fans through TestFlight, to try out the stickers free for 60 days. Or you can use the time to set up your taxes, agreements, certificates, and
bank details with Apple.
It's a bit time-consuming, and you will face some errors along the way, but with some patience and help, you will get there.
Congratulations!
Our "Lebanese Food" iMessage Stickers Are Ready!
Wow, I hope
you’ve enjoyed this tutorial! Designing stickers is fun, especially if you can make some money out of it!
Purchase the 'Lebanesy Food' sticker pack from iTunes.
I hope you found this tutorial useful! I would love to see what you create! Share your sticker designs with us.
You may have noticed that there is a craze for iMessage stickers since they were introduced with iOS 10. Today, you can now create and sell your very own digital stickers, instead of using the pre-installed emojis.
iMessage stickers are a cool and fun way to express your ideas and thoughts because you can peel, place, and stick them into your chats!!
If you've ever wondered what goes into making a sticker pack for iOS, in this tutorial, we will work from a sketch to design simple iMessage stickers in Adobe Illustrator using lines, shapes, and colors. Then will briefly shift to iTunes Connect and Xcode to launch our own iMessage sticker pack onto the App Store.
1. Come Up with a Concept and Prepare Your Sticker File in Adobe Illustrator
Step 1
I've been missing home for a while. No matter how much I enjoy the wonders of travel, my belly is
always filled with the scent of "home" food. So I decided to base my theme on "Lebanese food". There is absolutely nothing
as rich and abundant with variety as Lebanese cuisine.
So let's start by sketching out our food concept on paper or digitally.
I've sketched out 12 food "stickers" from the Lebanese table, and labelled their names in Lebanese, just for reference.
Generally it's good to have about 24 designs for a sticker pack, but for the sake of this tutorial we are working on just 12.
Start by sketching out 12 items in a 4 x 3 grid form, as below.
After you have finished sketching, move the sketch into Adobe Illustrator.
Step 2
Launch Adobe Illustrator CC.
Open a New Document: File> New (Command-N) and set up your Custom file:
Name: "Lebanese_Food_iMessage_Stickers".
Number of Artboards: 12 (respective to the number of stickers, as each sticker should have its own artboard)
You have three choices of sticker sizes to create.
Small: 300 x 300 pixels
Medium: 408 x 408 pixels
Large: 618 x 618 pixels
I personally prefer working on 618 x 618. So adjust the New Document Settings:
Columns: 4
Width:
618 px
Height:
618 px
Units: Pixels
Color Mode:
RGB
Click Create Document
Step 3
Use the Place shortcut (Command-Shift-P) to place the sketch onto the first Artboard. Drag the image to fill all the artboards, so your sketches fill all the artboards created.
Next, we need to create a layer for our line work.
Open the Layers panel, Window > Layers.
Click
on the sub-menu of the Layers panel:
Name: Sketch
Select Lock
Dim Images to:20%
OK
Afterlocking the "Sketch" layer, Create New Layer, on top of the 'Sketch', and name it 'Linework'.
File > Save (Command-S), and save your Illustrator file.
2. How to Draw the Line Work
Step
1
Let us prepare to draw in our line work.
First, we need to create a "pressure" Calligraphic art brush. Open the Brushes panel: Window > Brushes.
Select the Ellipse Tool (L), with a black Fill, null Stroke, and draw in a small ellipse the size of the brush you want to draw with.
Step 2
With the ellipse selected, drag and drop it into the Brushes panel.
A New Brushdialog box will pop open. SelectArt Brush, and OK.
The Art Brush Options dialog
box will open.
Note: If you have a digital pen tablet, it's perfect to create an art brush that reacts to the pen pressure.
To create a brush to move with the pressure of the pen, in the Art Brush Options dialog
box, select:
Width: Pressure
Brush Scale Options: Stretch to Fit
Stroke Length
Colorization Method: Tints; this will enable us to customize the colors of our custom brush strokes later.
Click OK.
Once the art brush is created, Delete the created ellipse.
Now we have a pressure-oriented art brush ready!
Step 3
Let's start tracing the sketch.
Open the Stroke panel: Windows > Stroke.
From the Brushes panel, select the art brush we just created.
Choose the PaintbrushTool (B), from the Tools panel.
In the Stroke panel, select a blackStroke of 0.5 pt, and Fill set to none.
Make sure your brush is not very smooth when you trace, so that it looks as if it's hand drawn. To smooth out the art brush, double click the PaintbrushTool (B), from the Tools panel, and the Paintbrush Tool Options dialog box will open.
Drop the Fidelity down a notch or two.
Step 4
Let's start tracing with the PaintbrushTool (B).
Follow the sketch and trace in the line work. It does not have to be perfect, as we want to give it a simple hand-drawn doodle effect.
Once all the line work is put in, Lock your “Linework” layer from the Layers panel.
Create New Layer, under 'Linework", and name it "Color", so we can apply color below the line work.
Always Save your file. File > Save (Command-S).
3. How to Quickly Color the Artwork
Step 1
Let's color!
From the Tools panel, select the Blob Brush Tool (Shift-B). The Blob Brush is similar to a marker, and it's fun to use.
Just select the Color Stroke swatch of your choice, with null fill,
and color in the food icons.
A quick tip: To manage the Blob Brush Tool size, hold the left square bracket key ([) to decrease the
brush size and the right square bracket (]) to enlarge.
Along with the Blob Brush Tool (Shift-B), use the Eraser Tool (Shift-E), to erase any blob shapes you want to clean out.
The coloring doesn't have to be perfect, as we need it to have an organic feel.
Start with the hummus.
As you see, you can achieve a lot with the Blob Brush Tool just by changing its size and colors. Plus it's fast to use.
So have fun and color in the rest of the artwork!
Step 2
You need to arrange the blob shapes as you color along, so to Arrange objects, use these shortcuts to speed up the workflow:
Arrange > Bring to Front (Command-Shift-])
Arrange > Bring Forward (Command-])
Arrange > Send Backward (Command-[)
Arrange > Send to Back (Command-Shift-[)
Once you have arranged everything, you should have a nice colorful set of icons.
Step 3
Now let's clean up our artboards.
Unlock the "Linework" layer from the Layers panel.
Delete the “Sketch” layer, by dragging
it from the Layers panel to the
trash icon on that panel.
Select the Selection Tool (V), and for each food icon, select the respective line work and color.
Group (Command-G) it together. Automatically, once you group the two layers, the new group moves into the "Linework" layer.
Center each icon to the center of the artboard by simply eyeing it.
6. Scale the grouped objects to fit within the artboard dimensions, and keeping a small space along the borders. To
maintain the object’s proportions, hold down Shift as you drag the bounding box handle until the object is the desired size.
7. Once all the food icons have been scaled, delete the "Color" layer, as it's empty.
File > Save (Command-S).
4. How to Export and Create Our App Icons
Step 1
Export the art files, using the Export for Screens feature (File > Export > Export for Screens).
Export as PNG.
Click the Advanced Settings icon, select which location to export your files to, and set Scale to 1x.
Set Background Color: Transparent.
Export Artboard.
Step 2
You have two choices. Either choose to use a secondary app Marketplace to launch the stickers, like LINE Creators Market, or build it yourself using the Apple Developers Account.
For this tutorial I will briefly guide you through opening an Apple Developers Account,which costs $100 a year. The application process is quite straightforward, and acceptance is quick. However, then you need to sign in to iTunes Connect and set up the administrative stuff like the Tax and Banking,if you're planning to sell your stickers (This is not required for free sticker packs).
Download the latest version of Xcode 8 direct from the Mac App Storeand Install.
Step 4
Uploading the stickers to Xcode is easy; just follow the steps:
Open Xcode.
Make sure you are signed in to Xcode with your Apple ID.
Preference > Account > Select + > Sign in.
Create a New Xcode Project.
Select Sticker Pack Application.
Click Next.
Give the sticker bundle a Product Name to identify it: 'Lebanese Food'.
Then fill out the Team, Organization Name, and Identifier (com.yourcompany.ProductName), according to your details.
Click Next
Select Stickers.xcstickers > Sticker Pack folder, and upload the stickers, by dragging and dropping the images.
Step 5
Next upload the iMessage App Icons.
The folder is above the 'Sticker Pack' folder.
Chose one image from the food set that you want to use for the App Icon, and convert it into a JPG in Adobe Photoshop, so it has a white background. Rename the App Icon image: "Lebanese_Food_App_Icon.jpg".
There are 12
icon images in total that you will need for Xcode, and 1 for iTunes Connect.
To generate the app icons easily, visit MakeAppIcon.
Upload the selected food icon onto the website, enter your Email, and it will generate all the various app icon file sizes for you.
You can Download them direct from your email.
Once you have downloaded the generated images, Upload into Xcode by dragging and dropping.
It should look something like this once you've uploaded to Xcode.
5. Configure and Register the Stickers
Step 1
Once you have uploaded the stickers, you need to 'Test the Build' from Xcode, to see if the stickers in
action are good. You can do this through the
Xcode Simulator, or
you can run it on your own device. All you need to do is:
Select your mobile device, under Set the Active Scheme.
Click the Run button.
If a notification error is showing, make the requested changes, export and upload the corrected icons again.
Then test the stickers on the Xcode Simulator by clicking the Run icon. Looks fab!
If you need to adjust the display size of your stickers on iMessage, you can do so in the right panel, as seen below.
The sizes "Medium" or "Small" work well.
Once it's done, we need to register an App ID for the stickers.
Now we can finally Export & Upload our "build" to iTunes Connect!
Go back to Xcode.
Select “Generic iOS device” under Set the Active Scheme.
Then select the 'Stickers.xcstickers' folder, and from the top main menu click Product > Archive, to Archive the build.
A new window should appear that will show the archived "Build". You will see the stickers in the left panel.
Click Validate to see if all stickers are good to go. If there are
issues, Xcode will display a notification in red on the top menu bar.
If everything goes well with the "Build", then you need to upload it to iTunes Connect for Review.
Click on Upload to App Store. This will upload the stickers to iTunes Connect. After it’s done uploading, it should appear on your iTunes Connect account. Give it a few minutes.
Step 4
Time to submit the stickers to the App Store!
In iTunes Connect:
Select My Apps.
Select + > New App, and select the'Build' we just uploaded for submission.
Select iOS platform.
Name:Lebanese Food.
Select Language and Bundle ID.
Create a unique SKU "eg.location.com.bundle_title".
Create!
On the left menu panel, fill out the App information and Pricing & Availability. Here you can write the app description, language, categories, and price of the app. We will not get into all the details, as it's quite straightforward, but it should look similar to this:
Step 5
In Adobe Photoshop, create some screenshots, using the Xcode Simulator, and upload them onto iTunes Connect, to create iMessage preview images for the screenshot displays. This might take some time. Create 5 screenshot dimensions: iPhone 5.5 inch display (1242 x 2208 px). The system will generate the rest of the sizes for you.
Once done, click Submit for Review.
And
that’s it!
Now all you've got to do is wait for the review process for a few days, to get approved.
While your stickers are in review, you can send out Testinvitations to your fans through TestFlight, to try out the stickers free for 60 days. Or you can use the time to set up your taxes, agreements, certificates, and
bank details with Apple.
It's a bit time-consuming, and you will face some errors along the way, but with some patience and help, you will get there.
Congratulations!
Our "Lebanese Food" iMessage Stickers Are Ready!
Wow, I hope
you’ve enjoyed this tutorial! Designing stickers is fun, especially if you can make some money out of it!
Purchase the 'Lebanesy Food' sticker pack from iTunes.
I hope you found this tutorial useful! I would love to see what you create! Share your sticker designs with us.
SpriteKit is Apple's 2D game engine—a rendering engine built on top of OpenGL. It was introduced with iOS 7, and each subsequent release has brought great additions to the framework. With the
use of textured sprites, a built-in physics engine, and the very
powerful SKAction class, you can very quickly build functional 2D games.
SpriteKit has built-in editors for scenes and particles, a camera node since the release of iOS9, and built-in support for tilesets since the release of iOS 10. With these new additions, SpriteKit is quickly becoming a powerhouse for creating 2D games.
Below is an image of the built-in scene editor, with a label, a colored sprite, and a textured sprite.
Rendering Loop
SpriteKit, like most game engines, uses a rendering loop to render and update the screen. The rendering loop goes through the following steps in rendering each scene:
Update the scene and its objects
Evaluate actions
Simulate physics
Apply constraints
Render the scene
Each of these steps has a corresponding method you can hook into to apply additional logic. The render loop methods are as follows:
update
didEvaluateActions
didSimulatePhysics
didApplyConstraints
didFinishUpdate
For example, if you wanted to manually move objects in your scene, then the update method would be what you would use. Or if you had objects that were being affected by actions or physics, you could tie into the corresponding methods to make sure those actions and the physics simulation are applied before whatever changes you make.
SKNode
The SKNode class is the fundamental building block of SpriteKit. All of your onscreen assets will be an SKNode or subclass thereof.
The SKNode class does not draw any visual assets itself. Its primary role is to provide baseline behavior that other classes implement. For example, the SKScene class is the root node in a tree of SKNode instances and is used to hold sprites and other content that needs to be rendered.
The rendering and animation are done by an SKView instance. The view is placed inside a window and an SKScene instance is added to it, and that scene will be rendered and animated as long as the view is active. You can use a single SKView instance in your window and switch between different scenes at any time.
The framework defines a number of other SKNode subclasses. The most common one used within a scene is the SKSpriteNode class. The SKSpriteNode class can be drawn either as a rectangle with an image mapped onto it with SKTexture, to create a sprite, or as a colored, untextured rectangle. You'll most often
use textured sprites, because this is how you will bring your game's artwork
to life.
Other important types of nodes include:
SKShapeNode, which renders a shape defined by a Core Graphics path
SKVideo, which displays video content
SKLabel, which displays a text label
We'll look at several of these subclasses of SKNode later in this series.
SKAction
The SKAction class is a very powerful class that is used to bring your nodes to life. SKAction can change your node's properties over time, for example by moving, scaling, or rotating them. You can chain actions together in a sequence, execute many actions together as a group, and repeat them in a loop. You can also use SKAction to run a custom block of code. For example, suppose you wanted to print out the coordinates of a node after it has moved. You could run a custom block of code within the SKAction to do just that.
SpriteKit Features
Physics
SpriteKit has a built-in physics engine that makes handling complex physics scenarios a breeze. Built on top of the popular Box2D framework, it allows you to respond to collisions and contact events, apply forces and gravity, and build very complex physics simulations using joints, such as pins and springs. You can use the scene editor to visually add physics to the nodes, or you can add physics programmatically.
Coordinate System
In SpriteKit, the coordinate (0,0) is
located at the bottom left of the screen instead of the top left, which
you may be used to if you've worked with Flash, Corona, HTML5 Canvas,
and many other game frameworks. Having the origin at the bottom left is an OpenGL convention, and SpriteKit follows it because SpriteKit uses OpenGL
under the hood.
Particle System
SpriteKit has a very powerful particle engine which can be used to simulate particle systems such as fire and smoke. There is also a built-in particle editor where you can visually lay out particle systems. If you prefer to stick with code, you can program these systems from the ground up using nothing but code.
Below is an image of the particle editor with a fire-like particle system.
Tiles
SpriteKit has a number of classes dedicated to building tiled layouts. Using tilemaps offers better memory usage than using a very large single image. The tiles can be arranged in rectangular, hexagonal, or isometric grids.
Below is an image of a tile map node using a rectangular grid.
Conclusion
These are a few of the highlights of the SpriteKit engine. I would suggest reading the SpriteKit overview to learn more about what it has to offer. To learn more about how to get started with SpriteKit, you should also check out Davis Allie's post here on Envato Tuts+.
Also, check out our SpriteKit courses! These will take you through all the steps of building your first SpriteKit game for iOS, even if you've never coded with SpriteKit before.
In this series, we’re looking at some of the up-and-coming UI features that you can start experimenting with today, via the Android O Developer Preview.
In the first tip, I showed you how to set up your development to support this early O preview, and how to create text that automatically scales up and scales down to suit the current screen configuration. In this tip, we’re going to be looking at how Android O is about to make working with custom fonts a hassle-free experience.
Adding Custom Fonts to Your Project
Have you ever wanted to make a particular piece of text stand out? Or maybe you were convinced that a custom font would be the perfect way to add some extra personality to your application?
While there are many benefits to using custom fonts, working with them in Android has traditionally been a painful experience, requiring you to either use a library or create a custom View.
Thankfully, working with custom fonts in Android is about to get much easier, as custom fonts are set to become a fully supported resource type in Android O. This means that adding a custom font to your app will be as straightforward as adding any other resource, such as images and text.
Android O supports both .otf (OpenType) and .ttf (TrueType) formats. There are plenty of websites offering these kinds of fonts for free, so spend a few minutes searching Google until you find a font you like.
Since we’re just experimenting with Android O’s new features, it doesn’t particularly matter which font you use, but when you’re looking for fonts to use in production releases of your app then you should always check that font’s terms and conditions. Just because a resource is free to download doesn’t automatically mean there aren’t restrictions on how you can use and redistribute this resource, so always check the fine print!
You should also consider your application’s purpose, content and target audience, as different fonts convey different messages. If you’re designing an app to help people complete their tax returns, then your audience may struggle to take your app’s financial and legal advice seriously if it’s delivered in a weird and wacky font!
Once you’ve found a font you want to work with, download and unzip it. At this point, you should check the font’s filename for invalid characters—essentially anything that isn’t lowercase a-z, 0-9 or an underscore. Try to include any other characters, and Android Studio will throw an error as soon as you try to reference that resource.
Once you have your font file(s), you’ll need somewhere to store them:
Right-click your project’s app/res folder and select New > Android resource directory.
Open the dropdown menu and select font.
Enter font as the File name.
Click OK.
Drag and drop your font file(s) into your new res/font folder.
Using Your Custom Fonts
You can apply a custom font to your text using the new android:fontFamily XML attribute:
<TextView
android:text="This is some text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/doublefeature"/>
You can also add custom fonts to any styles you’ve created in your app:
If you want to use custom fonts programmatically, then you can retrieve a custom font using the getFont(int) method, for example:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.LinearLayout.LayoutParams;
import android.graphics.Typeface;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout linearLayout = new LinearLayout(this);
TextView ProgrammaticallyTextView = new TextView(this);
ProgrammaticallyTextView.setText("This is some text");
//Reference your custom font//
Typeface typeface = getResources().getFont(R.font.doublefeature);
ProgrammaticallyTextView.setTypeface(typeface);
linearLayout.addView(ProgrammaticallyTextView);
this.setContentView(linearLayout, new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
}
Creating a Font Family
Sometimes you may unzip a font folder and discover multiple versions of the same font, such as an italicized version, or fonts with varying weights and thicknesses.
If you’re using multiple versions of the same font, then you may want to group them together into a font family. A font family is essentially a dedicated XML file where you define each version of the font, along with its associated style and weight attributes.
To create a font family:
Make sure you’ve added all the font files to your project’s res/font folder.
Right-click your project’s res/font folder, and then select New > Font resource file.
Give this file a name, and then click OK.
Open this XML file and define all the different versions of this font, along with their style and weight attributes, for example:
You can then reference any of the fonts in this family using the android:fontFamily attribute. For example:
android:fontFamily="@font/doublefeature_bold"
Don’t Forget to Test!
While it’s easy to get carried away with new features, don’t go overboard with custom fonts! Consistent UIs are easier to navigate and understand, and if your app is constantly switching between fonts then your users may end up paying more attention to how your text looks, rather than what it says.
If you do include custom fonts in your project, then it’s important to test how that font renders across a wide range of different screen configurations, as your top priority should always be ensuring that your text is easy to read.
Conclusion
In this tip, I showed you how to create more stylish and unique text, by adding custom fonts to your projects. In the final post in this series, we’re going to shift our focus from text to images, as I show you how to create adaptive launcher icons that automatically adapt their shape to suit the current device.
In the meantime, check out some of our other tutorials on Android app development!
SpriteKit is Apple's 2D game engine—a rendering engine built on top of OpenGL. It was introduced with iOS 7, and each subsequent release has brought great additions to the framework. With the
use of textured sprites, a built-in physics engine, and the very
powerful SKAction class, you can very quickly build functional 2D games.
SpriteKit has built-in editors for scenes and particles, a camera node since the release of iOS9, and built-in support for tilesets since the release of iOS 10. With these new additions, SpriteKit is quickly becoming a powerhouse for creating 2D games.
Below is an image of the built-in scene editor, with a label, a colored sprite, and a textured sprite.
Rendering Loop
SpriteKit, like most game engines, uses a rendering loop to render and update the screen. The rendering loop goes through the following steps in rendering each scene:
Update the scene and its objects
Evaluate actions
Simulate physics
Apply constraints
Render the scene
Each of these steps has a corresponding method you can hook into to apply additional logic. The render loop methods are as follows:
update
didEvaluateActions
didSimulatePhysics
didApplyConstraints
didFinishUpdate
For example, if you wanted to manually move objects in your scene, then the update method would be what you would use. Or if you had objects that were being affected by actions or physics, you could tie into the corresponding methods to make sure those actions and the physics simulation are applied before whatever changes you make.
SKNode
The SKNode class is the fundamental building block of SpriteKit. All of your onscreen assets will be an SKNode or subclass thereof.
The SKNode class does not draw any visual assets itself. Its primary role is to provide baseline behavior that other classes implement. For example, the SKScene class is the root node in a tree of SKNode instances and is used to hold sprites and other content that needs to be rendered.
The rendering and animation are done by an SKView instance. The view is placed inside a window and an SKScene instance is added to it, and that scene will be rendered and animated as long as the view is active. You can use a single SKView instance in your window and switch between different scenes at any time.
The framework defines a number of other SKNode subclasses. The most common one used within a scene is the SKSpriteNode class. The SKSpriteNode class can be drawn either as a rectangle with an image mapped onto it with SKTexture, to create a sprite, or as a colored, untextured rectangle. You'll most often
use textured sprites, because this is how you will bring your game's artwork
to life.
Other important types of nodes include:
SKShapeNode, which renders a shape defined by a Core Graphics path
SKVideo, which displays video content
SKLabel, which displays a text label
We'll look at several of these subclasses of SKNode later in this series.
SKAction
The SKAction class is a very powerful class that is used to bring your nodes to life. SKAction can change your node's properties over time, for example by moving, scaling, or rotating them. You can chain actions together in a sequence, execute many actions together as a group, and repeat them in a loop. You can also use SKAction to run a custom block of code. For example, suppose you wanted to print out the coordinates of a node after it has moved. You could run a custom block of code within the SKAction to do just that.
SpriteKit Features
Physics
SpriteKit has a built-in physics engine that makes handling complex physics scenarios a breeze. Built on top of the popular Box2D framework, it allows you to respond to collisions and contact events, apply forces and gravity, and build very complex physics simulations using joints, such as pins and springs. You can use the scene editor to visually add physics to the nodes, or you can add physics programmatically.
Coordinate System
In SpriteKit, the coordinate (0,0) is
located at the bottom left of the screen instead of the top left, which
you may be used to if you've worked with Flash, Corona, HTML5 Canvas,
and many other game frameworks. Having the origin at the bottom left is an OpenGL convention, and SpriteKit follows it because SpriteKit uses OpenGL
under the hood.
Particle System
SpriteKit has a very powerful particle engine which can be used to simulate particle systems such as fire and smoke. There is also a built-in particle editor where you can visually lay out particle systems. If you prefer to stick with code, you can program these systems from the ground up using nothing but code.
Below is an image of the particle editor with a fire-like particle system.
Tiles
SpriteKit has a number of classes dedicated to building tiled layouts. Using tilemaps offers better memory usage than using a very large single image. The tiles can be arranged in rectangular, hexagonal, or isometric grids.
Below is an image of a tile map node using a rectangular grid.
Conclusion
These are a few of the highlights of the SpriteKit engine. I would suggest reading the SpriteKit overview to learn more about what it has to offer. To learn more about how to get started with SpriteKit, you should also check out Davis Allie's post here on Envato Tuts+.
Also, check out our SpriteKit courses! These will take you through all the steps of building your first SpriteKit game for iOS, even if you've never coded with SpriteKit before.
In the previous lesson, we added the ability to create to-do items. While this addition has made the application a bit more useful, it would also be convenient to add the ability to mark items as done and delete items. That's what we'll focus on in this lesson.
Prerequisites
If you'd like to follow along with me, then make sure that you have Xcode 8.3.2 or higher installed on your machine. You can download Xcode 8.3.2 from Apple's App Store.
1. Deleting Items
To delete items, we need to implement two additional methods of the UITableViewDataSource protocol. We first need to tell the table view which rows can be edited by implementing the tableView(_:canEditRowAt:) method. As you can see in the below code snippet, the implementation is straightforward. We tell the table view that every row is editable by returning true.
We start by checking the value of editingStyle, an enumeration of type UITableViewCellEditingStyle. We only delete an item if the value of editingStyle is equal to UITableViewCellEditingStyle.delete.
Swift is smarter than that, though. Because it knows that editingStyle is of type UITableViewCellEditingStyle, we can omit UITableViewCellEditingStyle, the name of the enumeration, and write .delete, the member value of the enumeration that we're interested in. If you're new to enumerations in Swift, then I recommend you read this quick tip about enumerations in Swift.
Next, we update the table view's data source, items, by invoking remove(at:) on the items property, passing in the correct index. We also update the table view by invoking deleteRows(at:with:) on tableView, passing in an array with indexPath and .right to specify the animation type. As we saw earlier, we can omit the name of the enumeration, UITableViewRowAnimation, since Swift knows the type of the second argument is UITableViewRowAnimation.
The user should now be able to delete items from the list. Build and run the application to test this.
2. Checking Off Items
To mark an item as done, we're going to add a checkmark to the corresponding row. This implies that we need to keep track of the items the user has marked as done. For that purpose, we'll declare a new property that manages this for us. Declare a variable property, checkedItems, of type [String], and initialize it with an empty array.
var checkedItems: [String] = []
In tableView(_:cellForRowAt:), we check whether checkedItems contains the respective item by invoking the contains(_:) method, passing in the item that corresponds with the current row. The method returns true if checkedItems contains item.
If item is found in checkedItems, we set the cell's accessoryType property to .checkmark, a member value of the UITableViewCellAccessoryType enumeration. If item isn't found, we fall back to .none as the cell's accessory type.
The next step is adding the ability to mark an item as done by implementing a method of the UITableViewDelegate protocol, tableView(_:didSelectRowAt:). In this delegate method, we first call deselectRow(at:animated:) on tableView to deselect the row the user tapped.
// MARK: - Table View Delegate Methods
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
// Fetch Item
let item = items[indexPath.row]
// Fetch Cell
let cell = tableView.cellForRow(at: indexPath)
// Find Index of Item
let index = checkedItems.index(of: item)
if let index = index {
checkedItems.remove(at: index)
cell?.accessoryType = .none
} else {
checkedItems.append(item)
cell?.accessoryType = .checkmark
}
}
We then fetch the corresponding item from items and a reference to the cell that corresponds with the tapped row. We ask checkedItems for the index of the corresponding item by invoking index(of:). This method returns an optional Int. If checkedItems contains item, we remove it from checkedItems and set the cell's accessory type to .none. If checkedItems doesn't contain item, we add it to checkedItems and set the cell's accessory type to .checkmark.
With these additions, the user is now able to mark items as done. Build and run the application to make sure that everything is working as expected.
3. Saving State
The application currently doesn't save state between launches. To solve this, we're going to store the items and checkedItems arrays in the application's user defaults database.
Step 1: Loading State
Start by creating two helper methods, loadItems() and loadCheckedItems(). Note the private keyword prefixing each helper method. The private keyword tells Swift that these methods are only accessible from within the ViewController class.
// MARK: Private Helper Methods
private func loadItems() {
let userDefaults = UserDefaults.standard
if let items = userDefaults.object(forKey: "items") as? [String] {
self.items = items
}
}
private func loadCheckedItems() {
let userDefaults = UserDefaults.standard
if let checkedItems = userDefaults.object(forKey: "checkedItems") as? [String] {
self.checkedItems = checkedItems
}
}
The private keyword is part of Swift's access control. As the name implies, access control defines which code has access to which code. Access levels apply to methods, functions, types, etc. Apple simply refers to entities. There are five access levels: open, public, internal, file-private, and private.
Open/Public: Entities marked as open or public are accessible by entities defined in the same module as well as other modules. This is ideal for exposing the interface of a framework. There are several differences between the open and public access levels. You can read more about these differences in The Swift Programming Language.
Internal: This is the default access level. In other words, if no access level is specified, this access level applies. An entity with an access level of internal is only accessible by entities defined in the same module.
File-Private: An entity declared as file-private is only accessible by entities defined in the same source file. For example, the private helper methods defined in the ViewController class are only accessible by the ViewController class.
Private: Private is very similar to file-private. The only difference is that an entity declared as private is only accessible from within the declaration it is enclosed by. For example, if we create an extension for the ViewController class in ViewController.swift, any entities that are marked as file-private would not be accessible in the extension, but private entities would be accessible.
The implementation of the helper methods is simple if you're familiar with the UserDefaults class. For ease of use, we store a reference to the standard user defaults object in a constant named userDefaults. In the case of loadItems(), we ask userDefaults for the object associated with the key "items" and downcast it to an optional array of strings. We safely unwrap the optional, which means that we store the value in the constant items if the optional is not nil, and assign the value to the items property of the view controller.
If the if statement looks confusing, then have a look at a simpler version of the loadItems() method in the following example. The result is identical; the only difference is conciseness.
private func loadItems() {
let userDefaults = UserDefaults.standard
let storedItems = userDefaults.object(forKey: "items") as? [String]
if let items = storedItems {
self.items = items
}
}
The implementation of loadCheckedItems() is identical except for the key used to load the object stored in the user defaults database. Let's put loadItems() and loadCheckedItems() to use by updating the viewDidLoad() method.
override func viewDidLoad() {
super.viewDidLoad()
// Set Title
title = "To Do"
// Populate Items
items = ["Buy Milk", "Finish Tutorial", "Play Minecraft"]
// Load State
loadItems()
loadCheckedItems()
// Register Class for Cell Reuse
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "TableViewCell")
}
Step 2: Saving State
To save state, we implement two more private helper methods, saveItems() and saveCheckedItems(). The logic is similar to that of loadItems() and loadCheckedItems(). The difference is that we store data in the user defaults database. Make sure that the keys used in the setObject(_:forKey:) calls match those used in loadItems() and loadCheckedItems().
private func saveItems() {
let userDefaults = UserDefaults.standard
// Update User Defaults
userDefaults.set(items, forKey: "items")
userDefaults.synchronize()
}
private func saveCheckedItems() {
let userDefaults = UserDefaults.standard
// Update User Defaults
userDefaults.set(checkedItems, forKey: "checkedItems")
userDefaults.synchronize()
}
The synchronize() call isn't strictly necessary. The operating system will make sure that the data you store in the user defaults database is written to disk at some point. By invoking synchronize(), however, you explicitly tell the operating system to write any pending changes to disk. This is useful during development, because the operating system won't write your changes to disk if you kill the application. It may then seem as if something isn't working properly.
We need to invoke saveItems() and saveCheckedItems() in a number of places. To start, call saveItems() when a new item is added to the list. We do this in the delegate method of the AddItemViewControllerDelegate protocol.
When the state of an item changes in the tableView(_:didSelectRowAt:), we update checkedItems. It's a good idea to also invoke saveCheckedItems() at that point.
// MARK: - Table View Delegate Methods
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
// Fetch Item
let item = items[indexPath.row]
// Fetch Cell
let cell = tableView.cellForRow(at: indexPath)
// Find Index of Item
let index = checkedItems.index(of: item)
if let index = index {
checkedItems.remove(at: index)
cell?.accessoryType = .none
} else {
checkedItems.append(item)
cell?.accessoryType = .checkmark
}
// Save State
saveCheckedItems()
}
When an item is deleted, both items and checkedItems are updated. To save this change, we call both saveItems() and saveCheckedItems().
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Fetch Item
let item = items[indexPath.row]
// Update Items
items.remove(at: indexPath.row)
if let index = checkedItems.index(of: item) {
checkedItems.remove(at: index)
}
// Update Table View
tableView.deleteRows(at: [indexPath], with: .right)
// Save State
saveItems()
saveCheckedItems()
}
}
That's it. Build and run the application to test your work. Play with the application and force quit it. When you launch the application again, the last known state should be loaded and visible.
4. Property Observers
The application's user experience is a bit lacking at the moment. When every item is deleted or when the application is launched for the first time, the user sees an empty table view. This isn't great. We can solve this by showing a message when there are no items. This will also give me the opportunity to show you another feature of Swift, property observers.
Step 1: Adding a Label
Let's start by adding a label to the user interface for showing the message. Declare an outlet named messageLabel of type UILabel in the ViewController class, open Main.storyboard, and add a label to the view controller's view.
@IBOutlet var messageLabel: UILabel!
Add the necessary layout constraints to the label and connect it with the view controller's messageLabel outlet in the Connections Inspector. Set the label's text to You don't have any to-dos. and center the label's text in the Attributes Inspector.
Step 2: Implementing a Property Observer
The message label should only be visible if items contains no elements. When that happens, we should also hide the table view. We could solve this problem by adding various checks in the ViewController class, but a more convenient and elegant approach is to use a property observer.
As the name implies, property observers observe a property. A property observer is invoked whenever a property changes, even when the new value is the same as the old value. There are two types of property observers.
willSet: invoked before the value has changed
didSet: invoked after the value has changed
For our purpose, we will implement the didSet observer for the items property. Take a look at the syntax in the following code snippet.
The construct may look a bit odd at first, so let me explain what's happening. When the didSet property observer is invoked, after the items property has changed, we check if the items property contains any elements. Based on the value of the hasItems constant, we update the user interface. It's as simple as that.
The didSet observer is passed a constant parameter that contains the value of the old value of the property. It is omitted in the above example, because we don't need it in our implementation. The following example shows how it could be used.
var items: [String] = [] {
didSet(oldValue) {
if oldValue != items {
let hasItems = items.count > 0
tableView.isHidden = !hasItems
messageLabel.isHidden = hasItems
}
}
}
The oldValue parameter in the example doesn't have an explicit type, because Swift knows the type of the items property. In the example, we only update the user interface if the old value differs from the new value.
A willSet observer works in a similar fashion. The main difference is that the parameter passed to the willSet observer is a constant holding the new value of the property. When using property observers, keep in mind that they are not invoked when the instance is initialized.
Build and run the application to make sure everything is hooked up correctly. Even though the application isn't perfect and could use a few more features, you have created your first iOS application using Swift.
Conclusion
Over the course of the last three lessons of this series, you created a functional iOS application using Swift's object-oriented features. If you have some experience programming and developing applications, then you must have noticed that the current data model has a few shortcomings, to put it lightly.
Storing items as strings and creating a separate array to store an item's state isn't a good idea if you're building a proper application. A better approach would be to create a separate ToDo class for modeling items and store them in the application's sandbox. That will be our goal for the next lesson of this series.
In the meantime, check out some of our other courses and tutorials about Swift language iOS development!
Google has launched the first developer preview of the next Android version, currently code-named Android O (Oreo maybe?). Some exciting features were released, and one of them is Notification Channels. In this tutorial, we'll explore this feature and build a simple app that demonstrates the functionalities it provides.
What Are Notification Channels?
Notification channels enable us app developers to group our notifications into groups—channels—with the user having the ability to modify notification settings for the entire channel at once. For example, for each channel, users can completely block all notifications, override the importance level, or allow a notification badge to be shown. This new feature helps in greatly improving the user experience of an app.
We're going to learn about this feature by building a simple application called "TutsplusAlerts" that will provide two notification channels: Android and iOS. The user will receive a notification from one of these separate channels whenever a new article is submitted.
1. Set Up the Android O SDK
To begin to use Android O APIs as of this writing, you will need to have the latest Android Studio 2.4 Canary installed on your computer.
Launch Android Studio 2.4 and open the SDK Manager by clicking Tools > Android > SDK Manager.
Then, in the SDK Platforms tab, check Show Package Details. Below Android O Preview, check the following: Android SDK Platform O and Google APIs Intel x86 Atom System Image (required only for the emulator).
Then switch to the SDK Tools tab, and select the following:
Android SDK Build-Tools 26.0.0 (rc1 or higher)
Android SDK Platform-Tools 26.0.0 (rc1 or higher)
Android Emulator 26.0.0
Support Repository
Click the OK button to download all of these components.
2. Create an Android Studio Project
In your Android Studio, create a new project named TutsplusAlerts with an empty activity called MainActivity.
3. Update build.gradle
Go to your app module build.gradle file and update compileSdkVersion, buildToolsVersion and targetSdkVersion and finally the Support Library version.
Remember to sync your project after making these changes.
4. Create Notification Channels
Create a new class and name it NotificationUtils, extending ContextWrapper.
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.ContextWrapper;
import android.graphics.Color;
public class NotificationUtils extends ContextWrapper {
private NotificationManager mManager;
public static final String ANDROID_CHANNEL_ID = "com.chikeandroid.tutsplustalerts.ANDROID";
public static final String IOS_CHANNEL_ID = "com.chikeandroid.tutsplustalerts.IOS";
public static final String ANDROID_CHANNEL_NAME = "ANDROID CHANNEL";
public static final String IOS_CHANNEL_NAME = "IOS CHANNEL";
public NotificationUtils(Context base) {
super(base);
createChannels();
}
public void createChannels() {
// create android channel
NotificationChannel androidChannel = new NotificationChannel(ANDROID_CHANNEL_ID,
ANDROID_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
// Sets whether notifications posted to this channel should display notification lights
androidChannel.enableLights(true);
// Sets whether notification posted to this channel should vibrate.
androidChannel.enableVibration(true);
// Sets the notification light color for notifications posted to this channel
androidChannel.setLightColor(Color.GREEN);
// Sets whether notifications posted to this channel appear on the lockscreen or not
androidChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
getManager().createNotificationChannel(androidChannel);
// create ios channel
NotificationChannel iosChannel = new NotificationChannel(IOS_CHANNEL_ID,
IOS_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
iosChannel.enableLights(true);
iosChannel.enableVibration(true);
iosChannel.setLightColor(Color.GRAY);
iosChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
getManager().createNotificationChannel(iosChannel);
}
private NotificationManager getManager() {
if (mManager == null) {
mManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
return mManager;
}
}
In the code above, we created two instances of the NotificationChannel, passing an id (which must be unique within your package), a channel name, and also an importance level in its constructor. For each notification channel, we applied characteristics such as sound, lights, vibration, and a notification to show on the lock screen. Finally, we got the NotificationManager from the system and then registered the channel by calling the method createNotificationChannel(), passing the channel we have created.
We can create multiple notification channels all at once with createNotificationChannels(), passing a Java list of NotificationChannel instances. You can get all notification channels for an app with getNotificationChannels() and get a specific channel with getNotificationChannel(), passing only the channel id as an argument.
Importance Levels
Starting from Android O, priority levels are deprecated for individual notifications. Instead, you set an importance level when creating a notification channel—ranging from NotificationManager.IMPORTANCE_NONE to NotificationManager.IMPORTANCE_HIGH. We'll set the Android channel to be IMPORTANCE_DEFAULT, while that of the iOS channel will be IMPORTANCE_HIGH.
The full list of importance options available are:
IMPORTANCE_DEFAULT: shows everywhere, makes noise, but does not visually intrude
IMPORTANCE_LOW: shows everywhere, but is not intrusive
IMPORTANCE_MIN: only shows in the shade, below the fold
IMPORTANCE_NONE: a notification with no importance; does not show in the shade
All notifications for a channel will be given the same importance level.
5. Create Notifications and Post to Channels
We are going to create two Notifications for each of our channels in the NotificationUtils class we created. We specify which notification should be sent to a channel in the Notification.Builder (Android API 25) constructor, where we pass it the channel id as the second argument.
// ...
public Notification.Builder getAndroidChannelNotification(String title, String body) {
return new Notification.Builder(getApplicationContext(), ANDROID_CHANNEL_ID)
.setContentTitle(title)
.setContentText(body)
.setSmallIcon(android.R.drawable.stat_notify_more)
.setAutoCancel(true);
}
public Notification.Builder getIosChannelNotification(String title, String body) {
return new Notification.Builder(getApplicationContext(), IOS_CHANNEL_ID)
.setContentTitle(title)
.setContentText(body)
.setSmallIcon(android.R.drawable.stat_notify_more)
.setAutoCancel(true);
}
//...
Be aware that Notification.Builder() also has a notification channel id setter method called setChannel(String channelId), so you can choose to set the notification channel id either in the constructor or using the setter method.
6. Create the XML Layout
Now that we have the setup for creating and posting to notification channels, let's create the XML layout interface for posting the message in our activity_main.xml.
In this section, we are going to edit our MainActivity so that we can get the title and author from the EditText components and then send these to the Android channel. We get the Notification.Builder for the Android channel we created in our NotificationUtils, and then notify the NotificationManager.
At this point, run the app and enter a title and an author, and then click the send button to receive the notification immediately.
Posting to the iOS Channel
Here we would post to the iOS channel. We get the Notification.Builder for the iOS channel we created in our NotificationUtils and then call the notify() method in the NotificationManager.
Run the app again and enter a title and an author, and then click the send button to receive the notification immediately.
8. Notification Channel Settings
As of this writing, you can't programmatically change the configuration of specific notification channel settings. Instead, the only option available is for the user to go to the notification settings screen for the app in the device settings. From there, the user can access the app notification settings to modify settings such as vibration, sound, etc. Users can navigate to the app notification settings in either of the following ways:
Long pressing on the notification on the notification drawer (left image below).
Settings> Apps & notifications> Notifications> then selecting the app (right image below).
You can also send the user right from your app to the channel notification settings. Let's see how we can do that for the Android channel. It's recommended that you do this in your app settings to make it easy for users to access these notification options.
Edit the XML Layout
Include another button that will send the user to the channel notification settings.
Here we create an intent and pass it a Settings action—ACTION_CHANNEL_NOTIFICATION_SETTINGS (API 25)—and then add some extra values: the app package name and channel id. Finally, we start the settings activity with the intent.
Run the app and click the notification settings for the Android channel.
In the channel notification settings, users can edit settings such as enabling vibrations, changing the importance, or showing a badge (if supported) for the channel.
If you want to take users to the general notification settings for your app, you can also do that with an Intent:
Intent i = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
i.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
startActivity(i);
9. Create Notification Groups
We can also group notification channels into groups so they can be managed together. This is useful for apps that support multiple user accounts. The same notification channels are available across the individual accounts. For example, a social networking app might include support for both personal and business user accounts. The code below shows you how to create a notification channel group:
Deleting a notification channel is easy if it is no longer needed. Just use the notification manager method deleteNotificationChannel() and pass the channel id.
However, be aware that deleted channels remain visible in the notification settings so as to prevent spam.
Conclusion
In this tutorial, you learned about Notification Channels for Android O: what they are and how to create one, as well as how to post a notification to a channel, how to access a channel's notification settings, how to group notification channels, and how to delete a notification channel.
To learn more about Notification Channels, do refer to the official documentation. And in the meantime, check out some of our other courses and tutorials on Android app development!
Google has launched the first developer preview of the next Android version, currently code-named Android O (Oreo maybe?). Some exciting features were released, and one of them is Notification Channels. In this tutorial, we'll explore this feature and build a simple app that demonstrates the functionalities it provides.
What Are Notification Channels?
Notification channels enable us app developers to group our notifications into groups—channels—with the user having the ability to modify notification settings for the entire channel at once. For example, for each channel, users can completely block all notifications, override the importance level, or allow a notification badge to be shown. This new feature helps in greatly improving the user experience of an app.
We're going to learn about this feature by building a simple application called "TutsplusAlerts" that will provide two notification channels: Android and iOS. The user will receive a notification from one of these separate channels whenever a new article is submitted.
1. Set Up the Android O SDK
To begin to use Android O APIs as of this writing, you will need to have the latest Android Studio 2.4 Canary installed on your computer.
Launch Android Studio 2.4 and open the SDK Manager by clicking Tools > Android > SDK Manager.
Then, in the SDK Platforms tab, check Show Package Details. Below Android O Preview, check the following: Android SDK Platform O and Google APIs Intel x86 Atom System Image (required only for the emulator).
Then switch to the SDK Tools tab, and select the following:
Android SDK Build-Tools 26.0.0 (rc1 or higher)
Android SDK Platform-Tools 26.0.0 (rc1 or higher)
Android Emulator 26.0.0
Support Repository
Click the OK button to download all of these components.
2. Create an Android Studio Project
In your Android Studio, create a new project named TutsplusAlerts with an empty activity called MainActivity.
3. Update build.gradle
Go to your app module build.gradle file and update compileSdkVersion, buildToolsVersion and targetSdkVersion and finally the Support Library version.
Remember to sync your project after making these changes.
4. Create Notification Channels
Create a new class and name it NotificationUtils, extending ContextWrapper.
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.ContextWrapper;
import android.graphics.Color;
public class NotificationUtils extends ContextWrapper {
private NotificationManager mManager;
public static final String ANDROID_CHANNEL_ID = "com.chikeandroid.tutsplustalerts.ANDROID";
public static final String IOS_CHANNEL_ID = "com.chikeandroid.tutsplustalerts.IOS";
public static final String ANDROID_CHANNEL_NAME = "ANDROID CHANNEL";
public static final String IOS_CHANNEL_NAME = "IOS CHANNEL";
public NotificationUtils(Context base) {
super(base);
createChannels();
}
public void createChannels() {
// create android channel
NotificationChannel androidChannel = new NotificationChannel(ANDROID_CHANNEL_ID,
ANDROID_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
// Sets whether notifications posted to this channel should display notification lights
androidChannel.enableLights(true);
// Sets whether notification posted to this channel should vibrate.
androidChannel.enableVibration(true);
// Sets the notification light color for notifications posted to this channel
androidChannel.setLightColor(Color.GREEN);
// Sets whether notifications posted to this channel appear on the lockscreen or not
androidChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
getManager().createNotificationChannel(androidChannel);
// create ios channel
NotificationChannel iosChannel = new NotificationChannel(IOS_CHANNEL_ID,
IOS_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
iosChannel.enableLights(true);
iosChannel.enableVibration(true);
iosChannel.setLightColor(Color.GRAY);
iosChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
getManager().createNotificationChannel(iosChannel);
}
private NotificationManager getManager() {
if (mManager == null) {
mManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
return mManager;
}
}
In the code above, we created two instances of the NotificationChannel, passing an id (which must be unique within your package), a channel name, and also an importance level in its constructor. For each notification channel, we applied characteristics such as sound, lights, vibration, and a notification to show on the lock screen. Finally, we got the NotificationManager from the system and then registered the channel by calling the method createNotificationChannel(), passing the channel we have created.
We can create multiple notification channels all at once with createNotificationChannels(), passing a Java list of NotificationChannel instances. You can get all notification channels for an app with getNotificationChannels() and get a specific channel with getNotificationChannel(), passing only the channel id as an argument.
Importance Levels
Starting from Android O, priority levels are deprecated for individual notifications. Instead, you set an importance level when creating a notification channel—ranging from NotificationManager.IMPORTANCE_NONE to NotificationManager.IMPORTANCE_HIGH. We'll set the Android channel to be IMPORTANCE_DEFAULT, while that of the iOS channel will be IMPORTANCE_HIGH.
The full list of importance options available are:
IMPORTANCE_DEFAULT: shows everywhere, makes noise, but does not visually intrude
IMPORTANCE_LOW: shows everywhere, but is not intrusive
IMPORTANCE_MIN: only shows in the shade, below the fold
IMPORTANCE_NONE: a notification with no importance; does not show in the shade
All notifications for a channel will be given the same importance level.
5. Create Notifications and Post to Channels
We are going to create two Notifications for each of our channels in the NotificationUtils class we created. We specify which notification should be sent to a channel in the Notification.Builder (Android API 25) constructor, where we pass it the channel id as the second argument.
// ...
public Notification.Builder getAndroidChannelNotification(String title, String body) {
return new Notification.Builder(getApplicationContext(), ANDROID_CHANNEL_ID)
.setContentTitle(title)
.setContentText(body)
.setSmallIcon(android.R.drawable.stat_notify_more)
.setAutoCancel(true);
}
public Notification.Builder getIosChannelNotification(String title, String body) {
return new Notification.Builder(getApplicationContext(), IOS_CHANNEL_ID)
.setContentTitle(title)
.setContentText(body)
.setSmallIcon(android.R.drawable.stat_notify_more)
.setAutoCancel(true);
}
//...
Be aware that Notification.Builder() also has a notification channel id setter method called setChannel(String channelId), so you can choose to set the notification channel id either in the constructor or using the setter method.
6. Create the XML Layout
Now that we have the setup for creating and posting to notification channels, let's create the XML layout interface for posting the message in our activity_main.xml.
In this section, we are going to edit our MainActivity so that we can get the title and author from the EditText components and then send these to the Android channel. We get the Notification.Builder for the Android channel we created in our NotificationUtils, and then notify the NotificationManager.
At this point, run the app and enter a title and an author, and then click the send button to receive the notification immediately.
Posting to the iOS Channel
Here we would post to the iOS channel. We get the Notification.Builder for the iOS channel we created in our NotificationUtils and then call the notify() method in the NotificationManager.
Run the app again and enter a title and an author, and then click the send button to receive the notification immediately.
8. Notification Channel Settings
As of this writing, you can't programmatically change the configuration of specific notification channel settings. Instead, the only option available is for the user to go to the notification settings screen for the app in the device settings. From there, the user can access the app notification settings to modify settings such as vibration, sound, etc. Users can navigate to the app notification settings in either of the following ways:
Long pressing on the notification on the notification drawer (left image below).
Settings> Apps & notifications> Notifications> then selecting the app (right image below).
You can also send the user right from your app to the channel notification settings. Let's see how we can do that for the Android channel. It's recommended that you do this in your app settings to make it easy for users to access these notification options.
Edit the XML Layout
Include another button that will send the user to the channel notification settings.
Here we create an intent and pass it a Settings action—ACTION_CHANNEL_NOTIFICATION_SETTINGS (API 25)—and then add some extra values: the app package name and channel id. Finally, we start the settings activity with the intent.
Run the app and click the notification settings for the Android channel.
In the channel notification settings, users can edit settings such as enabling vibrations, changing the importance, or showing a badge (if supported) for the channel.
If you want to take users to the general notification settings for your app, you can also do that with an Intent:
Intent i = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
i.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
startActivity(i);
9. Create Notification Groups
We can also group notification channels into groups so they can be managed together. This is useful for apps that support multiple user accounts. The same notification channels are available across the individual accounts. For example, a social networking app might include support for both personal and business user accounts. The code below shows you how to create a notification channel group:
Deleting a notification channel is easy if it is no longer needed. Just use the notification manager method deleteNotificationChannel() and pass the channel id.
However, be aware that deleted channels remain visible in the notification settings so as to prevent spam.
Conclusion
In this tutorial, you learned about Notification Channels for Android O: what they are and how to create one, as well as how to post a notification to a channel, how to access a channel's notification settings, how to group notification channels, and how to delete a notification channel.
To learn more about Notification Channels, do refer to the official documentation. And in the meantime, check out some of our other courses and tutorials on Android app development!
Ready to create an Android app? Not so fast. Before you even start building your app, there are a lot of questions to answer. Who is it for? What will the revenue model be? Do other apps exist that already solve this problem?
In our latest ten-minute Coffee Break Course, Steps to Creating an Android App, Envato Tuts+ instructor Paul Trebilcox-Ruiz will walk you through some of the decisions that need to be made before starting an app project.
After that, you'll go on to learn about some of the other decisions and tasks that come up during the lifecycle of every app project, such as:
design
coding
testing
publishing
revision
Watch the introduction below to find out more.
You can take our new Coffee Break Course straight away with a free 10-day trial of our monthly subscription. If you decide to continue, it costs just $15 a month, and you’ll get access to hundreds of courses, with new ones added every week.
And if you need a head start, consider downloading one of the full-featured Android app templates from Envato Market.
Ready to create an Android app? Not so fast. Before you even start building your app, there are a lot of questions to answer. Who is it for? What will the revenue model be? Do other apps exist that already solve this problem?
In our latest ten-minute Coffee Break Course, Steps to Creating an Android App, Envato Tuts+ instructor Paul Trebilcox-Ruiz will walk you through some of the decisions that need to be made before starting an app project.
After that, you'll go on to learn about some of the other decisions and tasks that come up during the lifecycle of every app project, such as:
design
coding
testing
publishing
revision
Watch the introduction below to find out more.
You can take our new Coffee Break Course straight away with a free 10-day trial of our monthly subscription. If you decide to continue, it costs just $15 a month, and you’ll get access to hundreds of courses, with new ones added every week.
And if you need a head start, consider downloading one of the full-featured Android app templates from Envato Market.
In the previous lesson of Swift From Scratch, we created a functional to-do application. The data model could use some love, though. In this final lesson, we're going to refactor the data model by implementing a custom model class.
1. The Data Model
The data model we're about to implement includes two classes, a Task class and a ToDo class that inherits from the Task class. While we create and implement these model classes, we'll continue our exploration of object-oriented programming in Swift. In this lesson, we'll zoom in on the initialization of class instances and what role inheritance plays during initialization.
The Task Class
Let's start with the implementation of the Task class. Create a new Swift file by selecting New > File... from Xcode's File menu. Choose Swift File from the iOS > Source section. Name the file Task.swift and hit Create.
The basic implementation is short and simple. The Task class inherits from NSObject, defined in the Foundation framework, and has a variable property name of type String. The class defines two initializers, init() and init(name:). There are a few details that might trip you up, so let me explain what's happening.
import Foundation
class Task: NSObject {
var name: String
convenience override init() {
self.init(name: "New Task")
}
init(name: String) {
self.name = name
}
}
Because the init() method is also defined in the NSObject class, we need to prefix the initializer with the override keyword. We covered overriding methods earlier in this series. In the init() method, we invoke the init(name:) method, passing in "New Task" as the value for the name parameter.
The init(name:) method is another initializer, accepting a single parameter name of type String. In this initializer, the value of the name parameter is assigned to the name property. This is easy enough to understand. Right?
Designated and Convenience Initializers
What's with the convenience keyword prefixing the init() method? Classes can have two types of initializers, designated initializers and convenience initializers. Convenience initializers are prefixed with the convenience keyword, which implies that init(name:) is a designated initializer. Why is that? What's the difference between designated and convenience initializers?
Designated initializers fully initialize an instance of a class, meaning that every property of the instance has an initial value after initialization. Looking at the Task class, for example, we see that the name property is set with the value of the name parameter of the init(name:) initializer. The result after initialization is a fully initialized Task instance.
Convenience initializers, however, rely on a designated initializer to create a fully initialized instance of the class. That's why the init() initializer of the Task class invokes the init(name:) initializer in its implementation. This is referred to as initializer delegation. The init() initializer delegates initialization to a designated initializer to create a fully initialized instance of the Task class.
Convenience initializers are optional. Not every class has a convenience initializer. Designated initializers are required, and a class needs to have at least one designated initializer to create a fully initialized instance of itself.
The NSCoding Protocol
The implementation of the Task class isn't complete, though. Later in this lesson, we will write an array of ToDo instances to disk. This is only possible if instances of the ToDo class can be encoded and decoded.
Don't worry, though—this isn't rocket science. We only need to make the Task and ToDo classes conform to the NSCoding protocol. That's why the Task class inherits from the NSObject class since the NSCoding protocol can only be implemented by classes inheriting—directly or indirectly—from NSObject. Like the NSObject class, the NSCoding protocol is defined in the Foundation framework.
Adopting a protocol is something we already covered in this series, but there are a few gotchas that I want to point out. Let's start by telling the compiler that the Task class conforms to the NSCoding protocol.
import Foundation
class Task: NSObject, NSCoding {
var name: String
...
}
Next, we need to implement the two methods declared in the NSCoding protocol, init?(coder:) and encode(with:). The implementation is straightforward if you're familiar with the NSCoding protocol.
import Foundation
class Task: NSObject, NSCoding {
var name: String
@objc required init?(coder aDecoder: NSCoder) {
name = aDecoder.decodeObject(forKey: "name") as! String
}
@objc func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
}
convenience override init() {
self.init(name: "New Task")
}
init(name: String) {
self.name = name
}
}
The init?(coder:) initializer is a designated initializer that initializes a Task instance. Even though we implement the init?(coder:) method to conform to the NSCoding protocol, you won't ever need to invoke this method directly. The same is true for encode(with:), which encodes an instance of the Task class.
The required keyword prefixing the init?(coder:) method indicates that every subclass of the Task class needs to implement this method. The required keyword only applies to initializers, which is why we don't need to add it to the encode(with:) method.
Before we move on, we need to talk about the @objc attribute. Because the NSCoding protocol is an Objective-C protocol, protocol conformance can only be checked by adding the @objc attribute. In Swift, there's no such thing as protocol conformance or optional protocol methods. In other words, if a class adheres to a particular protocol, the compiler verifies and expects that every method of the protocol is implemented.
The ToDo Class
With the Task class implemented, it's time to implement the ToDo class. Create a new Swift file and name it ToDo.swift. Let's look at the implementation of the ToDo class.
The ToDo class inherits from the Task class and declares a variable property done of type Bool. In addition to the two required methods of the NSCoding protocol that it inherits from the Task class, it also declares a designated initializer, init(name:done:).
As in Objective-C, the super keyword refers to the superclass, the Task class in this example. There is one important detail that deserves attention. Before you invoke the init(name:) method on the superclass, every property declared by the ToDo class needs to be initialized. In other words, before the ToDo class delegates initialization to its superclass, every property defined by the ToDo class needs to have a valid initial value. You can verify this by switching the order of the statements and inspecting the error that pops up.
The same applies to the init?(coder:) method. We first initialize the done property before invoking init?(coder:) on the superclass.
Initializers and Inheritance
When dealing with inheritance and initialization, there are a few rules to keep in mind. The rule for designated initializers is simple.
A designated initializer needs to invoke a designated initializer from its superclass. In the ToDo class, for example, the init?(coder:) method invokes the init?(coder:) method of its superclass. This is also referred to as delegating up.
The rules for convenience initializers are a bit more complex. There are two rules to keep in mind.
A convenience initializer always needs to invoke another initializer of the class it's defined in. In the Task class, for example, the init() method is a convenience initializer and delegates initialization to another initializer, init(name:) in the example. This is known as delegating across.
Even though a convenience initializer doesn't have to delegate initialization to a designated initializer, a convenience initializer needs to call a designated initializer at some point. This is necessary to fully initialize the instance that's being initialized.
With both model classes implemented, it is time to refactor the ViewController and AddItemViewController classes. Let's start with the latter.
2. Refactoring AddItemViewController
Step 1: Update the AddItemViewControllerDelegate Protocol
The only changes we need to make in the AddItemViewController class are related to the AddItemViewControllerDelegate protocol. In the protocol declaration, change the type of didAddItem from String to ToDo, the model class we implemented earlier.
This means that we also need to update the create(_:) action in which we invoke the delegate method. In the updated implementation, we create a ToDo instance, passing it to the delegate method.
@IBAction func create(_ sender: Any) {
if let name = textField.text {
// Create Item
let item = ToDo(name: name, done: false)
// Notify Delegate
delegate?.controller(self, didAddItem: item)
}
}
3. Refactoring ViewController
Step 1: Update the items Property
The ViewController class requires a bit more work. We first need to change the type of the items property to [ToDo], an array of ToDo instances.
This also means that we need to refactor a few other methods, such as the tableView(_:cellForRowAt:) method shown below. Because the items array now contains ToDo instances, checking if an item is marked as done is much simpler. We use Swift's ternary conditional operator to update the table view cell's accessory type.
When the user deletes an item, we only need to update the items property by removing the corresponding ToDo instance. This is reflected in the implementation of the tableView(_:commit:forRowAt:) method shown below.
Updating the state of an item when the user taps a row is handled in the tableView(_:didSelectRowAt:) method. The implementation of this UITableViewDelegate method is much simpler thanks to the ToDo class.
The corresponding ToDo instance is updated, and this change is reflected by the table view. To save the state, we invoke saveItems() instead of saveCheckedItems().
Step 4: Add Item View Controller Delegate Methods
Because we updated the AddItemViewControllerDelegate protocol, we also need to update the ViewController's implementation of this protocol. The change, however, is simple. We only need to update the method signature.
func controller(_ controller: AddItemViewController, didAddItem: ToDo) {
// Update Data Source
items.append(didAddItem)
// Save State
saveItems()
// Reload Table View
tableView.reloadData()
// Dismiss Add Item View Controller
dismiss(animated: true)
}
Step 5: Save Items
The pathForItems() Method
Instead of storing the items in the user defaults database, we're going to store them in the application's documents directory. Before we update the loadItems() and saveItems() methods, we're going to implement a helper method named pathForItems(). The method is private and returns a path, the location of the items in the documents directory.
private func pathForItems() -> String {
guard let documentsDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first,
let url = URL(string: documentsDirectory) else {
fatalError("Documents Directory Not Found")
}
return url.appendingPathComponent("items").path
}
We first fetch the path to the documents directory in the application's sandbox by invoking NSSearchPathForDirectoriesInDomains(_:_:_:). Because this method returns an array of strings, we grab the first item.
Notice that we use a guard statement to make sure the value returned by NSSearchPathForDirectoriesInDomains(_:_:_:) is valid. We throw a fatal error if this operation fails. This immediately terminates the application. Why do we do this? If the operating system is unable to hand us the path to the documents directory, we have bigger problems to worry about.
The value we return from pathForItems() is composed of the path to the documents directory with the string "items" appended to it.
The loadItems() Method
The loadItems method changes quite a bit. We first store the result of pathForItems() in a constant, path. We then unarchive the object archived at that path and downcast it to an optional array of ToDo instances. We use optional binding to unwrap the optional and assign it to a constant, items. In the if clause, we assign the value stored in items to the items property.
private func loadItems() {
let path = pathForItems()
if let items = NSKeyedUnarchiver.unarchiveObject(withFile: path) as? [ToDo] {
self.items = items
}
}
The saveItems() Method
The saveItems() method is short and simple. We store the result of pathForItems() in a constant, path, and invoke archiveRootObject(_:toFile:) on NSKeyedArchiver, passing in the items property and path. We print the result of the operation to the console.
Let's end with the fun part, deleting code. Start by removing the checkedItems property at the top since we no longer need it. As a result, we can also remove the loadCheckedItems() and saveCheckedItems() methods, and every reference to these methods in the ViewController class.
Build and run the application to see if everything is still working. The data model makes the application's code much simpler and more reliable. Thanks to the ToDo class, managing the items in our list much is now easier and less error-prone.
Conclusion
In this lesson, we refactored the data model of our application. You learned more about object-oriented programming and inheritance. Instance initialization is an important concept in Swift, so make sure you understand what we've covered in this lesson. You can read more about initialization and initializer delegation in The Swift Programming Language.
In the meantime, check out some of our other courses and tutorials about Swift language iOS development!
In the previous lesson of Swift From Scratch, we created a functional to-do application. The data model could use some love, though. In this final lesson, we're going to refactor the data model by implementing a custom model class.
1. The Data Model
The data model we're about to implement includes two classes, a Task class and a ToDo class that inherits from the Task class. While we create and implement these model classes, we'll continue our exploration of object-oriented programming in Swift. In this lesson, we'll zoom in on the initialization of class instances and what role inheritance plays during initialization.
The Task Class
Let's start with the implementation of the Task class. Create a new Swift file by selecting New > File... from Xcode's File menu. Choose Swift File from the iOS > Source section. Name the file Task.swift and hit Create.
The basic implementation is short and simple. The Task class inherits from NSObject, defined in the Foundation framework, and has a variable property name of type String. The class defines two initializers, init() and init(name:). There are a few details that might trip you up, so let me explain what's happening.
import Foundation
class Task: NSObject {
var name: String
convenience override init() {
self.init(name: "New Task")
}
init(name: String) {
self.name = name
}
}
Because the init() method is also defined in the NSObject class, we need to prefix the initializer with the override keyword. We covered overriding methods earlier in this series. In the init() method, we invoke the init(name:) method, passing in "New Task" as the value for the name parameter.
The init(name:) method is another initializer, accepting a single parameter name of type String. In this initializer, the value of the name parameter is assigned to the name property. This is easy enough to understand. Right?
Designated and Convenience Initializers
What's with the convenience keyword prefixing the init() method? Classes can have two types of initializers, designated initializers and convenience initializers. Convenience initializers are prefixed with the convenience keyword, which implies that init(name:) is a designated initializer. Why is that? What's the difference between designated and convenience initializers?
Designated initializers fully initialize an instance of a class, meaning that every property of the instance has an initial value after initialization. Looking at the Task class, for example, we see that the name property is set with the value of the name parameter of the init(name:) initializer. The result after initialization is a fully initialized Task instance.
Convenience initializers, however, rely on a designated initializer to create a fully initialized instance of the class. That's why the init() initializer of the Task class invokes the init(name:) initializer in its implementation. This is referred to as initializer delegation. The init() initializer delegates initialization to a designated initializer to create a fully initialized instance of the Task class.
Convenience initializers are optional. Not every class has a convenience initializer. Designated initializers are required, and a class needs to have at least one designated initializer to create a fully initialized instance of itself.
The NSCoding Protocol
The implementation of the Task class isn't complete, though. Later in this lesson, we will write an array of ToDo instances to disk. This is only possible if instances of the ToDo class can be encoded and decoded.
Don't worry, though—this isn't rocket science. We only need to make the Task and ToDo classes conform to the NSCoding protocol. That's why the Task class inherits from the NSObject class since the NSCoding protocol can only be implemented by classes inheriting—directly or indirectly—from NSObject. Like the NSObject class, the NSCoding protocol is defined in the Foundation framework.
Adopting a protocol is something we already covered in this series, but there are a few gotchas that I want to point out. Let's start by telling the compiler that the Task class conforms to the NSCoding protocol.
import Foundation
class Task: NSObject, NSCoding {
var name: String
...
}
Next, we need to implement the two methods declared in the NSCoding protocol, init?(coder:) and encode(with:). The implementation is straightforward if you're familiar with the NSCoding protocol.
import Foundation
class Task: NSObject, NSCoding {
var name: String
@objc required init?(coder aDecoder: NSCoder) {
name = aDecoder.decodeObject(forKey: "name") as! String
}
@objc func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
}
convenience override init() {
self.init(name: "New Task")
}
init(name: String) {
self.name = name
}
}
The init?(coder:) initializer is a designated initializer that initializes a Task instance. Even though we implement the init?(coder:) method to conform to the NSCoding protocol, you won't ever need to invoke this method directly. The same is true for encode(with:), which encodes an instance of the Task class.
The required keyword prefixing the init?(coder:) method indicates that every subclass of the Task class needs to implement this method. The required keyword only applies to initializers, which is why we don't need to add it to the encode(with:) method.
Before we move on, we need to talk about the @objc attribute. Because the NSCoding protocol is an Objective-C protocol, protocol conformance can only be checked by adding the @objc attribute. In Swift, there's no such thing as protocol conformance or optional protocol methods. In other words, if a class adheres to a particular protocol, the compiler verifies and expects that every method of the protocol is implemented.
The ToDo Class
With the Task class implemented, it's time to implement the ToDo class. Create a new Swift file and name it ToDo.swift. Let's look at the implementation of the ToDo class.
The ToDo class inherits from the Task class and declares a variable property done of type Bool. In addition to the two required methods of the NSCoding protocol that it inherits from the Task class, it also declares a designated initializer, init(name:done:).
As in Objective-C, the super keyword refers to the superclass, the Task class in this example. There is one important detail that deserves attention. Before you invoke the init(name:) method on the superclass, every property declared by the ToDo class needs to be initialized. In other words, before the ToDo class delegates initialization to its superclass, every property defined by the ToDo class needs to have a valid initial value. You can verify this by switching the order of the statements and inspecting the error that pops up.
The same applies to the init?(coder:) method. We first initialize the done property before invoking init?(coder:) on the superclass.
Initializers and Inheritance
When dealing with inheritance and initialization, there are a few rules to keep in mind. The rule for designated initializers is simple.
A designated initializer needs to invoke a designated initializer from its superclass. In the ToDo class, for example, the init?(coder:) method invokes the init?(coder:) method of its superclass. This is also referred to as delegating up.
The rules for convenience initializers are a bit more complex. There are two rules to keep in mind.
A convenience initializer always needs to invoke another initializer of the class it's defined in. In the Task class, for example, the init() method is a convenience initializer and delegates initialization to another initializer, init(name:) in the example. This is known as delegating across.
Even though a convenience initializer doesn't have to delegate initialization to a designated initializer, a convenience initializer needs to call a designated initializer at some point. This is necessary to fully initialize the instance that's being initialized.
With both model classes implemented, it is time to refactor the ViewController and AddItemViewController classes. Let's start with the latter.
2. Refactoring AddItemViewController
Step 1: Update the AddItemViewControllerDelegate Protocol
The only changes we need to make in the AddItemViewController class are related to the AddItemViewControllerDelegate protocol. In the protocol declaration, change the type of didAddItem from String to ToDo, the model class we implemented earlier.
This means that we also need to update the create(_:) action in which we invoke the delegate method. In the updated implementation, we create a ToDo instance, passing it to the delegate method.
@IBAction func create(_ sender: Any) {
if let name = textField.text {
// Create Item
let item = ToDo(name: name, done: false)
// Notify Delegate
delegate?.controller(self, didAddItem: item)
}
}
3. Refactoring ViewController
Step 1: Update the items Property
The ViewController class requires a bit more work. We first need to change the type of the items property to [ToDo], an array of ToDo instances.
This also means that we need to refactor a few other methods, such as the tableView(_:cellForRowAt:) method shown below. Because the items array now contains ToDo instances, checking if an item is marked as done is much simpler. We use Swift's ternary conditional operator to update the table view cell's accessory type.
When the user deletes an item, we only need to update the items property by removing the corresponding ToDo instance. This is reflected in the implementation of the tableView(_:commit:forRowAt:) method shown below.
Updating the state of an item when the user taps a row is handled in the tableView(_:didSelectRowAt:) method. The implementation of this UITableViewDelegate method is much simpler thanks to the ToDo class.
The corresponding ToDo instance is updated, and this change is reflected by the table view. To save the state, we invoke saveItems() instead of saveCheckedItems().
Step 4: Add Item View Controller Delegate Methods
Because we updated the AddItemViewControllerDelegate protocol, we also need to update the ViewController's implementation of this protocol. The change, however, is simple. We only need to update the method signature.
func controller(_ controller: AddItemViewController, didAddItem: ToDo) {
// Update Data Source
items.append(didAddItem)
// Save State
saveItems()
// Reload Table View
tableView.reloadData()
// Dismiss Add Item View Controller
dismiss(animated: true)
}
Step 5: Save Items
The pathForItems() Method
Instead of storing the items in the user defaults database, we're going to store them in the application's documents directory. Before we update the loadItems() and saveItems() methods, we're going to implement a helper method named pathForItems(). The method is private and returns a path, the location of the items in the documents directory.
private func pathForItems() -> String {
guard let documentsDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first,
let url = URL(string: documentsDirectory) else {
fatalError("Documents Directory Not Found")
}
return url.appendingPathComponent("items").path
}
We first fetch the path to the documents directory in the application's sandbox by invoking NSSearchPathForDirectoriesInDomains(_:_:_:). Because this method returns an array of strings, we grab the first item.
Notice that we use a guard statement to make sure the value returned by NSSearchPathForDirectoriesInDomains(_:_:_:) is valid. We throw a fatal error if this operation fails. This immediately terminates the application. Why do we do this? If the operating system is unable to hand us the path to the documents directory, we have bigger problems to worry about.
The value we return from pathForItems() is composed of the path to the documents directory with the string "items" appended to it.
The loadItems() Method
The loadItems method changes quite a bit. We first store the result of pathForItems() in a constant, path. We then unarchive the object archived at that path and downcast it to an optional array of ToDo instances. We use optional binding to unwrap the optional and assign it to a constant, items. In the if clause, we assign the value stored in items to the items property.
private func loadItems() {
let path = pathForItems()
if let items = NSKeyedUnarchiver.unarchiveObject(withFile: path) as? [ToDo] {
self.items = items
}
}
The saveItems() Method
The saveItems() method is short and simple. We store the result of pathForItems() in a constant, path, and invoke archiveRootObject(_:toFile:) on NSKeyedArchiver, passing in the items property and path. We print the result of the operation to the console.
Let's end with the fun part, deleting code. Start by removing the checkedItems property at the top since we no longer need it. As a result, we can also remove the loadCheckedItems() and saveCheckedItems() methods, and every reference to these methods in the ViewController class.
Build and run the application to see if everything is still working. The data model makes the application's code much simpler and more reliable. Thanks to the ToDo class, managing the items in our list much is now easier and less error-prone.
Conclusion
In this lesson, we refactored the data model of our application. You learned more about object-oriented programming and inheritance. Instance initialization is an important concept in Swift, so make sure you understand what we've covered in this lesson. You can read more about initialization and initializer delegation in The Swift Programming Language.
In the meantime, check out some of our other courses and tutorials about Swift language iOS development!
Testing is a crucial part of Android development, allowing you to iron out all the bugs, errors and performance problems that may be lurking in your app, before you unleash it on the general public.
Every time you encounter an error, Android generates an error message, and then either displays that message as part of Android Studio’s Logcat Monitor or as a dialogue on the device you’re using to test your app.
These error messages are typically short and to the point, and at first glance may not seem all that helpful. However, these messages actually contain all the information you need to get your project back on track—you just need to know how to decipher them!
In this article, we’re going to take an in-depth look at the 13 error messages you’re most likely to encounter when developing any Android app. We’ll be scrutinising what each of these error messages really means, examining all the possible reasons why you might encounter each error and, most importantly, sharing step-by-step instructions on how you can resolve them.
Spotting Error Messages
There’s a wide range of error messages you may encounter when testing your app, ranging from severe errors that will cause your app to crash the very first time you try to install it on a target device to more subtle errors that degrade your application’s performance over time.
Depending on the kind of error you encounter, Android will display the error message either on the device you’re using to test your app or in Android Studio.
Spotting error messages that appear on a physical device or AVD is easy—you just need to be paying attention to any dialogues that appear on your device's screen! However, spotting errors that appear in Android Studio can be tricky, as the Logcat Monitor records a huge amount of information, making it easy to miss important error messages.
The easiest way to make sure you don’t miss out any error messages is to open Logcat Monitor’s Verbose dropdown and set it to Error, which will filter out everything except error messages.
1. R.layout.main Cannot Be Found / Cannot Resolve Symbol R
This error is caused when Android Studio can’t generate your R.java file correctly, and it can often crop up out of nowhere—one minute everything will be working fine, and the next minute every part of your project is failing to compile. To make matters worse, when Android Studio encounters the R.layout error, it’ll usually flag all your layout resource files as containing errors, which makes it difficult to know where to start looking for the source of the error.
Often, the most effective solution is the simplest: clean and rebuild your project. Select Build > Clean Project from the Android Studio toolbar, wait a few moments, and then build your project by selecting Build > Rebuild Project.
If a single clean/rebuild cycle doesn’t work, then try repeating this process a few times, as some developers have reported positive results after completing multiple clean/rebuild cycles in quick succession.
If you encounter this error after moving some files and directories around, then it’s possible that the R.layout error is being caused by a mismatch between Android Studio’s cache and your project’s current layout. If you suspect this may be the case, then select File > Invalidate Caches / Restart > Invalidate and Restart from Android Studio’s toolbar.
Issues with the names of your resources can also prevent the R.java file from being created correctly, so check that you don't have multiple resources with the same name and that none of your file names contain invalid characters. Android Studio only supports lowercase a-z, 0-9, full stops and underscores, and a single invalid character can cause an R.layout error across your entire project, even if you don’t actually use this resource anywhere in your project!
If you do identify and resolve an error, but Android Studio is still displaying the R.layout error, then you may need to complete a clean/rebuild cycle before Android Studio properly registers your changes.
2. Too Many Field References….Max is 65,536
When you compile your app, the APK contains executable bytecode files in the form of Dalvik Executable (DEX) bytecode files. The DEX specification states that a single DEX file can reference a maximum of 65,536 methods, and if you encounter the Too many fields… error then it means your app has gone over this limit. Note that this is a limitation on the number of methods your project references, and not the number of methods your project defines.
If you encounter this error, then you can either:
Reduce the number of references in your project. One of the most effective ways of trimming your method references is to review your application’s dependencies, as these are often one of the biggest contributors of method references.
Configure your app to use more than one DEX file, by enabling multidex.
The process of enabling multidex support will vary depending on the versions of Android your project supports.
If you’re targeting Android 5.0 or higher, then the first step is opening your module-level build.gradle file and setting multiDexEnabled to true:
However, if your minSdkVersion is 20 or lower, then you’ll need to add the multiDexEnabled true attribute and then add the multidex support library as a project dependency:
If your project doesn’t override the Application class, then you’ll need to extend MultiDexApplication instead:
public class MyApplication extends MultiDexApplication
Finally, if you do override the Application class but can’t change the base class, then you can enable multidex by overriding the attachBaseContext() method and calling MultiDex.install(this), for example:
If you’re getting a JDK error whenever you try to build your app, then it means Android Studio is struggling to find where the JDK is installed on your development machine.
To fix this error:
Select File > Project structure… from the Android Studio toolbar.
Select SDK Location from the left-hand menu.
Make sure the Use embedded JDK checkbox is selected.
If this doesn’t solve the problem, then navigate back to File > Project structure… > SDK Location, and manually enter the full file path for your JDK. If you’re not sure where the JDK is installed on your development machine, then you can find out by opening the Terminal (Mac) or Command Prompt (Windows) and entering the following command:
/usr/libexec/java_home
4. Error Installing APK
While AVDs are great for testing your app across a wide range of different hardware and software, you should always test your app on at least one physical Android smartphone or tablet. However, Android Studio’s ability to recognize a connected Android device is notoriously hit and miss.
If you’ve attached your device to your development machine but are encountering an Error installing APK message whenever you try to install your APK, or your device isn’t even appearing in the Select Deployment Target window, then try the following fixes:
Check USB debugging is enabled.
Open your device’s Settings, then select Developer Options, and make sure USB Debugging is enabled. If you don’t see Developer Options in the Settings menu, then select About Phone and keep tapping Build Number until a You are now a developer notification appears. Return to the main Settings screen, and you should find that Developer Options has been added.
Check your smartphone or tablet’s screen.
Sometimes your device may require some additional input before it connects to your development machine. For example, it may be asking you to choose between different modes, or to explicitly authorize the connection.
Make sure you have the correct USB driver installed.
If you're developing on Windows, then you’ll need to download the appropriate OEM USB driver for your device. If you’re a Nexus user, then you can download the Google USB driver through Android Studio’s SDK Manager.
Check that your device meets your project’s minimum SDK requirements.
You’ll find your project’s minimum SDK in your module-level gradle.build file, and can check what version of Android is installed on your device by opening its Settings and swiping to the About Phone section.
Try restarting your adb (Android Debug Bridge) process.
Open a Terminal or Command Prompt window, and then change directory (cd), so it’s pointing at your platform-tools window, for example:
cd /Users/Downloads/adt-bundle-mac/sdk/platform-tools
Then, terminate and restart the adb process by entering the following commands, one after the other:
./adb kill-server
./adb start-server
Restart everything!
If all else fails, then try disconnecting and then reconnecting your device, restarting your device, restarting Android Studio and, as an absolute last resort, restarting your development machine.
5. INSTALL_FAILED_INSUFFICIENT_STORAGE
If you encounter this error when attempting to install your project, then it means the target device doesn’t have enough memory.
If you’re trying to install your project on an AVD, then you should check how much space you’ve assigned this particular AVD:
Launch the AVD Manager.
Find the AVD in question, and click its accompanying Edit this AVD icon.
In the window that appears, click Show Advanced Settings.
Scroll to the Memory and Storage section.
This section lists the various types of memory you've allocated to this particular AVD. If any of these values are unusually low, then you should increase them to more closely reflect the memory that’s available to your typical Android smartphone or tablet:
RAM. The amount of RAM available to the emulated device.
VM Heap. How much heap space (i.e. memory) is allocated to the Virtual Machine (VM) of the emulated smartphone or tablet.
Internal Storage. The amount of non-removable memory available to the emulated device.
SD card. The amount of removable memory available. If you want to use a virtual SD card that’s managed by Android Studio, then select Studio-managed and enter the size of the virtual SD card you want to create (the minimum recommended value is 100 MB). Alternatively, you can manage the SD card “space” in a file, by selecting External file and then specifying the location you want to use.
If there’s nothing odd about your AVD’s memory, or you’re trying to install your app on a physical Android smartphone or tablet, then this error usually means that your compiled app is simply too large. An application that takes a significant bite out of the device’s memory at install time is never going to go down well.
If you need to dramatically reduce the size of your APK, then try the following techniques:
Use ProGuard to remove unused classes, fields, methods, and attributes. To enable ProGuard, open your module-level build.gradle file and add the following:
buildTypes {
release {
//Enable ProGuard//
minifyEnabled true
//Since we want to reduce our APK size as much as possible, I’m using the settings from the proguard-android-optimize.txt file//
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
Use the aapt tool to optimize your drawables with lossless compression, or use a program that’s designed to reduce the size of your PNG files (zopflipng, pngcrush, OptiPNG, TinyPNG, or pngquant) or the size of your JPEGs (packJPG). Alternatively, you may want to try replacing your PNG and JPEG files with images in the WebP format.
Remember to remove all debug-related functionality from the release version of your app. Android doesn’t require this information to run, so it’s just taking up unnecessary space.
Scour your project for any duplicate resources. Even lightweight resources like duplicate strings contribute something towards your final APK size.
Use Lint to identify any resources that aren’t referenced anywhere in your code, and remove these resources. To run Lint, select Analyze > Inspect Code... from the Android Studio toolbar.
Enable resource shrinking, by adding shrinkResources true to your project’s build.gradle file.
If you need to use variations of the same image, then use the same base image and customize it at runtime, rather than adding multiple versions of the same image to your project. For example, you can apply different colours to an image using android:tint and tintMode, and you can rotate an image using android:fromDegrees, android:toDegrees, android:pivotX, and android:pivotY.
Optimize your libraries. Try to remove any unnecessary or memory-intensive libraries from your project. If you do need to use a large library, then check whether there’s any way you can optimize this library for the mobile environment, as external library code often isn’t written with mobile in mind. You should also bear in mind that many libraries contain a large amount of localized strings. If your app doesn’t officially support these libraries, then you may be able to reduce the size of the library by telling Gradle not to include these strings in your compiled APK. To specify the languages that your app officially supports, open your module-level build.gradle file and use the resConfigs attribute. For example, here we’re specifying that we want to include only English-language strings in our project:
android {
defaultConfig {
resConfigs "en"
Consider whether your APK contains a large amount of content that the individual user may download but never use. For example, a device with an hdpi screen doesn’t have much use for xxxhdpi assets! One of the most effective ways of reducing the size of your APK is to separate it into multiple APKs, so when the user downloads your app, they’ll receive an APK that contains only the code and resources that make sense for their particular device. You’ll find more information on creating APKs that target different screen densities and specific ABIs (application binary interfaces) over at the official Android docs.
6. ActivityNotFoundException
An ActivityNotFoundException occurs when a call to startActivity(Intent) or one of its variants fails because the Activity can’t execute the given Intent.
The most common cause of an ActivityNotFoundException is forgetting to declare an activity in your manifest, so open your manifest and check that you’ve declared all your activities. You should also check that you’ve declared each activity correctly, using either a fully qualified class name or a full stop as a shorthand for the package name. For example, both of the following are valid:
If you can’t spot any problems with your manifest, then there are a few other potential causes of ActivityNotFoundExceptions. Firstly, if you encounter this error after moving an Activity class from one package to another, then it’s possible that you’ve confused Android Studio and just need to clean and rebuild your project.
An ActivityNotFoundException can also be caused if an error in the target Activity is not loading correctly. To check whether this is occurring in your project, put your intent code inside a try-catch block:
Run your application again, and then take a look at Android Studio’s Logcat Monitor to see whether it’s captured any exceptions that may be preventing the target activity from being created. If this is the case, then resolving these errors should solve the ActivityNotFoundException, too.
7. ClassCastException
The ClassCastException error is related to Java’s type conversion feature, which allows you to cast variables of one type to another. You encounter a ClassCastException when you try to cast an object to a class of which it’s not an instance. For example, both of the following code snippets will result in a ClassCastException:
Object x = new Integer(0);
System.out.println((String)x);
This error message contains information about the line that’s causing the ClassCastException error, so navigate to this part of your project, check what objects are being cast there, and resolve any mismatch.
If you can’t spot a problem with your casting, then consider whether you’ve recently moved some Views around in your layout resource files, as some users have reported encountering a ClassCastException after rearranging their Views. If you suspect this may be the cause of your ClassCastException, then tell Android Studio to regenerate your layout files from scratch, by performing a clean/rebuild cycle. This forces Android Studio to properly register your recent layout changes, which should resolve your ClassCastException.
8. NullPointerException
In Java, when you declare a reference variable, you’re actually creating a pointer to an object. You can declare that an object is currently pointing at an unknown piece of data by assigning a null value to that object’s reference. Null values can be useful in coding some design patterns, but if you encounter a NullPointerException (NPE) then it means that you’ve tried to use a reference that’s pointing at a null value, as though it were referencing an object. Since there’s no code to execute in the location where this reference is pointing, you wind up with an NPE.
An NPE is usually accompanied by information about where this exception was caught, so the Logcat Monitor should contain the exact line where this error occurred. Navigate to this area of your project and identify the reference that equals null. You'll then need to find the location where the value should be set, and set it.
The findViewById method can also return null if the requested View can’t be found, so if your NPE is occurring in a line that contains a findViewById, check that you’ve initialized the layout that contains this View. Also be on the lookout for any spelling mistakes or typos that may have crept into your findViewById call, as these can also result in an NPE.
To avoid NPEs occurring in your project, make sure all your objects are initialized before you attempt to use them, and always verify that a variable isn’t null before you request a method or field from that object.
9. Application Not Responding Error
This is an error that appears as a dialogue on the Android device or AVD you’re using to test your app. The Application Not Responding (ANR) error occurs when your app’s UI freezes and remains unresponsive to user input for more than five seconds. This usually happens because your app is trying to perform lengthy or intensive operations on Android’s main UI thread.
In Android, the main UI thread is responsible for dispatching all user input events to the appropriate UI widgets, and for updating your app’s UI. However, this thread can only process one task at a time, so if you block the main thread with any long-running or intensive operations, then your UI will be completely unresponsive until this task is complete.
If you encounter an ANR message while testing your app, then you definitely need to take a look at the work you’re performing on the main thread. However, if you don’t explicitly encounter this error but notice that your app sometimes feels sluggish or laggy, then this is an indication that you’re on the verge of an ANR error, and once again you should take a look at the state of your UI thread.
To resolve ANR errors (and near-ANR errors), you need to identify all the operations that have the potential to run slowly, or that require significant processing power, and then move them off the main thread. You do this by creating a worker thread where these operations can be performed with zero risk of blocking the main UI thread.
There are several methods of creating additional threads, but the simplest solution is to use an AsynTask, as this class already contains its own worker thread and an onPostExecute() callback that you can use to communicate with Android’s main UI thread.
However, AsyncTasks are better suited to performing short background operations, so if you need to perform a long-running operation, then you should use a Service or an IntentService instead.
Although moving long-running and intensive tasks off the main thread will have the most impact on your app’s performance, it’s best practice to perform as little work as possible on the main UI thread. Even running a small amount of unnecessary code on the main thread can have an impact on your app’s responsiveness, so once you’ve successfully relocated all of your long-running and intensive operations, you should look at whether there’s any more code that you can move off the main thread.
10. Only the Original Thread That Created a View Hierarchy Can Touch Its Views
In Android, you can update your UI from the main thread only. If you try to access UI elements from any other thread, then you’re going to encounter this error.
To resolve this issue, identify the part of your background task that’s attempting to update the UI and move it to a runOnUiThread, for example:
runOnUiThread(new Runnable() {
@Override
public void run() {
//Update your UI//
}
});
Alternatively, you can use a handler or perform your background work in an AsyncTask, as you can communicate with the main thread using AsyncTask’s onPostExecute() callback method. Finally, if you find yourself regularly switching between threads, then you may want to look into RxAndroid, as this library allows you to create a new thread, schedule work to be performed on this thread, and then post the results to the main thread, all with just a few lines of code.
This exception is thrown when your app attempts to perform networking operations on the main thread, such as sending API requests, connecting to a remote database, or downloading a file. Since network operations can be time-consuming and labour-intensive, they are highly likely to block the main thread, so Android 3.0 (Honeycomb) and higher will throw this error whenever you attempt to make a network request on the main thread.
If you do encounter a NetworkOnMainThreadException, then find the networking code that’s running on your main thread, and move it to a separate thread.
If you do need to make frequent networking requests, then you may want to take a look at Volley, an HTTP library that initiates its own background threads so that all networking requests are performed off the main thread by default.
12. Activity Has Leaked Window That Was Originally Added Here
This error occurs when trying to show a dialogue after exiting the Activity. If you do encounter this issue, then open your Activity and make sure you’re dismissing the dialogue properly, by calling dismiss() in either your Activity’s onDestroy() or onPause() method, for example:
This error occurs when your app makes a memory request that the system can’t meet. If you encounter this error message, then start by ruling out all of the most common memory management mistakes. Check that you’ve remembered to unregister all your broadcast receivers and that you’ve stopped all of your services; make sure you’re not holding onto references in any static member variables, and that you’re not attempting to load any large bitmaps.
If you’ve ruled out all the obvious causes of an OutOfMemoryError, then you’ll need to dig deeper and examine exactly how your app is allocating memory, as chances are there are a few areas where you can improve your app’s memory management.
Android Studio has a whole area dedicated to helping you analyze your app’s memory usage, so start by selecting View > Tools Window from the Android Studio toolbar. At this point you’ll see either an Android Monitor or Android Profiler option, depending on the version of Android Studio you have installed.
We’ve discussed working with the Memory Monitor on this website before, but since Android Profiler is a new addition to Android Studio, let’s take a quick look at its major features.
When you open Android Profiler, it starts recording three pieces of information automatically.
Since we’re interested in the way our app is using memory, give the Memory section a click, which will launch the Memory Profiler.
The Memory Profiler consists of a timeline that displays the different kinds of memory currently being allocated by your app, for example Java, native, and stack. Above this graph you’ll find a row of icons that you can use to trigger different actions:
Force a garbage collection event.
Take an Hprof snapshot of the application memory. This is a snapshot of all the objects in your app’s heap, including the kind of objects your app is allocating, the number of allocated objects, and how much space these objects are taking up.
Record memory allocations. By recording your app’s memory allocations while performing certain actions, you can identify the specific operations that are consuming too much memory.
To identify the parts of your application that are responsible for the OutOfMemoryError, spend some time interacting with your app, and monitor how your app’s memory allocations change in response to different actions. Once you’ve identified the section of your project that’s causing the problem, spend some time scrutinising it for any memory leaks, as well as any inefficiencies in the way it’s using memory.
Conclusion
In this article we looked at 13 of the error messages you’re most likely to encounter when developing for Android. We discussed all the different factors that can contribute towards these errors, and the steps you need to take to resolve them.
If you’re being plagued by an error message that we didn’t cover, then your first step should be copy/pasting the entire error message into Google, as this will often turn up threads and blog posts where people are discussing how to solve this particular error.
And, if you can’t find a solution anywhere on the web, then you can always reach out to the Android community for help directly, by posting your question to Stack Overflow.
While you're here, check out some of our other posts on Android app development!
Testing is a crucial part of Android development, allowing you to iron out all the bugs, errors and performance problems that may be lurking in your app, before you unleash it on the general public.
Every time you encounter an error, Android generates an error message, and then either displays that message as part of Android Studio’s Logcat Monitor or as a dialogue on the device you’re using to test your app.
These error messages are typically short and to the point, and at first glance may not seem all that helpful. However, these messages actually contain all the information you need to get your project back on track—you just need to know how to decipher them!
In this article, we’re going to take an in-depth look at the 13 error messages you’re most likely to encounter when developing any Android app. We’ll be scrutinising what each of these error messages really means, examining all the possible reasons why you might encounter each error and, most importantly, sharing step-by-step instructions on how you can resolve them.
Spotting Error Messages
There’s a wide range of error messages you may encounter when testing your app, ranging from severe errors that will cause your app to crash the very first time you try to install it on a target device to more subtle errors that degrade your application’s performance over time.
Depending on the kind of error you encounter, Android will display the error message either on the device you’re using to test your app or in Android Studio.
Spotting error messages that appear on a physical device or AVD is easy—you just need to be paying attention to any dialogues that appear on your device's screen! However, spotting errors that appear in Android Studio can be tricky, as the Logcat Monitor records a huge amount of information, making it easy to miss important error messages.
The easiest way to make sure you don’t miss out any error messages is to open Logcat Monitor’s Verbose dropdown and set it to Error, which will filter out everything except error messages.
1. R.layout.main Cannot Be Found / Cannot Resolve Symbol R
This error is caused when Android Studio can’t generate your R.java file correctly, and it can often crop up out of nowhere—one minute everything will be working fine, and the next minute every part of your project is failing to compile. To make matters worse, when Android Studio encounters the R.layout error, it’ll usually flag all your layout resource files as containing errors, which makes it difficult to know where to start looking for the source of the error.
Often, the most effective solution is the simplest: clean and rebuild your project. Select Build > Clean Project from the Android Studio toolbar, wait a few moments, and then build your project by selecting Build > Rebuild Project.
If a single clean/rebuild cycle doesn’t work, then try repeating this process a few times, as some developers have reported positive results after completing multiple clean/rebuild cycles in quick succession.
If you encounter this error after moving some files and directories around, then it’s possible that the R.layout error is being caused by a mismatch between Android Studio’s cache and your project’s current layout. If you suspect this may be the case, then select File > Invalidate Caches / Restart > Invalidate and Restart from Android Studio’s toolbar.
Issues with the names of your resources can also prevent the R.java file from being created correctly, so check that you don't have multiple resources with the same name and that none of your file names contain invalid characters. Android Studio only supports lowercase a-z, 0-9, full stops and underscores, and a single invalid character can cause an R.layout error across your entire project, even if you don’t actually use this resource anywhere in your project!
If you do identify and resolve an error, but Android Studio is still displaying the R.layout error, then you may need to complete a clean/rebuild cycle before Android Studio properly registers your changes.
2. Too Many Field References….Max is 65,536
When you compile your app, the APK contains executable bytecode files in the form of Dalvik Executable (DEX) bytecode files. The DEX specification states that a single DEX file can reference a maximum of 65,536 methods, and if you encounter the Too many fields… error then it means your app has gone over this limit. Note that this is a limitation on the number of methods your project references, and not the number of methods your project defines.
If you encounter this error, then you can either:
Reduce the number of references in your project. One of the most effective ways of trimming your method references is to review your application’s dependencies, as these are often one of the biggest contributors of method references.
Configure your app to use more than one DEX file, by enabling multidex.
The process of enabling multidex support will vary depending on the versions of Android your project supports.
If you’re targeting Android 5.0 or higher, then the first step is opening your module-level build.gradle file and setting multiDexEnabled to true:
However, if your minSdkVersion is 20 or lower, then you’ll need to add the multiDexEnabled true attribute and then add the multidex support library as a project dependency:
If your project doesn’t override the Application class, then you’ll need to extend MultiDexApplication instead:
public class MyApplication extends MultiDexApplication
Finally, if you do override the Application class but can’t change the base class, then you can enable multidex by overriding the attachBaseContext() method and calling MultiDex.install(this), for example:
If you’re getting a JDK error whenever you try to build your app, then it means Android Studio is struggling to find where the JDK is installed on your development machine.
To fix this error:
Select File > Project structure… from the Android Studio toolbar.
Select SDK Location from the left-hand menu.
Make sure the Use embedded JDK checkbox is selected.
If this doesn’t solve the problem, then navigate back to File > Project structure… > SDK Location, and manually enter the full file path for your JDK. If you’re not sure where the JDK is installed on your development machine, then you can find out by opening the Terminal (Mac) or Command Prompt (Windows) and entering the following command:
/usr/libexec/java_home
4. Error Installing APK
While AVDs are great for testing your app across a wide range of different hardware and software, you should always test your app on at least one physical Android smartphone or tablet. However, Android Studio’s ability to recognize a connected Android device is notoriously hit and miss.
If you’ve attached your device to your development machine but are encountering an Error installing APK message whenever you try to install your APK, or your device isn’t even appearing in the Select Deployment Target window, then try the following fixes:
Check USB debugging is enabled.
Open your device’s Settings, then select Developer Options, and make sure USB Debugging is enabled. If you don’t see Developer Options in the Settings menu, then select About Phone and keep tapping Build Number until a You are now a developer notification appears. Return to the main Settings screen, and you should find that Developer Options has been added.
Check your smartphone or tablet’s screen.
Sometimes your device may require some additional input before it connects to your development machine. For example, it may be asking you to choose between different modes, or to explicitly authorize the connection.
Make sure you have the correct USB driver installed.
If you're developing on Windows, then you’ll need to download the appropriate OEM USB driver for your device. If you’re a Nexus user, then you can download the Google USB driver through Android Studio’s SDK Manager.
Check that your device meets your project’s minimum SDK requirements.
You’ll find your project’s minimum SDK in your module-level gradle.build file, and can check what version of Android is installed on your device by opening its Settings and swiping to the About Phone section.
Try restarting your adb (Android Debug Bridge) process.
Open a Terminal or Command Prompt window, and then change directory (cd), so it’s pointing at your platform-tools window, for example:
cd /Users/Downloads/adt-bundle-mac/sdk/platform-tools
Then, terminate and restart the adb process by entering the following commands, one after the other:
./adb kill-server
./adb start-server
Restart everything!
If all else fails, then try disconnecting and then reconnecting your device, restarting your device, restarting Android Studio and, as an absolute last resort, restarting your development machine.
5. INSTALL_FAILED_INSUFFICIENT_STORAGE
If you encounter this error when attempting to install your project, then it means the target device doesn’t have enough memory.
If you’re trying to install your project on an AVD, then you should check how much space you’ve assigned this particular AVD:
Launch the AVD Manager.
Find the AVD in question, and click its accompanying Edit this AVD icon.
In the window that appears, click Show Advanced Settings.
Scroll to the Memory and Storage section.
This section lists the various types of memory you've allocated to this particular AVD. If any of these values are unusually low, then you should increase them to more closely reflect the memory that’s available to your typical Android smartphone or tablet:
RAM. The amount of RAM available to the emulated device.
VM Heap. How much heap space (i.e. memory) is allocated to the Virtual Machine (VM) of the emulated smartphone or tablet.
Internal Storage. The amount of non-removable memory available to the emulated device.
SD card. The amount of removable memory available. If you want to use a virtual SD card that’s managed by Android Studio, then select Studio-managed and enter the size of the virtual SD card you want to create (the minimum recommended value is 100 MB). Alternatively, you can manage the SD card “space” in a file, by selecting External file and then specifying the location you want to use.
If there’s nothing odd about your AVD’s memory, or you’re trying to install your app on a physical Android smartphone or tablet, then this error usually means that your compiled app is simply too large. An application that takes a significant bite out of the device’s memory at install time is never going to go down well.
If you need to dramatically reduce the size of your APK, then try the following techniques:
Use ProGuard to remove unused classes, fields, methods, and attributes. To enable ProGuard, open your module-level build.gradle file and add the following:
buildTypes {
release {
//Enable ProGuard//
minifyEnabled true
//Since we want to reduce our APK size as much as possible, I’m using the settings from the proguard-android-optimize.txt file//
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
Use the aapt tool to optimize your drawables with lossless compression, or use a program that’s designed to reduce the size of your PNG files (zopflipng, pngcrush, OptiPNG, TinyPNG, or pngquant) or the size of your JPEGs (packJPG). Alternatively, you may want to try replacing your PNG and JPEG files with images in the WebP format.
Remember to remove all debug-related functionality from the release version of your app. Android doesn’t require this information to run, so it’s just taking up unnecessary space.
Scour your project for any duplicate resources. Even lightweight resources like duplicate strings contribute something towards your final APK size.
Use Lint to identify any resources that aren’t referenced anywhere in your code, and remove these resources. To run Lint, select Analyze > Inspect Code... from the Android Studio toolbar.
Enable resource shrinking, by adding shrinkResources true to your project’s build.gradle file.
If you need to use variations of the same image, then use the same base image and customize it at runtime, rather than adding multiple versions of the same image to your project. For example, you can apply different colours to an image using android:tint and tintMode, and you can rotate an image using android:fromDegrees, android:toDegrees, android:pivotX, and android:pivotY.
Optimize your libraries. Try to remove any unnecessary or memory-intensive libraries from your project. If you do need to use a large library, then check whether there’s any way you can optimize this library for the mobile environment, as external library code often isn’t written with mobile in mind. You should also bear in mind that many libraries contain a large amount of localized strings. If your app doesn’t officially support these libraries, then you may be able to reduce the size of the library by telling Gradle not to include these strings in your compiled APK. To specify the languages that your app officially supports, open your module-level build.gradle file and use the resConfigs attribute. For example, here we’re specifying that we want to include only English-language strings in our project:
android {
defaultConfig {
resConfigs "en"
Consider whether your APK contains a large amount of content that the individual user may download but never use. For example, a device with an hdpi screen doesn’t have much use for xxxhdpi assets! One of the most effective ways of reducing the size of your APK is to separate it into multiple APKs, so when the user downloads your app, they’ll receive an APK that contains only the code and resources that make sense for their particular device. You’ll find more information on creating APKs that target different screen densities and specific ABIs (application binary interfaces) over at the official Android docs.
6. ActivityNotFoundException
An ActivityNotFoundException occurs when a call to startActivity(Intent) or one of its variants fails because the Activity can’t execute the given Intent.
The most common cause of an ActivityNotFoundException is forgetting to declare an activity in your manifest, so open your manifest and check that you’ve declared all your activities. You should also check that you’ve declared each activity correctly, using either a fully qualified class name or a full stop as a shorthand for the package name. For example, both of the following are valid:
If you can’t spot any problems with your manifest, then there are a few other potential causes of ActivityNotFoundExceptions. Firstly, if you encounter this error after moving an Activity class from one package to another, then it’s possible that you’ve confused Android Studio and just need to clean and rebuild your project.
An ActivityNotFoundException can also be caused if an error in the target Activity is not loading correctly. To check whether this is occurring in your project, put your intent code inside a try-catch block:
Run your application again, and then take a look at Android Studio’s Logcat Monitor to see whether it’s captured any exceptions that may be preventing the target activity from being created. If this is the case, then resolving these errors should solve the ActivityNotFoundException, too.
7. ClassCastException
The ClassCastException error is related to Java’s type conversion feature, which allows you to cast variables of one type to another. You encounter a ClassCastException when you try to cast an object to a class of which it’s not an instance. For example, both of the following code snippets will result in a ClassCastException:
Object x = new Integer(0);
System.out.println((String)x);
This error message contains information about the line that’s causing the ClassCastException error, so navigate to this part of your project, check what objects are being cast there, and resolve any mismatch.
If you can’t spot a problem with your casting, then consider whether you’ve recently moved some Views around in your layout resource files, as some users have reported encountering a ClassCastException after rearranging their Views. If you suspect this may be the cause of your ClassCastException, then tell Android Studio to regenerate your layout files from scratch, by performing a clean/rebuild cycle. This forces Android Studio to properly register your recent layout changes, which should resolve your ClassCastException.
8. NullPointerException
In Java, when you declare a reference variable, you’re actually creating a pointer to an object. You can declare that an object is currently pointing at an unknown piece of data by assigning a null value to that object’s reference. Null values can be useful in coding some design patterns, but if you encounter a NullPointerException (NPE) then it means that you’ve tried to use a reference that’s pointing at a null value, as though it were referencing an object. Since there’s no code to execute in the location where this reference is pointing, you wind up with an NPE.
An NPE is usually accompanied by information about where this exception was caught, so the Logcat Monitor should contain the exact line where this error occurred. Navigate to this area of your project and identify the reference that equals null. You'll then need to find the location where the value should be set, and set it.
The findViewById method can also return null if the requested View can’t be found, so if your NPE is occurring in a line that contains a findViewById, check that you’ve initialized the layout that contains this View. Also be on the lookout for any spelling mistakes or typos that may have crept into your findViewById call, as these can also result in an NPE.
To avoid NPEs occurring in your project, make sure all your objects are initialized before you attempt to use them, and always verify that a variable isn’t null before you request a method or field from that object.
9. Application Not Responding Error
This is an error that appears as a dialogue on the Android device or AVD you’re using to test your app. The Application Not Responding (ANR) error occurs when your app’s UI freezes and remains unresponsive to user input for more than five seconds. This usually happens because your app is trying to perform lengthy or intensive operations on Android’s main UI thread.
In Android, the main UI thread is responsible for dispatching all user input events to the appropriate UI widgets, and for updating your app’s UI. However, this thread can only process one task at a time, so if you block the main thread with any long-running or intensive operations, then your UI will be completely unresponsive until this task is complete.
If you encounter an ANR message while testing your app, then you definitely need to take a look at the work you’re performing on the main thread. However, if you don’t explicitly encounter this error but notice that your app sometimes feels sluggish or laggy, then this is an indication that you’re on the verge of an ANR error, and once again you should take a look at the state of your UI thread.
To resolve ANR errors (and near-ANR errors), you need to identify all the operations that have the potential to run slowly, or that require significant processing power, and then move them off the main thread. You do this by creating a worker thread where these operations can be performed with zero risk of blocking the main UI thread.
There are several methods of creating additional threads, but the simplest solution is to use an AsynTask, as this class already contains its own worker thread and an onPostExecute() callback that you can use to communicate with Android’s main UI thread.
However, AsyncTasks are better suited to performing short background operations, so if you need to perform a long-running operation, then you should use a Service or an IntentService instead.
Although moving long-running and intensive tasks off the main thread will have the most impact on your app’s performance, it’s best practice to perform as little work as possible on the main UI thread. Even running a small amount of unnecessary code on the main thread can have an impact on your app’s responsiveness, so once you’ve successfully relocated all of your long-running and intensive operations, you should look at whether there’s any more code that you can move off the main thread.
10. Only the Original Thread That Created a View Hierarchy Can Touch Its Views
In Android, you can update your UI from the main thread only. If you try to access UI elements from any other thread, then you’re going to encounter this error.
To resolve this issue, identify the part of your background task that’s attempting to update the UI and move it to a runOnUiThread, for example:
runOnUiThread(new Runnable() {
@Override
public void run() {
//Update your UI//
}
});
Alternatively, you can use a handler or perform your background work in an AsyncTask, as you can communicate with the main thread using AsyncTask’s onPostExecute() callback method. Finally, if you find yourself regularly switching between threads, then you may want to look into RxAndroid, as this library allows you to create a new thread, schedule work to be performed on this thread, and then post the results to the main thread, all with just a few lines of code.
This exception is thrown when your app attempts to perform networking operations on the main thread, such as sending API requests, connecting to a remote database, or downloading a file. Since network operations can be time-consuming and labour-intensive, they are highly likely to block the main thread, so Android 3.0 (Honeycomb) and higher will throw this error whenever you attempt to make a network request on the main thread.
If you do encounter a NetworkOnMainThreadException, then find the networking code that’s running on your main thread, and move it to a separate thread.
If you do need to make frequent networking requests, then you may want to take a look at Volley, an HTTP library that initiates its own background threads so that all networking requests are performed off the main thread by default.
12. Activity Has Leaked Window That Was Originally Added Here
This error occurs when trying to show a dialogue after exiting the Activity. If you do encounter this issue, then open your Activity and make sure you’re dismissing the dialogue properly, by calling dismiss() in either your Activity’s onDestroy() or onPause() method, for example:
This error occurs when your app makes a memory request that the system can’t meet. If you encounter this error message, then start by ruling out all of the most common memory management mistakes. Check that you’ve remembered to unregister all your broadcast receivers and that you’ve stopped all of your services; make sure you’re not holding onto references in any static member variables, and that you’re not attempting to load any large bitmaps.
If you’ve ruled out all the obvious causes of an OutOfMemoryError, then you’ll need to dig deeper and examine exactly how your app is allocating memory, as chances are there are a few areas where you can improve your app’s memory management.
Android Studio has a whole area dedicated to helping you analyze your app’s memory usage, so start by selecting View > Tools Window from the Android Studio toolbar. At this point you’ll see either an Android Monitor or Android Profiler option, depending on the version of Android Studio you have installed.
We’ve discussed working with the Memory Monitor on this website before, but since Android Profiler is a new addition to Android Studio, let’s take a quick look at its major features.
When you open Android Profiler, it starts recording three pieces of information automatically.
Since we’re interested in the way our app is using memory, give the Memory section a click, which will launch the Memory Profiler.
The Memory Profiler consists of a timeline that displays the different kinds of memory currently being allocated by your app, for example Java, native, and stack. Above this graph you’ll find a row of icons that you can use to trigger different actions:
Force a garbage collection event.
Take an Hprof snapshot of the application memory. This is a snapshot of all the objects in your app’s heap, including the kind of objects your app is allocating, the number of allocated objects, and how much space these objects are taking up.
Record memory allocations. By recording your app’s memory allocations while performing certain actions, you can identify the specific operations that are consuming too much memory.
To identify the parts of your application that are responsible for the OutOfMemoryError, spend some time interacting with your app, and monitor how your app’s memory allocations change in response to different actions. Once you’ve identified the section of your project that’s causing the problem, spend some time scrutinising it for any memory leaks, as well as any inefficiencies in the way it’s using memory.
Conclusion
In this article we looked at 13 of the error messages you’re most likely to encounter when developing for Android. We discussed all the different factors that can contribute towards these errors, and the steps you need to take to resolve them.
If you’re being plagued by an error message that we didn’t cover, then your first step should be copy/pasting the entire error message into Google, as this will often turn up threads and blog posts where people are discussing how to solve this particular error.
And, if you can’t find a solution anywhere on the web, then you can always reach out to the Android community for help directly, by posting your question to Stack Overflow.
While you're here, check out some of our other posts on Android app development!
Vue is a popular JavaScript framework for web apps with easy binding between data in memory and the user interface. Now Weex allows us to code native mobile apps using the Vue framework!
Why do we use Vue and other data binding frameworks like Angular and React? What's so special about them? We use them to ensure that application data which is stored in memory stays in sync with the user interface and vice versa.
We also use these frameworks to enable us to build applications very quickly, and in a way that is optimized for performance.
In this tutorial I'll show you how to use the Vue framework, in particular how to understand its concepts of data binding and templates. Then, I'll go on to introduce the Weex platform, for coding native mobile apps using Vue!
Data Binding
Let's look at a simple example to understand how these frameworks can actually save us time. What we want is a simple input field that stays in sync with our application data. Our app itself can change the data programmatically, and the user can change it via user input, so we need to be watching both the UI and application data.
Writing code that would support this data binding would be verbose. We would need to create event listeners and proxy objects and observables to capture any changes in the application data. And these complexities just grow and grow as more types of data and inputs are added. Vue and other data binding frameworks prevent us having to write all of that binding code.
With Vue, if our user changes some input data, it will sync back to the application data like so:
Or if the application changes the data, it will update the user interface like this:
When we keep the user interface and the app data together in sync, it means our app does exactly what we expect it to do. Vue will manage all of this and allow other powerful data binding processes to occur.
Getting Set Up
Now that we know why we use these frameworks, let's set up a simple Vue application to run some examples. Create a new HTML file anywhere on your computer and paste the following code into it:
<!DOCTYPE html><html><head><title>Tuts+ Vue</title><script src="https://unpkg.com/vue/dist/vue.min.js"></script></head><body><div id="app"></div><script>
var app = new Vue({ el: '#app' });</script></body></html>
This is a simple HTML file that links to the Vue JS library. It contains a div element with the ID of app. Inside the script tags we have a variable called app that we use to point to our view—I'll explain this part later.
Then we create a new Vue instance, or "view". This constructor tells the framework which element will contain our application UI and keep it in sync with the application data.
Now double click on the HTML file to open it in the browser, and open the browser console window.
Text Interpolation
Text interpolation lets us embed expressions in our HTML code that will be interpreted when the page is rendered. The expressions are live, so if the data they depend on is changed, the page will be updated in real time. This is known as declarative rendering, which allows us to place expressions anywhere within an element's content. Let's review with a simple example.
JS
<script>
var app = new Vue({
el: '#app',
data:
{
message: "Hello world"
}
});</script>
Our JavaScript code now contains a data object that will store all of our application data for this view. Within it, I've created the message property with the string "Hello world".
The expressions are indicated with double braces syntax. Vue JS manages the dependencies of each expression and updates the page live if they change. In our case, we have two expressions, with the message property as a dependency of each one. The first expression {{ message }} just displays the message property. The second {{ message.split('').reverse().join('') }} expression takes the message property, and then:
Splits the string into an array: [ "h","e","l","l","o"," ","w","o","r","l","d" ]
Reverses the order of the array: [ "d","l","r","o","w"," ","o","l","l","e","h" ]
Joins the array together to get "dlrow olleh", which is the same message but printed backwards.
Now open the browser and refresh the HTML page. Then open the browser's console window and assign app.message a new value and see how the output is updated in the browser.
You can also use math in template expressions, or structured data in objects. Actually, as you might have guessed, Vue expressions are just JavaScript. Access to global variables is sandboxed, though, so you can access Math, but not window. This helps keep your apps safe—and your users too.
By using Vue with just a few lines of code, we can have advanced two-way data binding. This ensures that the user interface and the application data are in sync with relative ease.
Directives
Directives are another way of binding data in our views. We can use them to bind application data to attributes, events, input fields, and repeatable data. Let's go through each directive and discover how they work.
v-bind: bind an attribute value
v-model: bind app data to an input element like a textarea
v-on: define an event handler
v-for: bind to an array or object
v-bind Directive
The v-bind directive is used for attribute data binding. It allows us to bind application data to an attribute's value. Think of it like text interpolation but within an attribute. You can bind class, id, value or any other attribute with the v-bind directive.
For this example, I would like to bind our application data to the data-attr attribute using the v-bind directive v-bind:data-attr.
JS
<script>
var app = new Vue({
el: '#app',
data:
{
number: 20,
object:
{
type: "Full Name Object",
names: ["Lawrence","Anothy","Turton"]
}
}
});</script>
In the example above, we bound an attribute called data-attr to a series of different data sources. First, we bound it to the type and names properties of object. Then we bound it to a math expression, and then joined all the bindings together into a single array attribute.
Take a look at the output in the browser: you can click on the Elements tab to see the output of each attribute's value.
Again, you can change the application data in the console to see the data binding process.
Think of the v-bind directive as allowing expressions in attribute values. This is incredibly powerful and easy to establish with the Vue library.
v-model Directive
This directive is used specifically to keep the application data in sync with input fields, text areas, and selection elements. Here's an example:
JS
<script>
var app = new Vue({
el: '#app',
data:
{
message: "message string",
selected:"",
checkedNames: []
}
});</script>
For the first section of this page, we have a text input field and text area, both of which have the v-model directive that keeps these input fields in sync with the message property, using v-model="message". That way, if you change either one, the model will be updated and then the other input field will be updated. Go ahead and try it out!
In the next section we have a drop-down synced to the selected property with v-model="selected". That way, if the selection is changed, our model will be updated.
Finally, we have a multiple selection. I've shown how to do this in two ways: with multiple checkboxes and with a multiple select input. As we can have multiple values, I've created the checkedNames array that can store multiple values. The multiple select input and the checkboxes each have a v-model directive targeting the checkNames property. Give it a try, and they should stay in sync as you make selections.
The v-on Directive
This directive allows us to attach events to HTML elements. This directive can be used on certain elements that can have input events attached to them like hovering, clicks, text input, key presses, and more. For this example, we'll create a simple button with a click event.
JS
<script>
var app = new Vue({
el: '#app',
data:
{
clicked: ""
}
});</script>
In the v-on directive, after the colon, we specify the event we want to attach. In this example, when the click event is fired, we'll run some expressions. First, we open an alert dialog box, and then we change the value of the clicked property. Note that you can call functions in v-on expressions.
v-for Directive
This directive is one of the most powerful of them all. We can watch any object or array for changes and render repeatedly a part of our template code for each property or element found in that object or array. For example, to render an array of names as a list:
JS
<script>
var app = new Vue({
el: '#app',
data:
{
names: [ "Lawrence", "John", "Mike", "Justin" ]
}
});</script>
HTML
<div id="app"><ul><li v-for="name in names">{{ name }}</li></ul></div>
In this example, we first attach the v-for directive onto the element that you want to repeat. The value of the directive specifies the object or array we want to iterate over (names) as well as a variable that will contain the value for each iteration (name). Now, within this repeated element, we can use the name variable in expressions.
If we then modify the array, for example with a method like push, sort or reverse, the template data will automatically be updated. Try running the example and modify the array in the console window.
Components
The data binding process happens in views. Components are just reusable views that we can repeat throughout our application. Each component must have some template data to be rendered as part of the user interface and some application data. You can optionally include some styling if you want.
Let's use the v-for directive again to output list items. But this time, we'll make each item a component. We'll give this component a name so that we can target it within our template code: list-item. Components can also have "registered properties", to which we can assign a value to be assigned when we create the component. In my case, the list-item component will expect to receive a value for the name property, defined with props:["name"].
Let's now create this component. Please add the following code into your script tags:
Also make sure you have the following application data:
var app = new Vue({
el: '#app',
data:
{
names: [ "Lawrence", "John", "Mike", "Justin" ]
}
});
HTML
<div id="app"><ul><list-item v-for="name in names" v-bind:name="name"></list-item></ul></div>
Here you can see the list-item component will be repeated for each name found in the array. Each name will be assigned to the name variable which is defined in the v-for directive. We're passing the name property to the component using v-bind.
Each instance is just a copy of the component. We can modify each copy or instance independently of one another. So think of components as the blueprint and the instances as copies made from the blueprint. Here is the end result:
Compilation and the Vue CLI
We've been working with a very simple HTML file and letting Vue interpret everything at run-time. However, Vue does come with a command-line tool. You may be asking yourself why. One reason is that pre-compiled page templates will perform better than templates that Vue interprets at run-time. Another reason is that, if we were to build a large-scale application and try to fit it into one HTML file, it would quickly become unmanageable.
So we need modularity to break down a large application into smaller pieces.
Installing the Vue CLI
To use the command line, you'll need to open up the console window for your operating system.
For Mac, press Command-Space and then type terminal and hit Return.
For Windows, search for the command prompt in the startup menu, make sure to right click and "open as administrator".
For Linux, press Control-Alt-T.
Before proceeding, make sure you have the latest version of Node.js installed. Then we need to install Webpack, which will compress our project's file size, making it faster in our browser. Then we can install the Vue CLI and run the relevant commands for your platform:
Mac & Linux
sudo npm install -g webpack
sudo npm install -g vue-cli
Windows (make sure you run the console as administrator)
npm install -g webpack
npm install -g vue-cli
That's it! We're now ready to start setting up our advanced Webpack project. Firstly, navigate to the directory where we want to create our project, in my case the desktop, and then create the project. You can replace myapp with any project name you'd like.
vue init webpack myapp
You will be led through a series of questions about your project, prompting you to fill in data like title and description and asking you whether to install third-party plugins. For this example, you can answer no to all of the optional extras. Once it's created, we need to install the dependencies, so navigate to the project directory, and we'll run the install command.
cd myapp
npm install
Let NPM install all the dependency packages, and then we're ready to carry on.
Creating a Vue Project
Once the packages have been installed, we can then run the development server by typing npm run dev. Your browser window should open, displaying the following page.
We won't go through the structure of the entire project, but when you open up the myapp directory you will see the src directory. It contains an App.vue file, a main.js file and, in the components directory, the Hello.vue file.
The Vue files are components. The main.js file configures the initial view and potentially other configurations as well. Let's take a look at the App.vue and Hello.vue files.
Here you can see each Vue component is broken down into three parts:
<template>: the HTML markup that makes up part of the UI.
<script>: application data, filters, methods, computed properties, watchers, and methods.
<style>: the CSS or Sass styling of our components.
Compilation
Compiling all the components together will produce a large-scale application. This means when we're developing, we work on small modular pieces of code, called components, instead of the entire application. In Vue we have single-file components that contain the JavaScript, HTML, and CSS. It'll even take care of transpiling ES6 to ES5, Sass to CSS and Jade to HTML if you choose to use those languages.
You'll notice in the App.vue file, I've highlighted where it imports the Hello.vue component. Components can be nested inside other components!
When compiling, our project is built with the following process:
We're still using the same syntax as in the earlier examples. However, we are now working with smaller Vue files and compiling them together. We also have some more advanced features like the preprocessors and Jade, Sass, ES6 and Webpack compression.
Weex
Now that we understand the Vue library, let's take a quick look to see how we can take our web app and turn it into a native mobile app, installable on iOS or Android.
Weex is a hybrid framework, meaning it allows multiple technologies, just as a hybrid car can use both electric and petrol. In our case, we use JavaScript code from our web app, but we render to a native user interface. Also, from JavaScript we can access the device's native APIs to access hardware like the camera, sensors, and file system.
With Weex, instead of rendering our application to HTML, we use XML. Actually, HTML and XML are very similar, so the syntax will look familiar. This XML will then be converted to native components. Now our JavaScript will be able to talk to those native components just like it talks to the DOM in Vue with HTML. Not only that, but native components can be styled and positioned with CSS including animations, transitions and more, and they can be integrated to the native layer.
That's just a teaser of what Weex can do. In the next tutorials, I'll take you more in depth with Weex, and we'll see how to build some practical cross-platform native apps.
Pros
Vue has a simple syntax and is increasing in popularity very quickly. Weex allows us to build mobile apps using JavaScript and Vue, but still deliver a native app experience.
Cons
Vue itself is stable, but Weex is still in early development—it currently resides in the Apache incubator. But don't worry, Weex will be production ready soon, and it's backed by the tech giant Alibaba. So, if building on a developer preview concerns you, you might wait until Weex reaches full release.
Conclusion
You've now seen how data binding is a key reason for using Vue and similar frameworks. They save us development time and provide a standard way to build applications. You've also seen how to use text interpolation, directives, and events in Vue, and how these features work together to keep app data in sync with the user interface.
We started out with a basic project with only one view. But then we created a more advanced project that had smaller, more modular views called components. Components let us break our project down so it is easier to code and maintain. After, we saw how to use the Vue compiler to allow transpilation of ES6, Jade and Sass into the standard languages, while compressing file sizes to the minimum.
All of this knowledge will help you when you start coding with Weex. Weex will allow us to take our Vue web app and turn it into a mobile app. Weex is a bit different—we use XML with special components, instead of HTML—but we can still use other web technologies, like CSS and JavaScript. That means we don't have to change our application logic from web app to mobile app.
Stay tuned for my next post on Weex development, and in the meantime, check out some of our other posts on mobile app development with web technologies.