Code News

Securing iOS Data at Rest: Protecting the User's Data

  • This is the first of three articles on securing user data at rest. In this post, we'll start off with the basics of protecting data on iOS so you can learn the current best practices for storing data securely with Swift.

    Any app that saves the user's data has to take care of the security and privacy of that data. As we've seen with recent data breaches, there can be very serious consequences for failing to protect your users' stored data. In this tutorial, you'll learn some best practices for protecting your users' data.

    Permissions

    Before we get into storing your custom data, let's take a look at data that can be shared by system apps. 

    For many iOS versions, it has been required to request app permissions to use and store some of the user's private data that is external to the app, such as when saving and loading pictures to the photo library. Starting in iOS 10, any APIs that access the user's private data require you to declare that access ahead of time in your project's info.plist file. 

    There are many frameworks that can access data outside of your app, and each framework has a corresponding privacy key.

    • Bluetooth Sharing: NSBluetoothPeripheralUsageDescription
    • Calendar: NSCalendarsUsageDescription
    • CallKit: NSVoIPUsageDescription
    • Camera: NSCameraUsageDescription
    • Contacts: NSContactsUsageDescription
    • Health: NSHealthShareUsageDescription, NSHealthUpdateUsageDescription
    • HomeKit: NSHomeKitUsageDescription
    • Location: NSLocationAlwaysUsageDescription, NSLocationUsageDescription, NSLocationWhenInUseUsageDescription
    • Media Library: NSAppleMusicUsageDescription
    • Microphone: NSMicrophoneUsageDescription
    • Motion: NSMotionUsageDescription
    • Photos: NSPhotoLibraryUsageDescription
    • Reminders: NSRemindersUsageDescription
    • Speech Recognition: NSSpeechRecognitionUsageDescription
    • SiriKit: NSSiriUsageDescription
    • TV Provider: NSVideoSubscriberAccountUsageDescription

    For example, here is an entry in info.plist to allow your app to load and store values to the calendar.

    <key>NSCalendarsUsageDescription</key> <string>View and add events to your calendar</string>

    If a usage description is missing when the API tries to access the data, the app will simply crash.

    The Data Protection API

    For any user data that's internal to the app, the first thing to think about is whether you need to store the information, and what data is essential to the app. Keep as much of that essential data in working memory instead of in file storage. This is especially important for any personally identifiable information. 

    But, if you must store data, it's a good idea to enable Apple's Data Protection.

    Data Protection encrypts the contents of your app’s container. It relies on the user having a passcode, and thus the security of the encryption is tied to the strength of the passcode. With Touch ID and the upgraded file system encryption introduced in iOS 10.3, the data protection system has had many improvements. You can enable data protection across your app by turning on Data Protection in the Capabilities section of your project file. This updates your provisioning profile and entitlements file to include the Data Protection capability. Data Protection offers four levels of protection, depicted by the FileProtectionType structure:

    • none: no protection.
    • complete: data is not accessible while the device is locked. This is the recommended setting for most applications.
    • completeUnlessOpen: data is accessible when the device is unlocked, and continues to be accessible until the file is closed, even if the user locks the device. Files can also be created when the device is locked. This option is good for when you need to open a file to process and have the process continue even if the user puts the app into the background and locks the device. An example might be a job that uploads a file to a server.
    • completeUntilFirstUserAuthentication: when the device is booted, files are not accessible until the user first unlocks the device. After that, files are available even when the device is locked again. The option is good for files that need to be accessed sometime later in the background when the device is locked, such as during a background fetch job.

    complete is the default level. To help avoid crashes when your code tries to access data that is locked, you can register for notifications via UIApplicationProtectedDataDidBecomeAvailable and UIApplicationProtectedDataWillBecomeUnavailable to find out when the data is available.

    NotificationCenter.default.addObserver(forName: .UIApplicationProtectedDataDidBecomeAvailable, object: nil, queue: OperationQueue.main, using: { (notification) in //... }) NotificationCenter.default.addObserver(forName: .UIApplicationProtectedDataWillBecomeUnavailable, object: nil, queue: OperationQueue.main, using: { (notification) in //... })

    Additionally, you can also check the UIApplication.shared.isProtectedDataAvailable flag.

    One important thing to keep in mind when enabling data protection is that if you are using any background services such as background fetch, that code may need access to your data in the background when the device is locked. For those files, you will need to set a protection level of completeUntilFirstUserAuthentication. You can control the protection level of each file individually when creating files and directories using the FileManager class.

    let ok = FileManager.default.createFile(atPath: somePath, contents: nil, attributes: [FileAttributeKey.protectionKey.rawValue: FileProtectionType.complete]) do { try FileManager.default.createDirectory(atPath: somePath, withIntermediateDirectories: true, attributes: [FileAttributeKey.protectionKey.rawValue: FileProtectionType.complete]) } catch { print(error) }

    You can also set the protection level when you write to a file. The Data object has a method that can write its data to a file, and you can set the protection level when you call this method.

    let data = Data.init() let fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("somedata.dat") do { try data.write(to: fileURL, options: ([.atomic, .completeFileProtection])) } catch { print(error) }

    You can also set the protection level when setting up your Core Data model.

    let storeURL = docURL?.appendingPathComponent("Model.sqlite") let storeOptions: [AnyHashable: Any] = [NSPersistentStoreFileProtectionKey: FileProtectionType.complete] do { try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: storeOptions) } catch { print(error) }

    To change the protection level of an existing file, use the following:

    do { try FileManager.default.setAttributes([FileAttributeKey.protectionKey : FileProtectionType.complete], ofItemAtPath: path) } catch { print(error) }Data Integrity

    Part of protecting your stored data includes checking its integrity. It's good practice not to blindly trust the data you are loading from storage; it may have been accidentally or maliciously altered. The NSSecureCoding protocol can be used to safely load and save your data objects from storage. It will make sure the objects you load contain the expected data. If you will be saving your own object, you can conform to the secure coding protocol inside your class.

    class ArchiveExample : NSObject, NSSecureCoding { var stringExample : String? ...

    The class must be inherited from NSObject. Then, to turn on secure coding, override the supportsSecureCoding protocol method.

    static var supportsSecureCoding : Bool { get { return true } }

    If your custom object is deserialized with init?(coder aDecoder: NSCoder), the decodeObject(forKey:) method should be replaced with decodeObject(of:forKey:), which makes sure that the correct object types are unpacked from storage.

    required init?(coder aDecoder: NSCoder) { stringExample = aDecoder.decodeObject(of: NSString.self, forKey: "string_example") as String? } func encode(with aCoder: NSCoder) { aCoder.encode(stringExample, forKey:"string_example") }

    If you are using NSKeyedUnarchiver to load data from storage, make sure to set its requiresSecureCoding property.

    class func loadFromSavedData() -> ArchiveExample? { var object : ArchiveExample? = nil let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String let url = NSURL(fileURLWithPath: path) let fileURL = url.appendingPathComponent("ArchiveExample.plist") if FileManager.default.fileExists(atPath: (fileURL?.path)!) { do { let data = try Data.init(contentsOf: fileURL!) let unarchiver = NSKeyedUnarchiver.init(forReadingWith: data) unarchiver.requiresSecureCoding = true object = unarchiver.decodeObject(of: ArchiveExample.self, forKey: NSKeyedArchiveRootObjectKey) unarchiver.finishDecoding() } catch { print(error) } } return object; }

    Turning on secure coding for your save operations will prevent you from accidentally archiving an object that does not adhere to the secure coding protocol.

    func save() { let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String let url = NSURL(fileURLWithPath: path) let filePath = url.appendingPathComponent("ArchiveExample.plist")?.path let data = NSMutableData.init() let archiver = NSKeyedArchiver.init(forWritingWith: data) archiver.requiresSecureCoding = true archiver.encode(self, forKey: NSKeyedArchiveRootObjectKey) archiver.finishEncoding() let options : NSData.WritingOptions = [.atomic, .completeFileProtection] do { try data.write(toFile: filePath!, options:options) } catch { print(error) } }

    Beyond NSSecureCoding, it’s always good to implement your own data validation checks upon unpacking any archive or receiving any arbitrary input in general.

    Data Trails

    As iOS continues to evolve, there are always new features that have the potential to leak stored data. Starting in iOS 9, you can have your content indexed in the Spotlight search, and on iOS 10 you can expose your content to Widgets such as the Today Widget that shows up on the lock screen. Use caution if you would like to expose your content with these new features. You might end up sharing more than you planned to!

    iOS 10 also adds a new Handoff feature where your copied pasteboard data is automatically shared between devices. Again, be careful not to expose any sensitive data in the pasteboard to Handoff. You can do this by marking the sensitive content as localOnly. You can also set an expiry date and time for the data.

    let stringToCopy = "copy me to pasteboard" let pasteboard = UIPasteboard.general if #available(iOS 10, *) { let tomorrow = Date().addingTimeInterval(60 * 60 * 24) pasteboard.setItems([[kUTTypeUTF8PlainText as String : stringToCopy]], options: [UIPasteboardOption.localOnly : true, UIPasteboardOption.expirationDate: tomorrow]) } else { pasteboard.string = stringToCopy }

    Files that are saved to the device's storage can automatically get backed up, either in iTunes or in iCloud. Even though backups can be encrypted, it's a good idea to exclude any sensitive files that don't even need to leave the device. This can be done by setting the isExcludedFromBackup flag on the file.

    let path: String = ... var url = URL(fileURLWithPath: path) do { var resourceValues = URLResourceValues() //or if you want to first check the flag: //var resourceValues = try url.resourceValues(forKeys: [.isExcludedFromBackupKey]) resourceValues.isExcludedFromBackup = true; try url.setResourceValues(resourceValues) } catch { print(error) }

    The animation that happens when putting an app into the background is achieved by iOS taking a screenshot of your app which it then uses for the animation. When you look at the list of open apps on the app switcher, this screenshot is used there too. The screenshot gets stored on the device. 

    It's a good idea to hide any views revealing sensitive data so that the data isn't captured in the screenshot. To do this, set up a notification when the application is going to the background and set the hidden property for the UI elements you want to exclude. They will be hidden before iOS captures the screen. Then when coming to the foreground, you can unhide the UI elements.

    NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: .UIApplicationDidEnterBackground, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: .UIApplicationWillEnterForeground, object: nil)

    Remove your notifications when the view disappears.

    NotificationCenter.default.removeObserver(self, name: .UIApplicationDidEnterBackground, object: nil) NotificationCenter.default.removeObserver(self, name: .UIApplicationWillEnterForeground, object: nil)

    Your app also has a keyboard cache for text fields that have auto-correct enabled. Text that the user types, along with newly learned words, are stored in the cache so that it is possible to retrieve various words that the user has previously entered in your application. The only way to disable the keyboard cache is to turn off the auto-correct option.

    textField.autocorrectionType = UITextAutocorrectionType.no

    You should mark password fields as secure text entry. Secure text fields don't display the password or use the keyboard cache.

    textField.isSecureTextEntry = true

    Debug logs are saved to a file and could be retrieved for production builds of your app. Even when you're coding and debugging your app, make sure not to log sensitive information such as passwords and keys to the console. You might forget to remove that information from the logs before submitting your code to the app store! While debugging, it's safer instead to use a breakpoint to view sensitive variables.

    Network connections may also get cached to storage. More information about removing and disabling the network cache can be found in the article Securing Communications on iOS.

    Destroying Data

    You may already know that when a file on a computer is deleted, often the file itself is not removed; only the reference for the file is removed. To actually remove the file, you can overwrite the file with random data before removing it. 

    The switch to solid state drives has made it hard to guarantee the data has been destroyed, and the best way to securely delete data is open to debate. However, this tutorial would not be complete without an example of how to wipe data from storage. Because of some other debates about the Swift optimizer, and because we hope to guarantee that each byte of the file is actually being overwritten, we are implementing this function in C. 

    The implementation below can go inside a .c file. You will need to add the function definition or the file that contains the function into your bridging header in order to use the function from Swift. You may then want to call this function right before places where you use FileManager's removeFile methods. Perhaps you may want to implement the best practices described in this and the upcoming tutorials on an app update. You could then wipe the previous unprotected data during migration.

    #import <string.h> #import <sys/stat.h> #import <unistd.h> #import <errno.h> #import <fcntl.h> #import <stdio.h> #define MY_MIN(a, b) (((a) < (b)) ? (a) : (b)) int SecureWipeFile(const char *filePath) { int lastStatus = -1; for (int pass = 1; pass < 4; pass++) { //setup local vars int fileHandleInt = open(filePath, O_RDWR); struct stat stats; unsigned char charBuffer[1024]; //if can open file if (fileHandleInt >= 0) { //get file descriptors int result = fstat(fileHandleInt, &stats); if (result == 0) { switch (pass) { //DOD 5220.22-M implementation states that we write over with three passes first with 10101010, 01010101 and then the third with random data case 1: //write over with 10101010 memset(charBuffer, 0x55, sizeof(charBuffer)); break; case 2: //write over with 01010101 memset(charBuffer, 0xAA, sizeof(charBuffer)); break; case 3: //write over with arc4random for (unsigned long i = 0; i < sizeof(charBuffer); ++i) { charBuffer[i] = arc4random() % 255; } break; default: //at least write over with random data for (unsigned long i = 0; i < sizeof(charBuffer); ++i) { charBuffer[i] = arc4random() % 255; } break; } //get file size in bytes off_t fileSizeInBytes = stats.st_size; //rewrite every byte of the file ssize_t numberOfBytesWritten; for ( ; fileSizeInBytes; fileSizeInBytes -= numberOfBytesWritten) { //write bytes from the buffer into the file numberOfBytesWritten = write(fileHandleInt, charBuffer, MY_MIN((size_t)fileSizeInBytes, sizeof(charBuffer))); } //close the file lastStatus = close(fileHandleInt); } } } return lastStatus; }Conclusion

    In this article, you have learned about setting permissions for the data that your app has access to, as well as how to ensure basic file protection and integrity. We also looked at some ways that user data could be leaked accidentally from your app. Your users put their trust in you to protect their data. Following these best practices will help you repay that confidence.

    While you're here, check out some of our other posts on iOS app development!


    6 days 16 hours ago

How to Use Android O's Autofill Framework

  • Auto form fill, often shortened to just autofill, is a feature browsers have supported for years now. Most of us use it all the time. I, for one, find it indispensable during tasks such as filling out a registration form or completing a checkout process.

    The latest release of Android, Android O, brings similar functionality to Android apps. In other words, Android can now help users fill out forms that belong to all the apps they have installed on their devices. This was a much-awaited feature because typing with a virtual keyboard on a small screen tends to be quite a hassle.

    As an app developer, you can use the new Autofill Framework to create your own custom autofill service, a service that decides how to populate an app's input fields. In this tutorial, I'll show you how.

    Prerequisites

    To be able to follow this tutorial, you'll need:

    1. Create a New Project

    Fire up Android Studio and create a new project with an empty activity. You must, of course, remember to choose Android 7+ in the Target Android Devices dialog.

    This project will need a few widgets that belong to the Design Support Library, so open the app module's build.gradle file and add the following compile dependency to it:

    compile 'com.android.support:design:26.+'

    Lastly, press the Sync Now button to update the project.

    2. Create a Settings Activity

    In this tutorial, we'll be creating an app containing a very simple autofill service that targets only those input fields where the user is expected to type in an email address. Because almost every other app on Google Play today asks for an email address, this service will be quite useful.

    Our service obviously needs to know what the user's email addresses are. Therefore, let us now build an activity where the user can type in and save two email addresses.

    Step 1: Define the Layout

    As you might expect, the layout of the activity will contain two EditText widgets where the user can type in his or her email addresses. If you want it to adhere to the guidelines of Material Design, placing the EditText widgets inside TextInputLayout containers is a good idea.

    Additionally, the layout must have a Button widget the user can press to save the email addresses.

    You are free to place the widgets anywhere you want. Nevertheless, for now, I suggest you place them all inside a LinearLayout whose orientation is vertical.

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <android.support.design.widget.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/primary" android:hint="Your primary email address" android:inputType="textEmailAddress"/> </android.support.design.widget.TextInputLayout> <android.support.design.widget.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/secondary" android:hint="Your other email address" android:inputType="textEmailAddress"/> </android.support.design.widget.TextInputLayout> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/save_button" style="@style/Widget.AppCompat.Button.Colored" android:text="Save" android:onClick="saveEmailAddresses"/> </LinearLayout>

    In the above code, you can see that the Button widget has an onClick attribute pointing to a method. Click on the yellow light bulb beside this attribute in Android Studio to generate a stub for it in the associated Activity class.

    public void saveEmailAddresses(View view) { // More code will be added here }Step 2: Save the Email Addresses

    We'll be using a shared preferences file called EMAIL_STORAGE to save our data. You can use the getSharedPreferences() method of your Activity class to access the file. Additionally, to be able to write to the file, you must call its edit() method, which generates a SharedPreferences.Editor object.

    Accordingly, add the following code inside the saveEmailAddresses() method:

    SharedPreferences.Editor editor = getSharedPreferences("EMAIL_STORAGE", MODE_PRIVATE).edit();

    To fetch the email addresses the user has typed into the EditText widgets, you'll have to first get references to them using the findViewById() method, and then call their getText() methods.

    String primaryEmailAddress = ((EditText)findViewById(R.id.primary)) .getText().toString(); String secondaryEmailAddress = ((EditText)findViewById(R.id.secondary)) .getText().toString();

    At this point, you can call the putString() method of the editor to add the email addresses to the preferences file as two key value pairs. After you do so, don't forget to call the commit() method to make your changes permanent.

    editor.putString("PRIMARY_EMAIL", primaryEmailAddress); editor.putString("SECONDARY_EMAIL", secondaryEmailAddress); editor.commit();Step 3: Create a Meta-Data File

    The settings activity we created in the previous step is currently just an ordinary activity. To let the Android platform know that it is a settings activity for an autofill service, we must create a meta-data XML file saying so.

    Create a new XML file called email_address_filler.xml in the project's res/xml folder. Inside it, add an <autofill-service> tag and set the value of its settingsActivity attribute to the name of your Activity class.

    <?xml version="1.0" encoding="utf-8"?> <autofill-service xmlns:android="http://schemas.android.com/apk/res/android" android:settingsActivity="com.tutsplus.simplefill.MainActivity"/>

    You can now run the app, type in two email addresses, and press the Save button to save them.

    3. Create an Autofill Service

    Any class that extends the abstract AutoFillService class can serve as an autofill service. So start by creating a new Java class with File > New > Java Class. In the dialog that pops up, name the class EmailAddressFiller and make sure that you set the value of the Superclass field to AutoFillService.

    Android Studio will now prompt you to generate stubs for two abstract methods: onSaveRequest() and onFillRequest(). In this tutorial, we'll be focusing only the onFillRequest() method, which is automatically called whenever the user opens an activity—of any app—containing input fields.

    @Override public void onFillRequest(AssistStructure assistStructure, Bundle bundle, CancellationSignal cancellationSignal, FillCallback fillCallback) { // More code goes here }Step 1: Analyze View Hierarchies

    An autofill service needs to analyze an app's user interface and identify input fields it can fill. That's why the onFillRequest() method receives an AssistStructure object, which contains details about all the widgets that are currently visible on the screen. More precisely, it contains a tree of ViewNode objects. 

    If you've never seen such a tree, I suggest you use the uiautomatorviewer tool, which is part of the Android SDK, to analyze the layout hierarchies of a few apps. For example, here's what the layout hierarchy of Android's default mail app looks like:

    Naturally, to analyze all nodes of a tree, you need a recursive method. Let's create one now:

    void identifyEmailFields(AssistStructure.ViewNode node, List<AssistStructure.ViewNode> emailFields) { // More code goes here }

    As you can see, this method has a ViewNode and a List as its parameters. We'll be using the List to store all the input fields that expect email addresses.

    You might now be wondering how you can programmatically tell if an input field expects an email address. Well, there's really no foolproof approach you can follow. For now, we're going to assume that all app developers always give meaningful resource IDs to their input fields. Based on that assumption, we can simply pick all input fields whose resource IDs contain strings such as "email" and "username".

    Accordingly, add the following code to the method:

    if(node.getClassName().contains("EditText")) { String viewId = node.getIdEntry(); if(viewId!=null && (viewId.contains("email") || viewId.contains("username"))) { emailFields.add(node); return; } }

    Next, whenever we encounter a ViewNode object that contains more ViewNode objects, we must recursively call the identifyEmailFields() method to analyze all its children. The following code shows you how:

    for(int i=0; i<node.getChildCount();i++) { identifyEmailFields(node.getChildAt(i), emailFields); }

    At this point, we can call the identifyEmailFields() method inside the onFillRequest() method and pass the root node of the view hierarchy to it.

    // Create an empty list List<AssistStructure.ViewNode> emailFields = new ArrayList<>(); // Populate the list identifyEmailFields(assistStructure .getWindowNodeAt(0) .getRootViewNode(), emailFields);

    If our service is unable to identify any input fields for emails, it should do nothing. Therefore, add the following code to it:

    if(emailFields.size() == 0) return;Step 2: Create and Populate Remote Views

    If our service does identify an input field it can fill, it must populate a drop-down list that will be shown below the input field. Doing so, however, is not straightforward because neither the input field nor the drop-down list belongs to our app.

    To populate the drop-down list, we must use RemoteViews objects. As its name suggests, a RemoteViews object is a collection of views that can be displayed in another app.

    To initialize a RemoteViews object, you'll need a layout XML file. Let's create one now called email_suggestion.xml. For now, it can contain just one TextView widget to display an email address.

    Accordingly, add the following code to email_suggestion.xml:

    <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/email_suggestion_item" android:textSize="18sp" android:textStyle="bold" android:padding="5dp"> </TextView>

    You can now go back to the onFillRequest() method and create two RemoteViews objects: one for the primary email, and another for the secondary.

    RemoteViews rvPrimaryEmail = new RemoteViews(getPackageName(), R.layout.email_suggestion); RemoteViews rvSecondaryEmail = new RemoteViews(getPackageName(), R.layout.email_suggestion);

    The TextView widgets inside the RemoteViews objects must display the two email addresses we stored in a shared preferences file earlier. To open the file, use the getSharedPreferences() method again. Once it's opened, you can use its getString() method to fetch both the email addresses.

    Finally, to set the contents of the remote TextView widgets, you must use the setTextViewText() method.

    // Load the email addresses from preferences SharedPreferences sharedPreferences = getSharedPreferences("EMAIL_STORAGE", MODE_PRIVATE); String primaryEmail = sharedPreferences.getString("PRIMARY_EMAIL", ""); String secondaryEmail = sharedPreferences.getString("SECONDARY_EMAIL", ""); // Update remote TextViews rvPrimaryEmail.setTextViewText(R.id.email_suggestion_item, primaryEmail); rvSecondaryEmail.setTextViewText(R.id.email_suggestion_item, secondaryEmail);Step 3: Create Data Sets

    We can now use the remote views to create autofill data sets that can be sent to any app. To keep this tutorial from getting too long, we'll be creating data sets only for the first email input field we encounter. The following code shows how to pick only the first email input field:

    AssistStructure.ViewNode emailField = emailFields.get(0);

    An autofill data set is nothing but an instance of the Dataset class, and can be built using the Dataset.Builder class.

    When the user selects one of the email addresses our service shows in the drop-down list, it must set the contents of the associated input field using the setValue() method of the Dataset.Builder class. However, you cannot pass a ViewNode object to the setValue() method. It actually expects an autofill identifier, which must be obtained by calling the getAutoFillId() method of the ViewNode object.

    Additionally, to specify the text that must be written into the input field, you must use the AutoFillValue.forText() method. The following code shows you how:

    Dataset primaryEmailDataSet = new Dataset.Builder(rvPrimaryEmail) .setValue( emailField.getAutoFillId(), AutoFillValue.forText(primaryEmail) ).build(); Dataset secondaryEmailDataSet = new Dataset.Builder(rvSecondaryEmail) .setValue( emailField.getAutoFillId(), AutoFillValue.forText(secondaryEmail) ).build();

    Before you send the data sets to an app, you must add them to a FillResponse object, which can be built using the FillResponse.Builder class. Call its addDataset() method twice to add both the data sets.

    Once the FillResponse object is ready, pass it as an argument to the onSuccess() method of the FillCallback object, which is one of the parameters of the onFillRequest() method.

    FillResponse response = new FillResponse.Builder() .addDataset(primaryEmailDataSet) .addDataset(secondaryEmailDataSet) .build(); fillCallback.onSuccess(response);Step 4: Update the Manifest

    Like all services, the autofill service too must be declared in the project's AndroidManifest.xml file. While doing so, you must make sure that it is protected by the android.permission.BIND_AUTO_FILL permission.

    This service also needs an <intent-filter> tag that allows it to respond to the android.service.autofill.AutoFillService action, and a <meta-data> tag that points to the meta-data XML file we created in an earlier step.

    Accordingly, add the following lines to your manifest file:

    <service android:name=".EmailAddressFiller" android:permission="android.permission.BIND_AUTO_FILL"> <meta-data android:name="android.autofill" android:resource="@xml/email_address_filler"/> <intent-filter> <action android:name="android.service.autofill.AutoFillService"/> </intent-filter> </service>

    Our autofill service and app are now ready. Build the project and install the app on your device.

    4. Activate and Use the Autofill Service

    To activate the autofill service, open your device's Settings app and navigate to Apps & Notifications > Advanced > Default apps > Autofill app. In the next screen, select your app from the list of available autofill apps.

    You can now open any app that asks for an email address to see your autofill service in action. For example, here's what you'd see on the login screens of Instagram and Pinterest:

    Conclusion

    You now know how to create and use a custom autofill service for Android. Feel free to extend it to support other common fields, such as first name or phone number. You can also try identifying input fields using other attributes, such as labels and hints.

    To learn more about the Autofill Framework, do refer to its official documentation. And in the meantime, check out some of our other posts about Android O and Android app development!

    1 week 8 hours ago

Swift Animation Basics

  •  

    In my recent course Go Further With Swift, I showed you how to code a functional iOS weather app with live weather data, custom UI components, and some slick animations to bring everything to life.

    In this video tutorial from the course, you'll see how to start adding some animation to an iOS app with Swift. We'll begin with constraint-based animation and then advance to cross-fading the background image with a handy technique.

    Watch the Full Course

    In the full course, Go Further With Swift: Animation, Networking, and Custom Controls, you'll learn some advanced skills for building professional-quality iOS apps. 

    App store customers expect animated, engaging apps with clear feedback on UI events. They want apps that connect to the web to get relevant, up-to-date information. And they want apps that stand out with unique custom interfaces. You'll learn how to add all those features in this course.

    Of, if you want to learn the basics of iOS app development with Swift, try some of these courses instead:

    And don't forget that you can find some fantastic, full-featured iOS app templates on Envato Market to get your app development off to a flying start.

    1 week 9 hours ago

How to Draw Bar Charts Using JavaScript and HTML5 Canvas

  • What You'll Be Creating

    In an earlier tutorial we covered how to draw a pie chart or doughnut chart using HTML5 canvas. In this tutorial I will show you how to use JavaScript and the HTML5 canvas as a means to graphically display data by using bar charts.

    There are easier ways to create charts than coding one from scratch, for example this complete charting library from CodeCanyon.

    Infographic Charts Library from CodeCanyon

    But if you want to know what it takes to create a library like this, this tutorial is for you.

    What Is a Bar Chart?

    Bar charts are very common tools used to represent numerical data. From financial reports to PowerPoint presentations to infographics, bar charts are used very often since they offer a view of numerical data that is very easy to understand.

    Bar charts represent numerical data using bars, which are rectangles with either their widths or heights proportional to the numerical data that they represent.

    There are many types of bar charts:

    • horizontal bar charts and vertical bar charts depending on the chart orientation
    • stacked bar charts or classic bar charts for representing multiple series of data
    • 2D or 3D bar charts
    • etc.
    What Are the Components of a Bar Chart?

    Let's take a look at the components that make up a bar chart regardless of its type:

    • The chart data: these are sets of numbers and associated categories which are represented by the chart.
    • Name of the data series (1).
    • The chart grid (2): giving a reference system so that the visual representation can be easily understood.
    • The bars (3): color-filled rectangles with dimensions proportional to the data represented.
    • Chart legend (4): shows the correspondence between the colors used and the data they represent.

    Now that we know the components of a bar chart, let's see how we can write the JavaScript code to draw a chart like this.

    Drawing the Bar Chart Using JavaScriptSetting Up the JS Project

    To start drawing using JavaScript and the HTML5 canvas, we will need to set up our project like this:

    • Create a folder to hold the project files; let's call this folder bar-chart-tutorial.
    • Inside the project folder, create a file and call it index.html. This will contain our HTML code.
    • Also inside the project folder, create a file and call it script.js. This will contain the JavaScript code for drawing the bar chart.

    We'll keep things very simple and add the following code inside index.html:

    <html> <body> <canvas id="myCanvas"></canvas> <script type="text/javascript" src="script.js"></script> </body> </html>

    We have the <canvas> element with the ID myCanvas so that we can reference it in our JS code. We then load the JS code via the <script> tag.

    Add the following code in the script.js file: 

    var myCanvas = document.getElementById("myCanvas"); myCanvas.width = 300; myCanvas.height = 300; var ctx = myCanvas.getContext("2d");

    This gets a reference to the canvas element and then sets the width and height to 300px. To draw on the canvas, we only need a reference to its 2D context, which contains all the drawing methods.

    Adding a Few Helper Functions

    Drawing the bar chart only requires knowing how to draw two elements:

    • drawing a line: for drawing the grid lines
    • drawing a color-filled rectangle: for drawing the bars of the chart

    Let's create the helper JS functions for these two elements.  We will add the functions in our script.js file.

    function drawLine(ctx, startX, startY, endX, endY,color){ ctx.save(); ctx.strokeStyle = color; ctx.beginPath(); ctx.moveTo(startX,startY); ctx.lineTo(endX,endY); ctx.stroke(); ctx.restore(); }

    The drawLine function takes six parameters:

    1. ctx: reference to the drawing context
    2. startX: the X coordinate of the line starting point
    3. startY: the Y coordinate of the line starting point
    4. endX: the X coordinate of the line end point
    5. endY: the Y coordinate of the line end point
    6. color: the color of the line

    We are modifying the color settings for the strokeStyle. This determines the color used to draw the line. We use ctx.save() and ctx.restore() so that we don't affect the colors used outside this function.

    We draw the line by calling beginPath(). This informs the drawing context that we are starting to draw something new on the canvas. We use moveTo() to set the starting point, call lineTo() to indicate the end point, and then do the actual drawing by calling stroke().

    Another helper function that we need is a function to draw a bar—which is a color-filled rectangle. Let's add it to script.js:

    function drawBar(ctx, upperLeftCornerX, upperLeftCornerY, width, height,color){ ctx.save(); ctx.fillStyle=color; ctx.fillRect(upperLeftCornerX,upperLeftCornerY,width,height); ctx.restore(); }

    The drawBar function takes six parameters:

    1. ctx: reference to the drawing context
    2. upperLeftCornerX: the X coordinate of the bar's upper left corner
    3. upperLeftCornerY: the X coordinate of the bar's upper left corner
    4. width: the width of the bar
    5. height: the height of the bar
    6. color: the color of the bar
    The Bar Chart Data Model

    Now that we have the helper functions in place, let's move on to the chart's data model. All types of chart, including bar charts, have a data model behind them. The data model is a structured set of numerical data. For this tutorial we will use a data series of categories and their associated numerical values representing the number of vinyl records in my collection of records grouped by music genre:

    • Classical music: 10
    • Alternative rock: 14
    • Pop: 2
    • Jazz: 12

    We can represent this in JavaScript in the form of an object. Let's add it to our script.js file:

    var myVinyls = { "Classical music": 10, "Alternative rock": 14, "Pop": 2, "Jazz": 12 };Implementing the Bar Chart Component

    Let's implement the component that will do the actual drawing of our bar chart. We will do this by adding the following JavaScript object to our script.js file:

    var Barchart = function(options){ this.options = options; this.canvas = options.canvas; this.ctx = this.canvas.getContext("2d"); this.colors = options.colors; this.draw = function(){ var maxValue = 0; for (var categ in this.options.data){ maxValue = Math.max(maxValue,this.options.data[categ]); } var canvasActualHeight = this.canvas.height - this.options.padding * 2; var canvasActualWidth = this.canvas.width - this.options.padding * 2; //drawing the grid lines var gridValue = 0; while (gridValue <= maxValue){ var gridY = canvasActualHeight * (1 - gridValue/maxValue) + this.options.padding; drawLine( this.ctx, 0, gridY, this.canvas.width, gridY, this.options.gridColor ); //writing grid markers this.ctx.save(); this.ctx.fillStyle = this.options.gridColor; this.ctx.font = "bold 10px Arial"; this.ctx.fillText(gridValue, 10,gridY - 2); this.ctx.restore(); gridValue+=this.options.gridScale; } //drawing the bars var barIndex = 0; var numberOfBars = Object.keys(this.options.data).length; var barSize = (canvasActualWidth)/numberOfBars; for (categ in this.options.data){ var val = this.options.data[categ]; var barHeight = Math.round( canvasActualHeight * val/maxValue) ; drawBar( this.ctx, this.options.padding + barIndex * barSize, this.canvas.height - barHeight - this.options.padding, barSize, barHeight, this.colors[barIndex%this.colors.length] ); barIndex++; } } }

    The class starts by storing the options passed as parameters. It stores the canvas reference and creates a drawing context also stored as a class member. Then it stores the colors array passed as options.

    The next part is the most consistent, the draw() function. This will draw the chart by first drawing the grid lines, the grid markers and then the bars using the parameters passed via the options object.

    Looking at the draw() function, we can see that first we calculate the maximum value for our data model. We need this number because we will need to scale all the bars according to this value and according to the size of the canvas. Otherwise, our bars might go outside the display area, and we don't want that.

    The canvasActualHeight and canvasActualWidth variables store the height and width of the canvas adjusted using the value of the padding passed via options. The padding variable indicates the number of pixels between the edge of the canvas and the chart inside. 

    We then draw the grid lines of the chart. The options.gridScale variable sets the step used for drawing the lines. So a gridScale of 10 will mean drawing grid lines every 10 units.

    To draw the grid lines, we use the helper function drawLine(); as for the color of the grid lines, we take it from the options.gridColor variable. Please note that the canvas coordinates start from 0,0 in the top left corner and increase towards the right and bottom, while our grid values increase in value from the bottom towards the top. That is why we used 1 - gridValue/maxValue in the formula calculating the gridY value.

    For every grid line, we also draw the value of the grid line 2 pixels above the grid line (that's why we have gridY - 2 for the Y coordinates of the text).

    Next we draw the bars by using the helper function drawBar(). The math for calculating the height and width of each bar is pretty straightforward; it takes into account the padding and the value and color for each category in the chart's data model.

    Using the Bar Chart Component

    Let's now see how to use the Barchart class implemented above. We need to instantiate the class and call the draw() function. Add the following code to the script.js file:

    var myBarchart = new Barchart( { canvas:myCanvas, padding:10, gridScale:5, gridColor:"#eeeeee", data:myVinyls, colors:["#a55ca5","#67b6c7", "#bccd7a","#eb9743"] } ); myBarchart.draw();

    The code creates a new instance of the Barchart class with the required options. Loading the index.html in a browser should produce a result like this:

    Adding the Data Series Name and Chart Legend

    To add the data series name below the chart, we need to add the following code in the script.js file after the for-loop that draws the bar:

    ... //drawing series name this.ctx.save(); this.ctx.textBaseline="bottom"; this.ctx.textAlign="center"; this.ctx.fillStyle = "#000000"; this.ctx.font = "bold 14px Arial"; this.ctx.fillText(this.options.seriesName, this.canvas.width/2,this.canvas.height); this.ctx.restore(); ...

    We also need to change the way we call the Barchart component like this:

    var myBarchart = new Barchart( { canvas:myCanvas, seriesName:"Vinyl records", padding:20, gridScale:5, gridColor:"#eeeeee", data:myVinyls, colors:["#a55ca5","#67b6c7", "#bccd7a","#eb9743"] } ); myBarchart.draw();

    And here is how the result looks:

    To add the legend, we first need to modify index.html to look like this:

    <html> <body> <canvas id="myCanvas" style="background: white;"></canvas> <legend for="myCanvas"></legend> <script type="text/javascript" src="script.js"></script> </body> </html>

    The legend tag will be used as a placeholder for the chart's legend. The for attribute links the legend to the canvas holding the chart. We now need to add the code that creates the legend. We will do this in the index.js file after the code that draws the data series name. The code identifies the legend tag corresponding to the chart, and it will add the list of categories from the chart's data model together with the corresponding color. The resulting index.js file will look like this:

    var myCanvas = document.getElementById("myCanvas"); myCanvas.width = 300; myCanvas.height = 300; var ctx = myCanvas.getContext("2d"); function drawLine(ctx, startX, startY, endX, endY,color){ ctx.save(); ctx.strokeStyle = color; ctx.beginPath(); ctx.moveTo(startX,startY); ctx.lineTo(endX,endY); ctx.stroke(); ctx.restore(); } function drawBar(ctx, upperLeftCornerX, upperLeftCornerY, width, height,color){ ctx.save(); ctx.fillStyle=color; ctx.fillRect(upperLeftCornerX,upperLeftCornerY,width,height); ctx.restore(); } var myVinyls = { "Classical music": 10, "Alternative rock": 14, "Pop": 2, "Jazz": 12 }; var Barchart = function(options){ this.options = options; this.canvas = options.canvas; this.ctx = this.canvas.getContext("2d"); this.colors = options.colors; this.draw = function(){ var maxValue = 0; for (var categ in this.options.data){ maxValue = Math.max(maxValue,this.options.data[categ]); } var canvasActualHeight = this.canvas.height - this.options.padding * 2; var canvasActualWidth = this.canvas.width - this.options.padding * 2; //drawing the grid lines var gridValue = 0; while (gridValue <= maxValue){ var gridY = canvasActualHeight * (1 - gridValue/maxValue) + this.options.padding; drawLine( this.ctx, 0, gridY, this.canvas.width, gridY, this.options.gridColor ); //writing grid markers this.ctx.save(); this.ctx.fillStyle = this.options.gridColor; this.ctx.textBaseline="bottom"; this.ctx.font = "bold 10px Arial"; this.ctx.fillText(gridValue, 10,gridY - 2); this.ctx.restore(); gridValue+=this.options.gridScale; } //drawing the bars var barIndex = 0; var numberOfBars = Object.keys(this.options.data).length; var barSize = (canvasActualWidth)/numberOfBars; for (categ in this.options.data){ var val = this.options.data[categ]; var barHeight = Math.round( canvasActualHeight * val/maxValue) ; drawBar( this.ctx, this.options.padding + barIndex * barSize, this.canvas.height - barHeight - this.options.padding, barSize, barHeight, this.colors[barIndex%this.colors.length] ); barIndex++; } //drawing series name this.ctx.save(); this.ctx.textBaseline="bottom"; this.ctx.textAlign="center"; this.ctx.fillStyle = "#000000"; this.ctx.font = "bold 14px Arial"; this.ctx.fillText(this.options.seriesName, this.canvas.width/2,this.canvas.height); this.ctx.restore(); //draw legend barIndex = 0; var legend = document.querySelector("legend[for='myCanvas']"); var ul = document.createElement("ul"); legend.append(ul); for (categ in this.options.data){ var li = document.createElement("li"); li.style.listStyle = "none"; li.style.borderLeft = "20px solid "+this.colors[barIndex%this.colors.length]; li.style.padding = "5px"; li.textContent = categ; ul.append(li); barIndex++; } } } var myBarchart = new Barchart( { canvas:myCanvas, seriesName:"Vinyl records", padding:20, gridScale:5, gridColor:"#eeeeee", data:myVinyls, colors:["#a55ca5","#67b6c7", "#bccd7a","#eb9743"] } );

    Which will produce a final result looking like this:

    Congratulations

    We have seen that drawing charts using the HTML5 canvas is actually not that hard. It only requires a bit of math and a bit of JavaScript knowledge. You now have everything you need for drawing your own bar charts.

    If you want a quick and easy solution for creating not only bar charts, but loads of other types of charts, you can download the Infographic Charts and Graphics HTML Tags Library or its WordPress plugin counterpart Charts and Graphs WordPress Visual Designer.

    Infographic Charts Library from CodeCanyon

    1 week 13 hours ago

Pages