Showing posts with label android. Show all posts
Showing posts with label android. Show all posts

Monday, November 9, 2015

The good and the bad of a Chrome OS/Android merge

Recently there have been reports of Google planning to merge Chrome OS and Android. I've been a Chrome OS user for 3 years (got a Pixel at Google I/O and have been loving it) and an Android user for 5 years. So I feel like I understand both operating systems very well.

My hope is that Google will take the best of both worlds and make something better. So in order to help facilitate that, here's my high level best/worst lists for each.

[update: 12/19/2015]: Russian translation available here thanks to Vlad!

What Chrome OS Does Well


Simplicity: Chrome OS is simple and intuitive. I would feel absolutely comfortable giving a chromebook to my mom or dad and be confident that they could surf the web and check their email without any problems.

Multiple Users: At first I didn't like that you had to log in to a Chromebook with a GMail account. But after using the Pixel for 3 years I think it makes things easier. My wife and I both share the Pixel and we do so with zero hassle. Logging in and out is fast and intuitive and there isn't an extra username/password that you have to remember. When friends come over and want to surf the web they can pick up the Pixel and start using it right away using their GMail account (and it seems almost everyone has one).

The Web: It should go without saying (but it won't) that an OS built on top of a browser should be good at surfing the web. And it is. Web based email works great. Streaming media like Netflix, Amazon Instant Video, and etc work great. Chrome OS just works when it comes to the web.

SSH: Surprisingly, Chrome OS's Secure Shell program works great and supports key based authentication. This makes using Chrome OS to work on servers simple and efficient.

Native Development Support: If you're a developer you can get shell access with crosh and use crouton to create a chroot to another flavor of Linux (like Ubuntu). Once you have a chroot setup you'll have access to the full suite of developer tools that Linux has to offer.

What Android Does Well


Niche Apps: I've come to rely on apps like KeePass, Owncloud, and Baby Connect in my day to day life. These apps go with me wherever I go and give me instant access to important information. Android is a very friendly environment for niche apps.

Mobile First Experiences: Instagram, Maps and Navigation (disclaimer, I work at Amazon on Maps), streaming media (these have web counterparts but they're lesser experiences than the mobile apps IMO because they're cluttered with display advertising), and a whole plethora of other apps that are built first for mobile and second for the web.

Games: While I'm not a big gamer, Android does games well. Games like Angry Birds, Fruit Ninja, Plants vs Zombies, Cut the Rope, Solitaire, Sudoku, and etc all run great and are fun on Android.

Contacts, Calendar, and Email: The native apps for contacts, email, and calendar work great, sync great, and are easy to use.

Switch to developer mode: Developer mode isn't turned on by default in Android. But it's really easy to enable. Just tap on the system version several times and you've unlocked the power of being a developer.

What Chrome OS Doesn't Do Well


Apps: Chrome is pretty horrible for apps. The HTML5 based apps I've used are clunky and not very functional.

Switch to developer mode: Switching to developer mode on Chrome OS is awful. It requires you to wipe your machine when switching in and out of developer mode. While booting up in developer mode you're presented with a terrible screen telling you that OS Verification is off. If you hit space bar you wipe the machine. Switching in and out of developer mode just isn't easy.

What Android Doesn't Do Well


The Web: The biggest concern I have with Android is that web surfing still sucks. So many sites still use flash or heavy javascript and just don't run well on Android. Web pages aren't optimized for small screens still (and probably won't ever really be). The web also wasn't made for touching but Android's primary input mechanism is touch.

Monday, March 2, 2015

Android WebView: HTML5 Video and Rotation

In the last few posts I've shown you have to add a Web View to your Android application. Web View's are useful for showing external content without leaving your app, adding dynamic content to your app allowing change without an app update, or simply display legal pages (terms of service, end user license agreements, etc) directly from your site.

I wanted to end this brief series talking about HTML5 video and rotation. There are some gotchas that it's important to be aware of in these areas that I've come across over the past few years.

Stopping Video


In some versions of Android the audio of video that was playing in a Web View will continue to play in the background even after exiting the Fragment or Activity that is hosting the web view. This appears to be a bug where onPause isn't being called on the web view in those versions of Android.

Handling this isn't simply a matter or calling onPause explicitly on the Web View if your app is targeting devices running Gingerbread or below. This is because those versions of Android don't have a public onPause method on the Web View.

The easiest way to handle this in a platform version independent way is to call the Web View's onPause method via reflection.

Class.forName("android.webkit.WebView")
         .getMethod("onPause", (Class[])null)
         .invoke(yourWebView, (Object[])null);

Playing Arbitrary HTML5 Videos


Web video players are very very fragile when it comes to playing arbitrary HTML5 video. Sites that host HTML5 video often rely on nuances that are present in some browsers or javascript engines but not in others. You may find that some HTML5 video just will not play in your embedded Web View.

Playing HTML5 Video Using A Native Video Player


While it is possible to create a custom WebChromeClient to intercept HTML5 video and play it using a native video player I would not recommend this. There are several problems with this approach that are very difficult to overcome.

  • You need to intercept ALL media playback events from javascript and handle them natively.
  • Handling all video related javascript events can only be accomplished by injecting javascript in the page or having direct access to the javascript engine.
  • Some pages dynamically load videos on the page which means you have to be monitoring the DOM for changes.

Rotation


Depending on how you handle rotation in your Activities and Fragments you may be accidentally reloading web pages on rotation. This happens when the call to loadUrl is made during the Activity or Fragment setup, since the rotation workflow causes the Activity or Fragment to be recreated.

This can be made even worse when the user had navigated several links deep into a web page. When the app is rotated and the Url is reloaded the user will find themselves back on the initial web page and not where they expect.

Handling rotation with a WebView can be done by:

Monday, February 23, 2015

Android WebView: Interacting with your apps Java from the Web Page.

In my previous post, Android WebView: Interacting with the Web Page from Java, I showed you how to call javascript functions from your native Java code. I also briefly mentioned that on Android KitKat and above you have the option of receiving the result directly from the javascript call using the evaluateJavascript method. This is an easy way to handle any result that may have been returned from the javascript call.

The evaluateJavascript method is very helpful but does not handle all use cases in which you may need to execute native Java code initiated by your web page. One use case would be handling web events (like a button click) or for returning data from a method on Android Jelly Bean or below.

One word of warning before we begin, allowing an HTML page to execute native Java code is inherently insecure. Web pages are isolated from the app they're running in by design. Creating a bridge between the web page and the app means that javascript on the page may have the ability to do anything on the device for which the app that's hosting the web page has permissions for. It is extremely important that you keep this in mind when creating the bridge from the web page to your app.

There are three main things that you need to do in order to call native Java methods from javascript:

  • Create a javascript interface object which serves as the bridge between javascript and Java.
  • Add the interface to the Web View so that any page that it hosts can use the bridge.
  • Update your javascript to use the bridge.

Creating a Javascript Interface Object


In this very basic class I am declaring a listener interface which I have the caller pass in via the constructor. This is one way to make sure that you only allow javascript to interact with your app in the way you expect. By using a well defined interface you are making it more difficult for malicious javascript to execute arbitrary code.

To make a method visible to javascript the method needs to be decorated with the @JavascriptInterface attribute.

Notice that I'm calling the listener back using a handler and runnable. This is because the bridge class methods aren't executing on the main thread. We need to execute the listener callback on the main thread.

public class WebViewNativeBridge
{
    public interface OnSomeChangeListener {
        public void onSomeChange(String text);
    } 
    private OnSomeChangeListener listener = null;
    public WebViewNativeBridge(OnSomeChangeListener listener) {
        this.listener = listener;
    } 
    @JavascriptInterface @SuppressWarnings("unused")
    public void someChangeFromWeb(String text)
    {
        final String newText = text; 
        // This code is not executed in the UI thread
        // so we must force it to happen
        new Handler(Looper.getMainLooper()).post(new Runnable()
        {
            @Override
            public void run()
            {
                if(listener != null)
                    listener.onSomeChange(newText);
            }
        });
    }
}

Add The Javascript Bridge Object To Your WebView


The way you wire up your native javascript bridge is to add the javascript interface to your Web View using the addJavascriptInterface method. This method takes in two parameters, your bridge object and the key word you want to use to access the bridge from within javascript.

WebViewNativeBridge.OnSomeChangeListener listener
                                                        = this.getOnSomeChangeListener();
WebViewNativeBridge bridge = new WebViewNativeBridge(listener);
browser.addJavascriptInterface(bridge, "NativeBridge");

I created the bridge object with a custom listener I created:

private WebViewNativeBridge.OnSomeChangeListener getOnSomeChangeListener() {
    return new WebViewNativeBridge.OnSomeChangeListener() {
        public void onSomeChange(String val) {
            // do something with the value passed in
        }
    };
}

Update The Javascript To Use The Native Bridge Object


The last step is to call into your native Java code from javascript. Using the NativeBridge object we registered with the Web View we can call the someChangeFromWeb method from our javascript. Note, the name of the object you use in javascript has to be exactly the same as the second parameter you passed into the addJavascriptInterface method.
function notifyNativeOfChange() {
    NativeBridge.someChangeFromWeb("some change");
}

Monday, February 16, 2015

Android WebView: Interacting with the Web Page from Java.

In my previous post, Android WebView: Displaying web content in your app, I explained the basic's of setting up a WebView to display web content in your app. In this post I'm going to explain how you can interact with the web page you're displaying.

It's not a good idea to try to mess with web page content for pages that you don't control because those pages can change and those changes may break your app or provide a poor user experience in your app. But there are cases when you do want to interact with web pages that you control the content of.

One example is if you provide content in your app via a web page whose content structure changes often. Using a web browser to display this data in your app provides the benefit to your users of being able to update that content without updating the app itself.

The way to accomplish this while providing a good user experience that's less likely to break when you update your web site is to define a Javascript contract for interacting with the web content. This contract is made up of a specific set of Javascript functions that your page offers the app which provides a known outcome. This allows the web page to change and add additional content while adhering to the predefined contract and gracefully failing the web page when the contract needs to change.

Executing Javascript


There are two main options you have to execute Javascript within a web page from native Java code. The option you choose will depend on what the minimum version of Android is that you are targeting in your app.

Executing Javascript on Android 4.4+


The preferred option is to use the evaluateJavascript method in the WebView class. This allows you to send some Javascript to the browser to execute and handle any expected result in native code. The  evaluateJavascript method was introduced in Android 4.4 with a new WebView class based on the Chromium.  Here's an example of how to scroll a web page to the top using this new method:

private void scrollWebPageToTop_KitKatOrAbove(WebView browser)
{
    String myScript = "window.scrollTo(0,0);";
    browser.evaluateJavascript(myScript, new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String result) {
            // Do something with the result
           Log.d("JSEXAMPLE", result);
        }
    });
}

Executing Javascript on Android 4.3 or below


If you are targeting Android 4.3 or below you'll need to use the WebView's loadUrl method to execute your Javascript. Prefacing the your URL with "javascript:" lets the WebView know that you would like to execute Javascript in the context of the current page being displayed.

private void scrollWebPageToTop_JellyBeanOrBelow(WebView browser)
{
    String myScript = "javascript:window.scrollTo(0,0);";
    browser.loadUrl(myScript);
}

The first thing you may have noticed is that you don't get the ability to deal with the result of the Javascript method you just executed. If you are executing Javascript from which you expect a result you'll need to use the WebView's addJavascriptInterface method and explicitly call the callback you've registered. Next week's post will go into greater detail on using this method to allow your web page to trigger native Java code to deal with the result of your Javascript code execution.

For more information on the new WebView based on the Chromium browser see the Migrating to WebView in Android 4.4 page in the Web Apps section of the Android developer guides.

Monday, February 9, 2015

Android WebView: Displaying web content in your app

The web has become a ubiquitous medium for information and communication. We use the web to disseminate information about ourselves, our companies, and our thoughts. We consume our news and other information about the world on the web. We purchase our groceries and gifts on the web. We even communicate with our friends, families, and loved ones over the web.

When we think about the web for native mobile applications we tend to think of the web as a data transfer mechanism. We talk about the cloud, JSON, XML, and RSS; all of which are important but often we need the web as more than the backbone of information. There are a couple of reasons you may want to display web content in your app instead of a native view:

  • You want to show external content without leaving your app.
  • You have dynamic content whose structure changes often.
  • You want to display legal pages (terms of service, end user license agreements, etc) directly from your site.

Today I'm going to start a series on the Android WebView. In this post we'll talk about the basic's of setting up a WebView to display web content in your app. In other posts I'll explain how to interact with web content via Javascript from your app as well as interacting with your app from Javascript. Finally I'll go over some gotchas or hurdles that it's important to be aware of when using a web view in your app.

Initializing the WebView


Initializing the WebView should be done when your Activity or Fragment is created. While the default settings the WebView uses will probably work for most basic web pages, you can be more explicit about how your WebView acts. You specify your preferences using the WebSettings object on your WebView.

Obtaining your WebView's settings is as simple as calling getSettings() on the WebView object. Here's an example of changing some of the default WebView settings:

private void initializeWebView(Bundle savedInstanceState)
{
    /** make sure you have a WebView in your layout with the id: browser **/
    WebView browser = (WebView)getView().findViewById(R.id.browser);

    // I like my scroll bars inside the content
    browser.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);

    // allow web pages to execute Javascript.
    browser.getSettings().setJavaScriptEnabled(true);

    // if you know your web pages use a different encoding than utf-8 you can change it.
    browser.getSettings().setDefaultTextEncodingName("utf-8");

    // Start with content zoomed all the way out.
    browser.getSettings().setLoadWithOverviewMode(true);

    // Allow zooming with the default zoom controls
    browser.getSettings().setSupportZoom(true);
    browser.getSettings().setBuiltInZoomControls(true);

    // Change how the web page is laid out.
    // See WebSettings.LayoutAlgorithm for more detail.
    browser.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);

    // Allow the HTML viewport meta tag to specify the width
    browser.getSettings().setUseWideViewPort(true);

    // Used to handle external interactions. More details below.
    browser.setWebChromeClient(this.getWebChromeClient());

    // Used to handle page events. More details below.
    browser.setWebViewClient(this.getWebViewClient());
}

Loading A Web Page


Once you have a WebView added to your app displaying web pages is as simple as calling loadUrl on the WebView. For example:

private void loadWebPage(String url)
{
    /** make sure you have a WebView in your layout with the id: browser **/
    WebView browser = (WebView)getView().findViewById(R.id.browser);
    browser.loadUrl(url);
}

Loading HTML Directly


Loading web data into your page doesn't have to be done using a remote URL. If you already have the HTML content you'd like to display (possibly from an RSS feed or offline content), you can do so very easily by first converting the content to Base64 and then creating a data URI to display the content. For example, you can generate a url for HTML content you already have that the WebView can display as follows:

private String getUrlFromHTML(String html)
{
    String base64Data = Base64.encodeToString(html.getBytes(), Base64.DEFAULT);
    return String.format("data:text/html;charset=utf-8;base64,%s", base64Data);
}

Advanced Browser Features


Handling Web Page Life-cycle and Other Events


There are several events associated with the web page life-cycle. For example there are events associated with web page loading being started/stopped. There are also events for web page errors as well as a login request from the web page. These web page events are handled via a WebViewClient. Your browser should, at a minimum, provide feedback to the user when a web page starts loading and when it stops loading. Here's an example of how you can handle those events:

protected WebViewClient getWebViewClient()
{
    return new WebViewClient()
    {
        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon)
        {
            /** provide feedback using a spinner or some other dialog to  
the user to let them know that you're doing some work **/
        }

        @Override
        public void onPageFinished(WebView view, String url)
        {

            /** hide the feedback that was provided to the user **/
        }
    };
}


External Browser Interactions


One of the more nuanced aspects of displaying web content in your app is when the web content wants to do something external to the current browser. In order to interact with the browser for some of these more advanced features you need to use a WebChromeClient. For example, playing back HTML5 video fullscreen is done using the WebChromeClient's onShowCustomView method. Other examples of advanced external browser interactions are reporting debug messages to the Javascript console, opening/closing additional browser windows, displaying messages from Javascript, and etc.

It's important to note that there is only one WebChromeClient. Because of this you'll likely want to subclass WebChromeClient if you plan to provide many of these advanced features in your app. Here's an example of creating a WebChromeClient that can update the progress indicator of a Fragments Activity.

private WebChromeClient getWebChromeClient()
{
    return new WebChromeClient()
    {
        public void onProgressChanged(WebView view, int progress)
        {
            if(getActivity() != null)
            {
                getActivity().setProgress(progress * 100);
            }
        }
    };
}

For a more detailed example of integrating a basic web browser into your app, see the BrowserFragment in my open source RSS Reader Android app.

Monday, January 19, 2015

Don't hate your users or potential users

The other day I had an incredibly frustrating experience with iCloud that was 100% avoidable. I'm not an iCloud user but a buddy of mine is and when he had his first kid he sent out an iCloud link to some pictures. Being out and about at the time I tried to open the link on my phone using the mobile Firefox browser. I was greeted with this not-so-friendly message:



Something I've gotten used to doing on my mobile when web pages act weird is to check the "request desktop version" button and guess what? The page loaded fine but I was left with more animosity towards iCloud and Apple for blatantly breaking an experience that should "just work".

So what can we learn from this?
  • Your customers aren't just the people who bought your products. 
  • Provide "correct" details when things aren't going to work.

Your customers aren't just the people who bought your products

Most businesses view their customers as the who buy their products or use their services. But in a day and age where social media has become a primary mechanism for sharing information your customer base is now extended to those that your customers share their content with.

When you're building a product that allows users to share information via a URL you are essentially making a contract with that user that their content will be accessible by those they share it with on a modern web browser.

In the iCloud case I'm baffled at why Apple would explicitly block Android users. I understand they're trying to get people to use their ecosystem, but they also need to face the reality that people in their ecosystem interact with people on the outside.

How do I know they're explicitly blocking Android users? I took a look at the difference between the request headers that Firefox sends normally on Android and what it sends when you check the "request desktop site". The big difference was in Firefox's user agent string

Mobile OnlyMobile: Mozilla/5.0 (Android; Mobile; rv:34.0) Gecko/34.0 Firefox/34.0
With Request Desktop Site CheckedMozilla/5.0 (X11; Linux x86_64; rv:34.0) Gecko/20100101 Firefox/34.0

While I don't think this is the case for iCloud, there may be completely valid reasons they're blocking Android mobile user specifically. For instance they may not have the resources, bandwidth, or experience to test their site fully on enough variations of the Android platform to support the experience. Even if that was the case, which I do not think it is, they need to be more explicit with their users and those trying to use the site that there are known and likely use cases where the experience is not going to be available.

This leads me to the next thing we can learn from this experience.

Provide "correct" details when things aren't going to work

The web is often looked at as an "easier" way to provide a unified experience with broader reach than providing a native experience on multiple platforms. But with that broad reach and unified experience comes more responsibility to provide that reach.

The above screenshot says that my browser isn't supported. That's not true, they don't support my operating system. I tried to open that link in Chrome for Android and the normal Android browser and all three of them had the same experience.

The screenshot also says that I may be able to use iCloud with my phones mail, calendar, and contacts. They know that I'm trying to access a photo album. How is what they're suggesting as a possible fix applicable to my situation? 

Providing your customers (or potential customers) a great experience means being helpful when things don't go as they expect. It's not enough to make the experience work as you expect, you need to make the experience work as THEY expect.

Monday, December 1, 2014

Why is getting your data on a new phone so much work?

Recently my wife upgraded her phone after finishing her two year contract with our mobile provider. She transitioned between phones on the same carrier made by the same manufacturer.

For some context, my wife's primary email comes from a standard IMAP server. She gets her calendars from a standard CalDAV enabled server. She gets her contacts from a standard CardDAV enabled server. She downloads her music and files from a standard WebDAV server. She installs her applications from two app stores, Google Play and Amazon Appstore.

It took us over 4 hours to transition everything from her old phone to her new phone. Why in 2014 is this still so cumbersome?

What transferred/setup without any work

  • The applications installed from the Google Play Store.
  • GMail.
  • Home screen background image.

What we had to manually transfer/setup

  • Applications that were NOT installed from Google Play Store.
  • IMAP email.
  • CalDAV calendars.
  • CardDAV contacts.
  • Lock screen background image.
  • Phone PIN.
  • Phone home screens
    • Widgets.
    • Application Shortcuts.
  • Alarms.
  • Application Settings.
  • Her camera pictures.
  • Her downloaded music.
  • Her downloaded files.
  • 3rd party application data (Instagram, Facebook, Pintrest, and etc).

There's nothing on the second list that couldn't have been automatically transferred. I'm not sure what the right solution is to this problem, but I do know this shouldn't be as much work as it was.

As technologists we put way too much on the shoulders of our users. We expect them to do the heavy lifting for things that we can do easily through software. I think part of this problem is that we, as an industry, don't think enough about the import/export scenarios for our mobile products. But that's sad given that most people are on 2 year contracts with their carriers and they have an opportunity to upgrade their phones if they can financially afford it.

In my opinion this is real opportunity lost.

Monday, October 6, 2014

Starting From Scratch: Android - Creating A Release Build

This week we're finishing the Starting From Scratch series with a look at how to create a release build of our app. I'll show you how to create a release key for your app, secure your release key via encryption, and how to integrate the automatic decryption (and clean up) of your encrypted key during the normal Android Ant build process

Creating a release key for your app


Your key is what identifies your app as being published by you. This is what ensures that only official versions of your app can be released. It's ABSOLUTELY important that noone gets access to your key. DO NOT commit this keystore to your source control repo as is.

Create a release keystore
$ keytool -genkey -v -keystore my.release.keystore -alias myalias -keyalg RSA -keysize 2048 -validity 10000

Securing your release key


Not having the keystore in source control doesn't create a pit of success as you have to manage your key separately from your project. Furthermore, anyone with access to your key can sign an app as you. In order to safely create a pit of success we're going to encrypt our keystore and delete the original so it's not lying around anywhere for someone to abuse.

To encrypt the keystore we'll use openssl and DES3 encryption.
$ openssl des3 -salt -in my.release.keystore -out my.release.keystore.encrypted
$ rm my.release.keystore
The next thing you want to do is put your encrypted keystore in the provisioning directory.
$ mkdir provisioning
$ mv my.release.keystore.encrypted provisioning/

Integrating into the Android Ant build process


Now that we have a key that can be used to sign our applicaiton and we've secured that key from unauthorized access we now need to integrate into the standard Android Ant build process.

The first thing we need to do is create an Ant target that will decrypt the keystore. We also want to create a target to clean up the decrypted keystore immediately after the build. Note that the -decrypt-keystore target supports both prompting the builder for the password or getting the password from an Ant property in the case of an automated release build.

Here's what our encryption.xml file looks like. Create this file in the same directory as your projects build.xml file.
<?xml version="1.0" encoding="UTF-8"?>
<project name="encryption">
    <target name="-decrypt-keystore" depends="" if="isRelease">
        <echo>Decrypting keystore</echo>
        <if>
            <condition>
                <and>
                    <isset property="key.store.password"/>
                </and>
            </condition>
            <then>
                <exec executable="openssl">
                    <arg value="des3"/>
                    <arg value="-d"/>
                    <arg value="-salt"/>
                    <arg value="-in"/>
                    <arg value="provisioning/${assets.keystore}"/>
                    <arg value="-out"/>
                    <arg value="provisioning/release.keystore"/>
                    <arg value="-pass"/>
                    <arg value="pass:${key.store.password}"/>
                </exec>
            </then>
            <else>
                <exec executable="openssl">
                    <arg value="des3"/>
                    <arg value="-d"/>
                    <arg value="-salt"/>
                    <arg value="-in"/>
                    <arg value="provisioning/${assets.keystore}"/>
                    <arg value="-out"/>
                    <arg value="provisioning/release.keystore"/>
                </exec>
            </else>
        </if>
    </target>
    <target name="-clean-keystore" depends="">
        <echo>Cleaning up decrypted keystore</echo>
        <delete file="provisioning/release.keystore"/>
    </target>
</project>
In order to support automated release builds we need to add a few Ant properties to our projects local.properties file. DO NOT CHECK THIS IN TO YOUR SOURCE CONTROL. This file should be restricted as much as possible because it contains the password used to decrypt your keystore. You do not have to put your password in this file. If you don't you'll be prompted to enter your password during the release build.
assets.keystore=my.release.keystore.encrypted
key.store=provisioning/release.keystorekey.alias=myalias
key.alias.password=PASSWORD_YOU_USED_WHEN_CREATING_YOUR_KEYSTORE

key.store.password=PASSWORD_YOU_USED_WHEN_CREATING_YOUR_KEYSTORE
The last thing we need to do is wire up the decryption and cleanup of our key into the existing Android Ant build process. To do this we'll implement the -pre-build, -pre-clean, and -post-build build targets in our custom_rules.xml file. Note that we only want our decryption to happen during a release build. So we're going to define an isRelease property. Our -decrypt-keystore target checks for this property before execution.
<?xml version="1.0" encoding="UTF-8"?><project name="custom_rules">
      <condition property="isRelease"><contains string="${ant.project.invoked-targets}" substring="release"/></condition>

    <target name="-pre-build">
        <antcall target="-decrypt-keystore" />

    </target>

    <target name="-pre-clean" depends="-clean-keystore"></target>
    <target name="-post-build" depends="-clean-keystore"></target></project>
Finally, the last thing we need to do is update our projects build.xml file to include our encryption.xml and custom_rules.xml files.  Add the following two import statements ABOVE the existing ant/build.xml import. For example:
<import file="encryption.xml" optional="false" />
<import file="custom_rules.xml" optional="false" />

<import file="${sdk.dir}/tools/ant/build.xml" />
You can now build a signed release version of your app on the command line with the following command.
$ ant release


Monday, September 29, 2014

Starting From Scratch: Android - Action Bar

This week we're continuing the Starting From Scratch series. Today we're going to take a look at the Action Bar. The action bar UI was first defined in Android 3.x (API 11+). It provided a consistent navigation paradigm for applications as well as a convenient way to present context specific calls to action (like sharing, search, and etc).

Because Android only supported the Action Bar APIs on Android 11+ an open source project called ActionBarSherlock provided support for earlier versions of Android.  ActionBarSherlock is a great project and very useful and eventually Android added a similar Appcompat project to it's v7 support library. While I'm a big fan of ActionBarSherlock I'm going to be using Android's Appcompat project in my example code for consistency.

Creating a project that has an Action Bar.


Setup the support appcompat project as a library. Note, for purposes of this series i'm using android-14 as my target version. You'll want this to match whatever Android version your app is targeting.
$ cd /path/to/android-sdk/extras/android/support/v7/appcompat
$ android update project -p . -t android-14
Make sure that the project.properties file has the android library project property set.
android.library=true
Now that we have our pre-requisites complete let's create an app that will use an Action Bar.
$ cd ~
$ mkdir MyActionBarProject
$ cd MyActionBarProject
$ cp /path/to/android-sdk/extras/android/support/v4/android-support-v4.jar libs/
$ android create project -t android-14 -k com.example. myactionbarproject -p . -n MyActionBarProject -a MainActivity
In order to actually use the support library you need to add a reference to the Android Support Appcompat Library in your project.properties. Note that the path is relative NOT absolute.
android.library.reference.1=relative/path/to/android-sdk/extras/android/support/v7/appcompat
At this point you've got your application setup so that it can use an Action Bar, but it's not using one yet. In order to actually use an Action Bar you'll need to update your Android manifest to use a theme with an Action Bar. This can be done by using either Theme.AppCompat.Light or Theme.AppCompat (dark theme) as the theme of your activity in your AndroidManifest.xml. For example:
<activity android:name="MainActivity"
               android:label="@string/app_name"
               android:theme="@style/Theme.AppCompat.Light">
You'll also want to hint Android that you're going to support an older version of Android. You can do that by adding the following in your AndroidManifest.xml
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="19" />
At this point you can compile your app and install it on your test device or emulator and you'll see an Action Bar. Make sure you either have an emulator running or a device attached in debug mode and run:
$ ant clean && ant debug && ant installd

Action Bar Menu


One of the great things about the Action Bar is that you can provide menu items directly in the action bar. This allows you to provide context specific menu options in a place that is convenient and easy for your users to access.

If you are planning on supporting the Action Bar via the Support Library then the first thing to do is to update your Activity to extend ActionBarActivity instead of Activity.

Android provides an Action Bar Icon Pack which you can download and use in your application. Simply copy the resources from the theme your app is using into your res/drawable folders. For this example we'll use the refresh icon.

$ mkdir ./res/drawable-xxhdpi
$ cp /path/to/icons/holo_light/01_core_refresh/drawable-xxhdpi/ic_action_refresh.png ./res/drawable-xxhdpi/
$ cp /path/to/icons/holo_light/01_core_refresh/drawable-xhdpi/ic_action_refresh.png ./res/drawable-xhdpi/
$ cp /path/to/icons/holo_light/01_core_refresh/drawable-hdpi/ic_action_refresh.png ./res/drawable-hdpi/
$ cp /path/to/icons/holo_light/01_core_refresh/drawable-mdpi/ic_action_refresh.png ./res/drawable-mdpi/

The first thing you need to do to add a menu in your Action Bar is to define the menu layout. In my layout I'll be referencing an icon. Here's what our main_activity_menu.xml looks like.

$ mkdir res/menu
$ vim res/menu/main_activity_menu.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:myapp="http://schemas.android.com/apk/res-auto">
    <item android:id="@+id/menu_refresh"
              android:icon="@drawable/ic_action_refresh"
              android:showAsAction="ifRoom"
              myapp:showAsAction="ifRoom" />
</menu>

Now that we've defined the Action Bar menu we need to inflate it into our Action Bar. This is done via the Activity's onCreateOptionsMenu event.
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.main_activity_menu, menu);
              return super.onCreateOptionsMenu(menu);
          }

When a user selects a menu item the onOptionsItemSelected method will be called. This method is called regardless of which Menu Item was selected. So you'll need to check the id of the item before you handle the action. Here's an example of handling our refresh action.
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if(item.getItemId() == R.id.menu_refresh) {
        Toast.makeText(this, "Refresh selected", Toast.LENGTH_LONG).show();
        return true;
    }

    return super.onOptionsItemSelected(item);
}
At this point you can compile your app and install it on your test device or emulator and you'll see an Action Bar with a refresh menu button. Selecting the refresh button will display message saying Refresh select.
$ ant clean && ant debug && ant installd

Monday, September 22, 2014

Starting From Scratch: Android - Fragments

This week we're continuing the Starting From Scratch series. Today we're going to take a look at Android Fragments. I'll discuss what a Fragment is, the Fragment life-cycle, creating a Fragments options menu, and finally I'll give a few Fragment tips I've found along the way.

What Is A Fragment

Simply put, a Fragment is a way to encapsulate a piece of your applications UI and UI interactions into a reusable set of resources and code. A Fragment is not tied to any particular Activity, but instead can be used within many Activities.

One example of where I've used this modularity in my applications is with Lists. Lists are common in mobile applications and often only differ by what they show. Using Fragments it would be easy to encapsulate creating, displaying, and interaction with a list into a set of reusable code and resources. You could then display multiple lists in a variety of places throughout your app using the same code and resources.

Fragment Life-cycle

The Fragment life-cycle is very similar to the Activity life-cycle we've already gone through in this series. You still have creating, starting, resuming, pausing, stopping, and destroying life-cycle events. But in addition to those you have life-cycle events that are associated with creating your Fragments view and attaching/detaching from an Activity. I'm not going to go through every Fragment life-cycle method but instead will call out two key life-cycle differences from Activities.

The first big difference is in creating the Fragments view. In an Activity this is done via the onCreate method. In a Fragment this is done in the onCreateView method. The onCreateView method is expected to return the view to use with this Fragment. Creating this view is pretty simple, just inflate your Fragments layout using the LayoutInflator passes into this method.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
    return inflater.inflate(R.layout.my_first_fragment_layout, container, false);
}

The next difference comes with the onActivityCreated method. The main purpose of this method is to allow the Fragment to restore state. There are two places that state can be stored. The first is internally within the Fragment via a Bundle. The second is in any arguments that were passed into the Fragment by it's composing Activity.

The internal saved state is passed into the onActivityCreated method in the form of a Bundle. The state that was passed in via arguments can be retrieved via a call to the getArguments method. It's important to restore Fragment state using the correct source of information. For instance, if you get the initial state from the Arguments but then update that state and save it in your Fragments Bundle then it's important to have some logic that determines the correct place to get the saved state from.

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
    super.onActivityCreated(savedInstanceState);
    Bundle arguments = this.getArguments();
    if(arguments != null && arguments.size() > 0)
    {
        // set any state that was passed in
        // via the arguements bundle
        this.someVariable = arguments.getString("SomeVariable");
    }
}

Creating Fragments Options Menu

Creating menu items for your Fragment is a four step process. The fist step is declaring your Fragments options menu in an XML file. The second step is declaring that your Fragment has an options menu. The third step is inflating your options menu layout. The last step is handling users selecting an option in your Fragments menu.

First, create res/menu/my_first_fragment_menu.xml. The important thing to call out here is that each menu item needs to have an id. This id is important because there is one method that is called when the user selects a menu item. So we need a way to differentiate the desired action the user wishes to perform.

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/my_menu_item" android:showAsAction="ifRoom"/>
</menu>

Declaring that your Fragment has an options menu is done via a call to setHasOptionsMenu. This call should be made in the Fragments default constructor.

public MyFirstAndroidFragment()
{
    setHasOptionsMenu(true);
}

Inflating your Fragments options menu is done by overriding the onCreateOptionsMenu method. This is done by passing the MenuInflater's inflate method the id of the menu XML file you created. If you want to use an ActionProvider in your Fragment, like the ShareActionProvider, this is the right time to set that provider up.

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
{
    inflater.inflate(R.menu.my_first_fragment_menu, menu);
    // this is a good time to setup a share action provider
}

Finally, handling the selection of a menu option is done by overriding the onOptionsItemSelected method. This method is called when any menu item is selected. It's a good idea to encapsulate the menu item action into it's own method and just call that method when the item has been selected. It's important to remember to return true in the onOptionsItemSelected method if you did handle the menu item selection.

@Override
public boolean onOptionsItemSelected(MenuItem item)
{
    if (item.getItemId() == R.id.my_menu_item)
    {
        this.handleMyMenuItem();
        return true;
    }
    return false;
}

Fragment Tips

Retaining

One thing that often causes people to stumble is putting a video, web browser, or any other stateful object inside a Fragment. The reason is that when the device changes orientation the Activity (and it's child Fragments) are torn down and recreated. This causes problems when the user isn't expecting it. One way to solve this problem is to tell Android to retain the Fragment instance across Activity re-creation. This is done via a call to setRetainInstance. This call should be made in the Fragments default constructor.

public MyFirstAndroidFragment()
{
    setRetainInstance(true);
}

Cross Fragment Coordination

Cross Fragment coordination is done by declaring an interface in your Fragment for any events you want to allow others Fragments to respond to. The Activities that compose your fragment will implement your Fragments interface and can then dispatch messages to other Fragments that it is composing. This allows you to keep your concerns separated correctly by NOT tightly coupling your Fragment with any other Fragment. It's okay to tightly couple your Activity with your Fragment because your Activity is composing that Fragment.

Monday, September 15, 2014

Starting From Scratch: Android - Activities


This week we're continuing the Starting From Scratch series. Today we're going to take a look at Android Activities. I'll discuss what an Activity is, how you create one, the Activity life-cycle, launching an Activity programmatically, and finally I'll give a few Activity tips I've found along the way.

What Is An Activity


According to the Android documentation, an Activity is "... a single, focused thing that the user can do." Yeah, that's pretty vague. So let's break it down.

In the early days of Android (pre-tablet) an Activity represented pretty much everything on the screen, how it got there, and how you interact with it. Basically the Activity was analogous to a "screen" in an application. If you wanted to display a different screen you either had to dynamically tear down the current screen and rebuild the new screen (very laborious) or, preferably, start a new Activity which owned the UI and interactions for the new screen. This allowed developers to encapsulate the concepts within a screen pretty nicely and manage workflow easier.

As Android matured, and tablets became mainstream, it became necessary to reuse portions of an Activity within other Activities. Thus was born the Fragment (which I'll talk about later in this series). Prior to Fragments a typical application would have many Activities. Once Fragments were introduced the number of Activities an application had were reduced from one per screen to one per workflow.

Creating an Activity


There are two steps necessary when creating an Activity. The first is to write the Java class which extends the Activity class. This class should implement any necessary life-cycle events (which are outlined below). The second step is to declare the Activity in the Android manifest. You can see an example of this by looking at the initial Activity that was created for your application in your AndroidManifest.xml.

Activity Life-cycle


An Activity has several states that it can be in. Those states are associated with creating, starting, resuming, pausing, stopping, or destroying the Activity.

Creating


When an Activity if first being created a call will be made to the Activities onCreate method. The onCreate method is responsible for associating a UI layout with the Activity. This is done via a call to the setContentView method with the associated layout ID. As mentioned previously in this series, a layout is a composition of the UI elements to display on screen.

For Example:
@Override 
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
}

If you have any state that needs to be restored before the official call to onRestoreInstanceState you can do it after the call to setContentView using the saved instance state Bundle passed in. The typical reasons you may want state restored here is if you want to be able to make any decisions on the other life-cycle methods based on the current state of the Activity. Because of this, the onCreate method is passed a Bundle which will contain any state that was saved when the Activity was paused.

You cannot assume that the Bundle passed in to the onCreate method will exist. In the case that there is no previous state or if it's the first time the Activity is run the Bundle will be null. Therefore it is very important to check to make sure the Bundle exists before you try to use it. If the Bundle exists you can use the information you've saved in it to restore the internal state of the Activity.

NOTE: It is really important to draw the distinction here between resetting the internal state of an Activity and resetting the UI of an Activity. YOU SHOULD NOT try to interact with the UI in the onCreate method. The onCreate method is called when the Activity is starting but before it is displayed on the screen. If you try to get a handle to UI elements they will not exist.


Restarting


The onCreate method is only called when the Activity is not already in memory. Often it is the case that the Activity has been paused but not cleared out of memory. In this case when the Activity is about to be re-started the onRestart method is called. For most people there really isn't anything you need to do in onRestart. The internal state of your Activity will be the same as when it was paused, so there is nothing to really restore. The exception being if you are using a Cursor.

@Override 
public void onRestart() {
}

Starting


The onStart method is called after either onCreate or onRestart when the Activity is visible to the user. The Activity UI is not yet ready to be interacted with. At this point you still don't want to do anything programmatically with UI elements but you are able to reset internal Activity data.

@Override 
public void onStart() {
}

Resuming


Finally, we're at the point in the Activity life-cycle where you can do something with the UI. The onResume method is called right after the Activity has been displayed on the screen. At this point the UI is ready to be interacted with. If you did not register your UI event listeners in the layout file this is where you would want to register those listeners.
@Override 
public void onResume() {
}


At this point in the life-cycle your Activity is considered to be running.

Pausing


When your Activity is about to go into the background, but it is still active, the onPause method is called. This happens when another Activity is about to be put on top of your Activity (while it's still running) or your Activity is about to be put into the background or killed.
@Override 
public void onPause() {
}

This is a good time to commit changes to data and unwire any listeners that you've set up. It's important to note that onPause doesn't necessarily mean that your Activity is going into the background. This will also be called when a dialog is displayed on top of your Activity. In this case the next life-cycle event will be onResume instead of onStop.

It's important that this method returns very quickly as the onResume method for the Activity about to show will not get called until this method returns.

Stopping


The onStop method is called when another Activity has come to the foreground and fills the entire screen. On stop cause be used to save state as well as stop things that are time intensive.
@Override 
public void onStop() {
}

Destroying


The onDestroy method is called right before your Activity is about to be removed from memory. This is a good time to clean up the internal handles that your Activity may have.
@Override 
public void onDestroy() {
}

Saving State


In the previous life-cycle events I mentioned saving/restoring Activity state several times. Users will expect an Activity to be in the same state that they left it in. The user can leave the Activity for a variety of reasons like to answer a phone call or to use another app. Sometimes when a user has a lot of applications running or is doing something very memory intensive Android needs to completely remove the Activity from memory when it's not on the screen. In that case displaying the Activity again will cause it to re-display as if it was never on the screen to begin with. This is why it is important to make sure you restore the internal Activity state if there is any to restore

Saving state is done via the onSaveInstanceState method. Restoring state is done via the onRestoreInstanceState method. The onSaveInstanceState method is called before the Activity is killed. If onSaveInstanceState is called, it will be called before the onStop life-cycle event is called. The onSaveInstanceState is passed a Bundle which should be used to save instance state too. That bundle will then be passed back to the onCreate life-cycle event.

onRestoreInstanceState is called after onStart and is used to restore any state saved in the call to onSaveInstanceState. For example:
@Override
public void onSaveInstanceState(Bundle outState) {
    outState.putString("SomeVariable", someVariable);
@Override
public void onRestoreInstanceState(Bundle savedState) {
    if(savedState != null) {
        this.someVariable = savedState.getString("SomeVariable");
    }
}

Launching an Activity


There are two main ways to launch an Activity. The first way is to specify the type of action you want performed and allowing the user to select the appropriate application they want to handle that. This is commonly used when sharing data or launching a url.
private void openUrlInBrowser(String url) {
    Uri uri = Uri.parse(url);
    Intent intent = new Intent(Intent.ACTION_VIEW, uri);
    this.startActivity(intent);
}

The second way to launch an Activity is by class name. This is the case when you want to launch a very specific Activity. Typically this is how you would launch another Activity within your application.

private void openSomeOtherActivity() {
    Intent intent = new Intent(this, SomeOtherActivity.class);
    this.startActivity(intent);
}

In either case launching an Activity is a two step process. The first step is to create an Intent which details what you want to do. You decide how you want to launch the Activity based on the Intent constructor you select. The second step is to call the startActivity method from your Activity.


Activity Tips



  • Only restore an Activities internal state in onCreate. Never try to restore UI.
  • The first time an Activity is being displayed, the Bundle passed into onCreate will be null.
  • Reset an Activities UI in onResume.
  • Don't change the name of the Activity used to start the application. Changing that will cause the any home screen icons associated with the previous Activity to be broken after the application is updated. The is because the icons are associated with the previous Activity name.