Badblog

welcome to our blog

We are Learncodz.


Posts

Comments

The Team

Blog Codz Author

Connect With Us

Join To Connect With Us

Portfolio

  • his post is part of a series called Working with NSURLSession.
    Networking with NSURLSession: Part 1
    Working with NSURLSession: Part 3
    In the previous tutorial, I introduced you to NSURLSession. I talked about the advantages it has over NSURLConnection and how to use NSURLSession for simple tasks, such as fetching data from a web service and downloading an image from the web. In this tutorial, we'll take a closer look at the configuration options of NSURLSession and how to cancel and resume a download task. We've got a lot of ground to cover so let's get started.

    As we saw in the previous tutorial, a session, an instance of NSURLSession, is a configurable container for putting network requests into. The configuration of the session is handled by an instance of NSURLSessionConfiguration.
    A session configuration object is nothing more than a dictionary of properties that defines how the session it is tied to behaves. A session has one session configuration object that dictates cookie, security, and cache policies, the maximum number of connections to a host, resource and network timeouts, etc. This is a significant improvement over NSURLConnection, which relied on a global configuration object with much less flexibility.
    Once a session is created and configured by a NSURLSessionConfiguration instance, the session's configuration cannot be modified. If you need to modify a session's configuration, you have to create a new session. Keep in mind that it is possible to copy a session's configuration and modify it, but the changes have no effect on the session from which the configuration was copied.
    The NSURLSessionConfiguration class provides three factory constructors for instantiating standard configurations, defaultSessionConfiguration, ephemeralSessionConfiguration, and backgroundSessionConfiguration. The first method returns a copy of the default session configuration object, which results in a session that behaves similarly to an NSURLConnection object in its standard configuration. Altering a session configuration obtained through the defaultSessionConfiguration factory method doesn't change the default session configuration which it's a copy of.
    A session configuration object created by invoking the ephemeralSessionConfiguration factory method ensures that the resulting session uses no persistent storage for cookies, caches, or credentials. In other words, cookies, caches, and credentials are kept in memory. Ephemeral sessions are therefore ideal if you need to implement private browsing, something that simply wasn't possible before the introduction of NSURLSession.
    The backgroundSessionConfiguration: factory method creates a session configuration object that enables out-of-process uploads and downloads. The upload and download tasks are managed by a background daemon and continue to run even if the application is suspended or crashes. We'll talk more about background sessions later in this series.
    As we saw in the previous tutorial, creating a session configuration object is simple. In the example shown below, I used the defaultSessionConfiguration factory method to create a NSURLSessionConfiguration instance. Configuring a session configuration object is as simple as modifying its properties as shown in the example. We can then use the session configuration object to instantiate a session object. The session object serves as a factory for data, upload, and download tasks, with each task corresponding to a single request. In the example below, we query the iTunes Search API as we did in the previous tutorial.
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    // Create Session Configuration
    NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
    // Configure Session Configuration
    [sessionConfiguration setAllowsCellularAccess:YES];
    [sessionConfiguration setHTTPAdditionalHeaders:@{ @"Accept" : @"application/json" }];
    // Create Session
    NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
    // Send Request
    NSURL *url = [NSURL URLWithString:@"https://itunes.apple.com/search?term=apple&media=software"];
    [[session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSLog(@"%@", [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]);
    }] resume];
    The example also illustrates how easy it is to add custom headers by setting the HTTPAdditionalHeaders property of the session configuration object. The beauty of the NSURLSession API is that every request that passes through the session is configured by the session's configuration object. Adding authentication headers to a set of requests, for example, becomes easy as pie.
    In the previous tutorial, I showed you how to download an image using the NSURLSession API. However, network connections are unreliable and it happens all too often that a download fails due to a flaky network connection. Fortunately, resuming a download isn't difficult with the NSURLSession API. In the next example, I'll show you how to cancel and resume the download of an image.
    Before we take a closer look at resuming a download task, it is important to understand the difference between canceling and suspending a download task. It is possible to suspend a download task and resume it at a later time. Canceling a download task, however, stops the task and it isn't possible to resume it at a later time. There is one alternative, though. It is possible to cancel a download task by calling cancelByProducingResumeData: on it. It accepts a completion handler that accepts one parameter, an NSData object that is used to resume the download at a later time by invoking downloadTaskWithResumeData: or downloadTaskWithResumeData:completionHandler: on a session object. The NSData object contains the necessary information to resume the download task where it left off.
    Open the project we created in the previous tutorial or download it here. We start by adding two buttons to the user interface, one to cancel the download and one to resume the download. In the view controller's header file, create an outlet and an action for each button as shown below.
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    #import <UIKit/UIKit.h>
    @interface MTViewController : UIViewController
    @property (weak, nonatomic) IBOutlet UIButton *cancelButton;
    @property (weak, nonatomic) IBOutlet UIButton *resumeButton;
    @property (weak, nonatomic) IBOutlet UIImageView *imageView;
    @property (weak, nonatomic) IBOutlet UIProgressView *progressView;
    - (IBAction)cancel:(id)sender;
    - (IBAction)resume:(id)sender;
    @end
    Open the project's main storyboard and add two buttons to the view controller's view. Position the buttons as shown in the screenshot below and connect each button with its corresponding outlet and action.


    Update the user interface.

    We'll need to do some refactoring to make everything work correctly. Open MTViewController.m and declare one instance variable and two properties. The instance variable, session, will keep a reference to the session we'll use for downloading the image.
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    #import "MTViewController.h"
    @interface MTViewController () <NSURLSessionDelegate, NSURLSessionDownloadDelegate> {
        NSURLSession *_session;
    }
    @property (strong, nonatomic) NSURLSessionDownloadTask *downloadTask;
    @property (strong, nonatomic) NSData *resumeData;
    @end
    We also need to refactor the viewDidLoad method, but first I'd like to implement a getter method for the session. Its implementation is pretty straightforward as you can see below. We create a session configuration object using the defaultSessionConfiguration factory method and instantiate the session object with it. The view controller serves as the session's delegate.
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    - (NSURLSession *)session {
        if (!_session) {
            // Create Session Configuration
            NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
            // Create Session
            _session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
        }
        return _session;
    }
    With the session accessor implemented, the viewDidLoad method becomes much simpler. We create a download task, as we did in the previous tutorial, and store a reference to the task in downloadTask. We then tell the download task to resume.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Create Download Task
        self.downloadTask = [self.session downloadTaskWithURL:[NSURL URLWithString:@"http://cdn.tutsplus.com/mobile/uploads/2014/01/5a3f1-sample.jpg"]];
        // Resume Download Task
        [self.downloadTask resume];
    }
    The cancel: action contains the logic for canceling the download task we just created. If downloadTask is not nil, we call cancelByProducingResumeData: on the task. This method accepts one parameter, a completion block. The completion block also takes one parameter, an instance of NSData. If resumeData is not nil, we store a reference to the data object in view controller's resumeData property.
    If a download is not resumable, the completion block's resumeData parameter is nil. Not every download is resumable so it's important to check if resumeData is a valid NSData object.
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    - (IBAction)cancel:(id)sender {
        if (!self.downloadTask) return;
        // Hide Cancel Button
        [self.cancelButton setHidden:YES];
        [self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) {
            if (!resumeData) return;
            [self setResumeData:resumeData];
            [self setDownloadTask:nil];
        }];
    }
    Resuming the download task after it was canceled is easy. In the resume: action, we check if the view controller's resumeData property is set. If resumeData is a valid NSData object, we tell the session object to create a new download task and pass it the NSData object. This is all the session object needs to recreate the download task that we canceled in the cancel: action. We then tell the download task to resume and set resumeData to nil.
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    - (IBAction)resume:(id)sender {
        if (!self.resumeData) return;
        // Hide Resume Button
        [self.resumeButton setHidden:YES];
        // Create Download Task
        self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
        // Resume Download Task
        [self.downloadTask resume];
        // Cleanup
        [self setResumeData:nil];
    }
    Build the project and run the application in the iOS Simulator or on a physical device. The download should start automatically. Tap the cancel button to cancel the download and tap the resume button to resume the download.
    There are a number of details we need to take care of. First of all, the buttons shouldn't always be visible. We'll use key value observing to show and hide the buttons when necessary. In viewDidLoad, hide the buttons and add the view controller as an observer of itself for the resumeData and downloadTask key paths.
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Add Observer
        [self addObserver:self forKeyPath:@"resumeData" options:NSKeyValueObservingOptionNew context:NULL];
        [self addObserver:self forKeyPath:@"downloadTask" options:NSKeyValueObservingOptionNew context:NULL];
        // Setup User Interface
        [self.cancelButton setHidden:YES];
        [self.resumeButton setHidden:YES];
        // Create Download Task
        self.downloadTask = [self.session downloadTaskWithURL:[NSURL URLWithString:@"http://cdn.tutsplus.com/mobile/uploads/2014/01/5a3f1-sample.jpg"]];
        // Resume Download Task
        [self.downloadTask resume];
    }
    In observeValueForKeyPath:ofObject:change:context:, we hide the cancel button if resumeData is nil and we hide the resume button if downloadTask is nil. Build the project and run the application one more time to see the result. This is better. Right?
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
        if ([keyPath isEqualToString:@"resumeData"]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.resumeButton setHidden:(self.resumeData == nil)];
            });
             
        } else if ([keyPath isEqualToString:@"downloadTask"]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.cancelButton setHidden:(self.downloadTask == nil)];
            });
        }
    }
    As George Yang points out in the comments, we don't know whether observeValueForKeyPath:ofObject:change:context: is called on the main thread. It is therefore important to update the user interface in a GCD (Grand Central Dispatch) block that is invoked on the main queue.
    Advertisement
    There is one key aspect of NSURLSession that I haven't talked about yet, session invalidation. The session keeps a strong reference to its delegate, which means that the delegate isn't released as long as the session is active. To break this reference cycle, the session needs to be invalidated. When a session is invalidated, active tasks are canceled or finished, and the delegate is sent a URLSession:didBecomeInvalidWithError: message and the session releases its delegate.
    There are several places that we can invalidate the session. Since the view controller downloads only one image, the session can be invalidated when the download finishes. Take a look at the updated implementation of URLSession:downloadTask:didFinishDownloadingToURL:. The cancel button is also hidden when the download finishes.
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
        NSData *data = [NSData dataWithContentsOfURL:location];
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.cancelButton setHidden:YES];
            [self.progressView setHidden:YES];
            [self.imageView setImage:[UIImage imageWithData:data]];
        });
        // Invalidate Session
        [session finishTasksAndInvalidate];
    }
    The example project we created in this tutorial is a simplified implementation of how to cancel and resume downloads. In your applications, it may be necessary to write the resumeData object to disk for later use and it may be possible that several download tasks are running at the same time. Even though this adds complexity, the basic principles remain the same. Be sure to prevent memory leaks by always invalidating a session that you no longer need.
  • his post is part of a series called The SOLID Principles.
    SOLID: Part 2 - The Open/Closed Principle
    SOLID: Part 4 - The Dependency Inversion Principle
    The Single Responsibility (SRP), Open/Closed (OCP), Liskov Substitution, Interface Segregation, and Dependency Inversion. Five agile principles that should guide you every time you write code.
    Because both the Liskov Substitution Principle (LSP) and the Interface Segregation Principle (ISP) are quite easy to define and exemplify, in this lesson we will talk about both of them.
    Child classes should never break the parent class' type definitions.
    The concept of this principle was introduced by Barbara Liskov in a 1987 conference keynote and later published in a paper together with Jannette Wing in 1994. Their original definition is as follows:
    Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.
    Later on, with the publication of the SOLID principles by Robert C. Martin in his book Agile Software Development, Principles, Patterns, and Practices and then republished in the C# version of the book Agile Principles, Patterns, and Practices in C#, the definition became known as the Liskov Substitution Principle.
    This leads us to the definition given by Robert C. Martin:
    Subtypes must be substitutable for their base types.
    As simple as that, a subclass should override the parent class' methods in a way that does not break functionality from a client's point of view. Here is a simple example to demonstrate the concept.
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    class Vehicle {
        function startEngine() {
            // Default engine start functionality
        }
        function accelerate() {
            // Default acceleration functionality
        }
    }
    Given a class Vehicle - it may be abstract - and two implementations:
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    class Car extends Vehicle {
        function startEngine() {
            $this->engageIgnition();
            parent::startEngine();
        }
        private function engageIgnition() {
            // Ignition procedure
        }
    }
    class ElectricBus extends Vehicle {
        function accelerate() {
            $this->increaseVoltage();
            $this->connectIndividualEngines();
        }
        private function increaseVoltage() {
            // Electric logic
        }
        private function connectIndividualEngines() {
            // Connection logic
        }
    }
    A client class should be able to use either of them, if it can use Vehicle.
    1
    2
    3
    4
    5
    6
    class Driver {
        function go(Vehicle $v) {
            $v->startEngine();
            $v->accelerate();
        }
    }
    Which leads us to a simple implementation of the Template Method Design Pattern as we used it in the OCP tutorial.


    template_method
    Based on our previous experience with the Open/Closed Principle, we can conclude that Liskov's Substitution Principle is in strong relation with OCP. In fact, "a violation of LSP is a latent violation of OCP" (Robert C. Martin), and the Template Method Design Pattern is a classic example of respecting and implementing LSP, which in turn is one of the solutions to respect OCP also.
    To illustrate this completely, we will go with a classic example because it is highly significant and easily understandable.
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Rectangle {
        private $topLeft;
        private $width;
        private $height;
        public function setHeight($height) {
            $this->height = $height;
        }
        public function getHeight() {
            return $this->height;
        }
        public function setWidth($width) {
            $this->width = $width;
        }
        public function getWidth() {
            return $this->width;
        }
    }
    We start with a basic geometrical shape, a Rectangle. It is just a simple data object with setters and getters for width and height. Imagine that our application is working and it is already deployed to several clients. Now they need a new feature. They need to be able to manipulate squares.
    In real life, in geometry, a square is a particular form of rectangle. So we could try to implement a Square class that extends a Rectangle class. It is frequently said that a child class is a parent class, and this expression also conforms to LSP, at least at first sight.


    SquareRect
    But is a Square really a Rectangle in programming?
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    class Square extends Rectangle {
        public function setHeight($value) {
            $this->width = $value;
            $this->height = $value;
        }
        public function setWidth($value) {
            $this->width = $value;
            $this->height = $value;
        }
    }
    A square is a rectangle with equal width and height, and we could do a strange implementation like in the above example. We could overwrite both setters to set the height as well as the width. But how would that affect client code?
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    class Client {
        function areaVerifier(Rectangle $r) {
            $r->setWidth(5);
            $r->setHeight(4);
            if($r->area() != 20) {
                throw new Exception('Bad area!');
            }
            return true;
        }
    }
    It is conceivable to have a client class that verifies the rectangle's area and throws an exception if it is wrong.
    1
    2
    3
    function area() {
        return $this->width * $this->height;
    }
    Of course we added the above method to our Rectangle class to provide the area.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class LspTest extends PHPUnit_Framework_TestCase {
        function testRectangleArea() {
            $r = new Rectangle();
            $c = new Client();
            $this->assertTrue($c->areaVerifier($r));
        }
    }
    And we created a simple test by sending an empty rectangle object to area verifier and the test passes. If our Square class is correctly defined, sending it to the Client's areaVerifier() should not break its functionality. After all, a Square is a Rectangle in all mathematical sense. But is our class?
    1
    2
    3
    4
    5
    function testSquareArea() {
        $r = new Square();
        $c = new Client();
        $this->assertTrue($c->areaVerifier($r));
    }
    Testing it is very easy and it breaks big time. An exception is thrown to us when we run the test above.
    1
    2
    3
    4
    5
    PHPUnit 3.7.28 by Sebastian Bergmann.
    Exception : Bad area!
    #0 /paht/: /.../.../LspTest.php(18): Client->areaVerifier(Object(Square))
    #1 [internal function]: LspTest->testSquareArea()
    So, our Square class is not a Rectangle after all. It breaks the laws of geometry. It fails and it violates the Liskov Substitution Principle.
    I especially love this example because it not only violates LSP, it also demonstrates that object oriented programming is not about mapping real life to objects. Each object in our program must be an abstraction over a concept. If we try to map one-to-one real objects to programmed objects, we will almost always fail.
    The Single Responsibility Principle is about actors and high level architecture. The Open/Closed Principle is about class design and feature extensions. The Liskov Substitution Principle is about subtyping and inheritance. The Interface Segregation Principle (ISP) is about business logic to clients communication.
    In all modular applications there must be some kind of interface that the client can rely on. These may be actual Interface typed entities or other classic objects implementing design patterns like Facades. It doesn't matter which solution is used. It always has the same scope: to communicate to the client code on how to use the module. These interfaces can reside between different modules in the same application or project, or between one project as a third party library serving another project. Again, it doesn't matter. Communication is communication and clients are clients, regardless of the actual individuals writing the code.
    So, how should we define these interfaces? We could think about our module and expose all the functionalities we want it to offer.


    hugeInterface
    This looks like a good start, a great way to define what we want to implement in our module. Or is it? A start like this will lead to one of two possible implementations:
    • A huge Car or Bus class implementing all the methods on the Vehicle interface. Only the sheer dimensions of such classes should tell us to avoid them at all costs.
    • Or, many small classes like LightsControl, SpeedControl, or RadioCD which are all implementing the whole interface but actually providing something useful only for the parts they implement.
    It is obvious that neither solution is acceptable to implement our business logic.


    specializedImplementationInterface
    We could take another approach. Break the interface into pieces, specialized to each implementation. This would help to use small classes that care about their own interface. The objects implementing the interfaces will be used by the different type of vehicles, like car in the image above. The car will use the implementations but will depend on the interfaces. So a schema like the one below may be even more expressive.


    carUsingInterface
    But this fundamentally changes our perception of the architecture. The Car becomes the client instead of the implementation. We still want to provide to our clients ways to use our whole module, that being a type of vehicle.


    oneInterfaceManyClients
    Assume we solved the implementation problem and we have a stable business logic. The easiest thing to do is to provide a single interface with all the implementations and let the clients, in our case BusStation, HighWay, Driver and so on, to use whatever thew want from the interface's implementation. Basically, this shifts the behavior selection responsibility to the clients. You can find this kind of solution in many older applications.
    The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use.
    However, this solution has its problems. Now all the clients depend on all the methods. Why should a BusStation depend on the state of lights of the bus, or on the radio channels selected by the driver? It should not. But what if it does? Does it matter? Well, if we think about the Single Responsibility Principle, it is a sister concept to this one. If BusStation depends on many individual implementations, not even used by it, it may require changes if any of the individual small implementations change. This is especially true for compiled languages, but we can still see the effect of the LightControl change impacting BusStation. These things should never happen.
    Interfaces belong to their clients and not to the implementations. Thus, we should always design them in a way to best suite our clients. Some times we can, some times we can not exactly know our clients. But when we can, we should break our interfaces in many smaller ones, so they better satisfy the exact needs of our clients.


    segregatedInterfaces
    Of course, this will lead to some degree of duplication. But remember! Interfaces are just plain function name definitions. There is no implementation of any kind of logic in them. So the duplications is small and manageable.
    Then, we have the great advantage of clients depending only and only on what they actually need and use. In some cases, clients may use and need several interfaces, that is OK, as long as they use all the methods from all the interfaces they depend on.
    Another nice trick is that in our business logic, a single class can implement several interfaces if needed. So we can provide a single implementation for all the common methods between the interfaces. The segregated interfaces will also force us to think of our code more from the client's point of view, which will in turn lead to loose coupling and easy testing. So, not only have we made our code better to our clients, we also made it easier for ourselves to understand, test and implement.
    LSP taught us why reality can not be represented as a one-to-one relation with programmed objects and how subtypes should respect their parents. We also put it in light of the other principles that we already knew.
    ISP teaches us to respect our clients more than we thought necessary. Respecting their needs will make our code better and our lives as programmers easier.
    Thank you for your time.
  • Most Android devices don't have a physical keyboard. Instead, they rely on a virtual or soft keyboard to accept user input. If you're into Android personalization, knowing how to build a custom, soft keyboard can take your hobby to a whole new level.
    Using the Android SDK, you can quickly create a soft keyboard with surprisingly few lines of code, because the SDK takes care of a lot of the low level tasks, such as recognizing key touches, drawing the keyboard, and establishing connections between the keyboard and input fields.

    In this tutorial, you will learn how to create a fully functional soft keyboard that can serve as your Android device's default keyboard.
    You will need the Eclipse ADT Bundle installed. You can download it from the Android Developer website.
    Fire up Eclipse and create a new Android application. Call this application, SimpleKeyboard. Make sure you choose a unique package name. Set the minimum required SDK to Android 2.2 and set the target SDK to Android 4.4.

    This app will have no activities so deselect Create Activity and click Finish.


    A soft keyboard is considered as an Input Method Editor (IME) by the Android operating system. An IME is declared as a Service in AndroidManifest.xml that uses the BIND_INPUT_METHOD permission, and responds to the action android.view.InputMethod.
    Add the following lines to the application tag of the manifest:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <service android:name=".SimpleIME"
        android:label="@string/simple_ime"
        android:permission="android.permission.BIND_INPUT_METHOD"
        >
        <meta-data android:name="android.view.im" android:resource="@xml/method"/>
        <intent-filter>
            <action android:name="android.view.InputMethod" />
        </intent-filter>           
    </service>
    The service tag in the manifest file containes a meta-data tag that references an XML file named method.xml. Without this file, the Android operating system won't recognize our Service as a valid IME service. The file contains details about the input method and its subtypes. For our keyboard, we define a single subtype for the en_US locale. Create the directory res/xml if it doesn't exist, and add the file method.xml to it. The contents of the file should be:
    1
    2
    3
    4
    5
    6
    7
    <?xml version="1.0" encoding="utf-8"?>
    <input-method xmlns:android="http://schemas.android.com/apk/res/android">
        <subtype
            android:label="@string/subtype_en_US"      
            android:imeSubtypeLocale="en_US"
            android:imeSubtypeMode="keyboard" />
    </input-method>
    The strings that this app uses are defined in the res/values/strings.xml file. We're going to need three strings:
    • the name of the app
    • the label of the IME
    • the label of the IME's subtype
    Update your strings.xml so that it has the following contents:
    1
    2
    3
    4
    5
    <resources>
        <string name="app_name">SimpleKeyboard</string>
        <string name="simple_ime">Simple IME</string>
        <string name="subtype_en_US">English (US)</string>
    </resources>
    The layout of our keyboard contains only a KeyboardView. The layout_alignParentBottom attribute is set to true so that keyboard appears at the bottom of the screen.
    Create a file named res/layout/keyboard.xml and replace its contents with the following:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?xml version="1.0" encoding="UTF-8"?>
    <android.inputmethodservice.KeyboardView
        android:id="@+id/keyboard"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:keyPreviewLayout ="@layout/preview"
    />
    The keyPreviewLayout is the layout of the short-lived pop-up that shows up whenever a key on the keyboard is pressed. It contains a single TextView. Create a file named res/layout/preview.xml and add the following to it:
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    <?xml version="1.0" encoding="utf-8"?>
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:background="#ffff00"   
        android:textStyle="bold"
        android:textSize="30sp"
        >   
    </TextView>
    The details of the keyboard keys and their positions are specified in an XML file. Every key has the following attributes:
    • keyLabel: This attribute contains the text that is displayed on the key.
    • codes: This attribute contains the unicode values of the characters that the key represents.
    For example, to define a key for the letter A, the codes attribute should have the value 97 and the keyLabel attribute should be set to A.
    If more than one code is associated with a key, then the character that the key represents will depend on the number of taps the key receives. For example, if a key has the codes 63, 33, and 58:
    • a single tap on the key results in the character ?
    • two taps in quick succession results in the character !
    • three taps in quick succession results in the character :
    A key can also have a few optional attributes:
    • keyEdgeFlags: This attribute can take the value left or right. This attribute is usually added to the leftmost and rightmost keys of a row.
    • keyWidth: This attribute defines the width of a key. It's usually defined as a percentage value.
    • isRepeatable: If this attribute is set to true, long-pressing the key will repeat the action of the key multiple times. It is usually set to true for the delete and spacebar keys.
    The keys of a keyboard are grouped as rows. It's good practice to limit the number of keys on a row to a maximum of ten, with each key having a width equal to 10% of the keyboard. The height of the keys is set to 60dp in this tutorial. This value can be adjusted, but values less than 48dp are not recommended. Our keyboard will have five rows of keys.
    We can now go ahead and design the keyboard. Create a new file named res/xml/qwerty.xml and replace its contents with the following:
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
        android:keyWidth="10%p"
        android:horizontalGap="0px"
        android:verticalGap="0px"  
        android:keyHeight="60dp"
    >
        <Row>
            <Key android:codes="49" android:keyLabel="1" android:keyEdgeFlags="left"/>
            <Key android:codes="50" android:keyLabel="2"/>
            <Key android:codes="51" android:keyLabel="3"/>
            <Key android:codes="52" android:keyLabel="4"/>
            <Key android:codes="53" android:keyLabel="5"/>
            <Key android:codes="54" android:keyLabel="6"/>
            <Key android:codes="55" android:keyLabel="7"/>
            <Key android:codes="56" android:keyLabel="8"/>
            <Key android:codes="57" android:keyLabel="9"/>
            <Key android:codes="48" android:keyLabel="0" android:keyEdgeFlags="right"/>
        </Row>
        <Row>
            <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
            <Key android:codes="119" android:keyLabel="w"/>
            <Key android:codes="101" android:keyLabel="e"/>
            <Key android:codes="114" android:keyLabel="r"/>
            <Key android:codes="116" android:keyLabel="t"/>
            <Key android:codes="121" android:keyLabel="y"/>
            <Key android:codes="117" android:keyLabel="u"/>
            <Key android:codes="105" android:keyLabel="i"/>
            <Key android:codes="111" android:keyLabel="o"/>
            <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
        </Row>
        <Row>
            <Key android:codes="97" android:keyLabel="a" android:keyEdgeFlags="left"/>
            <Key android:codes="115" android:keyLabel="s"/>
            <Key android:codes="100" android:keyLabel="d"/>
            <Key android:codes="102" android:keyLabel="f"/>
            <Key android:codes="103" android:keyLabel="g"/>
            <Key android:codes="104" android:keyLabel="h"/>
            <Key android:codes="106" android:keyLabel="j"/>
            <Key android:codes="107" android:keyLabel="k"/>      
            <Key android:codes="108" android:keyLabel="l"/>
            <Key android:codes="35,64" android:keyLabel="\# \@" android:keyEdgeFlags="right"/>
        </Row>
        <Row>
            <Key android:codes="-1" android:keyLabel="CAPS" android:keyEdgeFlags="left"/>
            <Key android:codes="122" android:keyLabel="z"/>
            <Key android:codes="120" android:keyLabel="x"/>
            <Key android:codes="99" android:keyLabel="c"/>
            <Key android:codes="118" android:keyLabel="v"/>
            <Key android:codes="98" android:keyLabel="b"/>
            <Key android:codes="110" android:keyLabel="n"/>
            <Key android:codes="109" android:keyLabel="m"/>
            <Key android:codes="46" android:keyLabel="."/>
            <Key android:codes="63,33,58" android:keyLabel="\? ! :" android:keyEdgeFlags="right"/>   
        </Row>
        <Row android:rowEdgeFlags="bottom">
            <Key android:codes="44" android:keyLabel="," android:keyWidth="10%p"  android:keyEdgeFlags="left"/>  
            <Key android:codes="47" android:keyLabel="/" android:keyWidth="10%p" />
            <Key android:codes="32" android:keyLabel="SPACE" android:keyWidth="40%p" android:isRepeatable="true"/>       
            <Key android:codes="-5" android:keyLabel="DEL" android:keyWidth="20%p" android:isRepeatable="true"/>
            <Key android:codes="-4" android:keyLabel="DONE" android:keyWidth="20%p" android:keyEdgeFlags="right"/>
        </Row>   
    </Keyboard>
    You may have noticed that some keys have negative values for the codes attribute. Negative values are equal to predefined constants in the Keyboard class. For example, the value -5 is equal to the value of Keyboard.KEYCODE_DELETE.
    Create a new Java class and call it SimpleIME.java. The class should extend InputMethodService class and implement the OnKeyboardActionListener interface. The OnKeyboardActionListener interface contains the methods that are called when keys of the soft keyboard are tapped or pressed.
    The SimpleIME class should have three member variables:
    • KeyboardView referencing the view defined in the layout
    • Keyboard instance that is assigned to the KeyboardView
    • boolean telling us if the caps lock is enabled
    After declaring these variables and adding the methods of the OnKeyboardActionListener interface, the SimpleIME class should look like this:
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    public class SimpleIME extends InputMethodService
        implements OnKeyboardActionListener{
         
        private KeyboardView kv;
        private Keyboard keyboard;
         
        private boolean caps = false;
        @Override
        public void onKey(int primaryCode, int[] keyCodes) {       
        }
        @Override
        public void onPress(int primaryCode) {
        }
        @Override
        public void onRelease(int primaryCode) {           
        }
        @Override
        public void onText(CharSequence text) {    
        }
        @Override
        public void swipeDown() {  
        }
        @Override
        public void swipeLeft() {
        }
        @Override
        public void swipeRight() {
        }
        @Override
        public void swipeUp() {
        }
    }
    When the keyboard is created, the onCreateInputView method is called. All the member variables of the Service can be initialized here. Update the implementation of the onCreateInputView method as shown below:
    1
    2
    3
    4
    5
    6
    7
    8
    @Override
    public View onCreateInputView() {
        kv = (KeyboardView)getLayoutInflater().inflate(R.layout.keyboard, null);
        keyboard = new Keyboard(this, R.xml.qwerty);
        kv.setKeyboard(keyboard);
        kv.setOnKeyboardActionListener(this);
        return kv;
    }
    Next, we create a method that plays a sound when a key is pressed. We use the AudioManager class to play the sounds. The Android SDK includes a few default sound effects for key presses and those are used in the playClick method.
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    private void playClick(int keyCode){
        AudioManager am = (AudioManager)getSystemService(AUDIO_SERVICE);
        switch(keyCode){
        case 32:
            am.playSoundEffect(AudioManager.FX_KEYPRESS_SPACEBAR);
            break;
        case Keyboard.KEYCODE_DONE:
        case 10:
            am.playSoundEffect(AudioManager.FX_KEYPRESS_RETURN);
            break;
        case Keyboard.KEYCODE_DELETE:
            am.playSoundEffect(AudioManager.FX_KEYPRESS_DELETE);
            break;             
        default: am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD);
        }      
    }
    Finally, update the onKey method so that our keyboard app can communicate with input fields (usually EditText views) of other applications.
    The getCurrentInputConnection method is used to get a connection to the input field of another application. Once we have the connection, we can use the following methods:
    • commitText to add one or more characters to the input field
    • deleteSurroundingText to delete one or more characters of the input field
    • sendKeyEvent to send events, like KEYCODE_ENTER, to the external application
    Whenever a user presses a key on the soft keyboard, the onKey method is called with the unicode value of the key as one of its parameters. Based on this value, the keyboard  performs one of the following actions:
    • If the code is KEYCODE_DELETE, one character to the left of the cursor is deleted using the deleteSurroundingText method.
    • If the code is KEYCODE_DONE, a KEYCODE_ENTER key event is fired.
    • If the code is KEYCODE_SHIFT, the value of the caps variable is changed and the shift state of the keyboard is updated using the setShifted method. The keyboard needs to be redrawn when the state changes so that the labels of the keys are updated. The invalidateAllKeys method is used to redraw all keys.
    • For all other codes, the code is simply converted into a character and sent to the input field. If the code represents a letter of the alphabet and the caps variable is set to true, then the character is converted to uppercase.
    Update the onKey method so that it looks like this:
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    @Override
    public void onKey(int primaryCode, int[] keyCodes) {       
        InputConnection ic = getCurrentInputConnection();
        playClick(primaryCode);
        switch(primaryCode){
        case Keyboard.KEYCODE_DELETE :
            ic.deleteSurroundingText(1, 0);
            break;
        case Keyboard.KEYCODE_SHIFT:
            caps = !caps;
            keyboard.setShifted(caps);
            kv.invalidateAllKeys();
            break;
        case Keyboard.KEYCODE_DONE:
            ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
            break;
        default:
            char code = (char)primaryCode;
            if(Character.isLetter(code) && caps){
                code = Character.toUpperCase(code);
            }
            ic.commitText(String.valueOf(code),1);                 
        }
    }
    Advertisement
    The soft keyboard is now ready to be tested. Compile and run it on an Android device. This app doesn't have an Activity, which means that it won't show up in the launcher. To use it, it should first be activated in the device's Settings.

    After activating Simple IME, open any app that allows text input (for example, any messaging app) and click on one of its input fields. You should see a keyboard icon appear in the notifications area. Depending on your device, you can either click on that icon or drag the notification bar down and select Simple IME as the input method. You should now be able to type using your new keyboard.


    In this tutorial, you have learned how to create a custom keyboard app from scratch. To change the look and feel of your keyboard, all you have to do is add extra styling to the res/layout/keyboard.xml and res/layout/preview.xml files. To change the positions of the keys, update the res/xml/qwerty.xml file. To add more features to your keyboard, refer to the developer documentation.

Comments

The Visitors says
Download Free Software Latest Version