An introduction to Android’s App Shortcuts

Android Nougat 7.1, the newest version of Android at the time of writing this article, comes with several new features.  The one that we will be exploring in this blog post is the App Shortcuts feature.

App Shortcuts allows you to create shortcuts for specific actions within your app that can bring users directly from the launcher to the associated screens. The feature is available on any launcher that supports them, such as Google Now and Pixel launchers. To reveal the shortcuts of an app, simply long-press the launcher icon of that app.

chrome-app-shortcuts

If you are enrolled in the Beta program and got the Android 7.1 Developer Preview, you can find that some of the apps already support App Shortcuts, for example: Camera, Dialer, Messenger, Google Drive, and Youtube. Unfortunately there’s no visual indication on which apps support this feature (at least not at this time), so you will have to long-press on every app’s launcher icon in order to discover the ones that support it.

Adding App Shortcuts to your app

There are 2 ways that can be used to add app shortcuts to your app:  statically (by declaring all the shortcuts in a resource file, also known as manifest shortcuts) and dynamically (by adding the shortcuts at runtime).

First we will take a look at the static approach.

1. Create a resource file: res/xml/shortcuts.xml

<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
    
    <shortcut
        android:shortcutId="new_task"
        android:enabled="true"
        android:icon="@drawable/ic_shortcut_new_task"
        android:shortcutLongLabel="@string/shortcut_label_create_new_task"
        android:shortcutShortLabel="@string/shortcut_label_new_task"
        android:shortcutDisabledMessage="@string/shortcut_label_disabled" >
        <intent
            android:action="android.intent.action.VIEW"
            android:targetClass="com.example.vgrec.testshortcuts.NewTaskActivity"
            android:targetPackage="com.example.vgrec.testshortcuts" />
    </shortcut>

    <shortcut
        android:shortcutId="opened_tasks"
        android:enabled="true"
        android:icon="@drawable/ic_shortcut_opened_tasks"
        android:shortcutLongLabel="@string/shortcut_label_view_opened_tasks"
        android:shortcutShortLabel="@string/shortcut_label_opened_tasks"
        android:shortcutDisabledMessage="@string/shortcut_label_disabled" >
        <intent
            android:action="android.intent.action.VIEW"
            android:targetClass="com.example.vgrec.testshortcuts.OpenedTasksActivity"
            android:targetPackage="com.example.vgrec.testshortcuts" />
    </shortcut>

</shortcuts>

Each shortcut defines a series of attributes, like the icon and labels, and also references an intent which is set to launch a specific activity when triggered.
The recommended maximum number of shortcuts is 4, although it is possible to publish up to 5.

Please note that the android:shortcutId attribute is a mandatory attribute. Not declaring it will cause the respective shortcut not to appear in the shortcuts list.

2. Next step is to reference the shortcuts.xml file in the AndroidManifest.xml as metadata to the app’s launcher activity:

<activity android:name=".MainActivity">
     <intent-filter>
          <action android:name="android.intent.action.MAIN" />
          <category android:name="android.intent.category.LAUNCHER" />
     </intent-filter>

     <meta-data
           android:name="android.app.shortcuts"
           android:resource="@xml/shortcuts" />
</activity>

Any activity that has the intent-filter action set to android.intent.action.MAIN and the category to android.intent.category.LAUNCHER can display app shortcuts.

Having all these pieces in place, the result might look something like this:

android app shortcuts

For testing purposes I used the Android Studio wizard to generate the shortcut icons, but in a production app you should follow the Android Design Guidelines 🙂.

If you tap and drag a shortcut then it will be pinned to the device’s launcher:

android-pinned-app-shortcuts

Have you noticed that the text of the shortcuts in the first image differs from the text of the shortcuts in the second image? Well, that’s what the android:shortcutLongLabel and android:shortcutShortLabel attributes are for.

What about the android:shortcutDisabledMessage? In case a shortcut is disabled it will no longer appear in the shortcuts list. However, if the shortcut is already pinned, it will be simply grayed out and tapping on it will display the message specified by the android:shortcutDisabledMessage attribute.

action_disabled

Adding App Shortcuts dynamically

ShortcutManager is the entry point for manipulating (adding, updating, removing) shortcuts at runtime:

ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);

Intent intent = new Intent(this, NewTaskActivity.class);
intent.setAction(Intent.ACTION_VIEW);

ShortcutInfo shortcut = new ShortcutInfo.Builder(this, getString(R.string.shortcut_id_new_task))
    .setShortLabel(getString(R.string.shortcut_label_new_task))  
    .setLongLabel(getString(R.string.shortcut_label_create_new_task))
    .setIcon(Icon.createWithResource(this, R.drawable.ic_action_add) 
    .setIntent(intent)
    .build();

 shortcutManager.setDynamicShortcuts(Arrays.asList(shortcut));

Other methods from ShortcutManager that might be of interest:

updateShortcuts(List ids) – update all existing shortcuts by ID.

removeDynamicShortcuts(List ids) – delete dynamic shortcuts by ID.

disableShortcuts(List ids) – disable dynamic shortcuts by ID.

reportShortcutUsed(String shortcutId) – You want to call this method whenever the user selects the shortcut containing the given ID or when the user completes an action in the application that is equivalent to selecting the shortcut. Typically, launcher applications use this information to build a prediction model so that they can promote the shortcuts that are likely to be used at the moment. (This method works both for static and dynamic shortcuts.)

Please note that attempting to manipulate a static shortcut via the ShortcutManager will throw IllegalArgumentException: Manifest shortcut ID=xyz may not be manipulated via APIs.

Android BottomNavigationView Example

The 25th version of Android Support Library brings an implementation of the Bottom navigation pattern called BottomNavigationView – a navigation bar intended to make it easy to switch between top level views with a single tap.

Android Bottom Navigation View

In order to benefit from this new addition, the design support library needs to be updated to API Level 25.0.0:

dependencies {
    compile 'com.android.support:design:25.0.0'
    // ...
}

Having the support library updated, the BottomNavigationView now can be used in your layout:

    <android.support.design.widget.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        app:itemBackground="@color/colorPrimary"
        app:itemIconTint="@color/white"
        app:itemTextColor="@color/white"
        app:menu="@menu/bottom_nav_items" />

The menu items are inflated from a menu resource file:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/action_recents"
        android:icon="@drawable/ic_recent"
        android:title="@string/recents" />

    <item
        android:id="@+id/action_favorites"
        android:icon="@drawable/ic_favorite"
        android:title="@string/favorites" />

    <item
        android:id="@+id/action_explore"
        android:icon="@drawable/ic_explore"
        android:title="@string/explore" />

</menu>

Handling click events

From code side BottomNavigationView is as easy to use as from layout side. An implementation of OnNavigationItemSelectedListener is what you need to provide in order to handle events on bottom navigation items.

BottomNavigationView bottomNavigation = 
        (BottomNavigationView) findViewById(R.id.bottom_navigation);
        bottomNavigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                handleBottomNavigationItemSelected(item);
                return true;
            }
        });

// ... 

private void handleBottomNavigationItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_recents:
                switchFragment(new RecentsFragment());
                break;
            case R.id.action_favorites:
                switchFragment(new FavoritesFragment());
                break;
            case R.id.action_explore:
                switchFragment(new ExploreFragment());
                break;
        }
    }

Handling items state

In order to have a full featured example, the last thing that we are missing is to have the navigation items change accordingly to their state, eg.: checked/uncheked. To achieve this we need to provide a ColorStateList resource, and set this resource as background for the app:itemIconTint and app:itemTextColor attributes:

<!-- item_bg.xml -->
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/blue" android:state_checked="true" />
    <item android:color="@color/white" />
</selector>


<!-- activity_main.xml -->
<android.support.design.widget.BottomNavigationView
        // ...       
        app:itemIconTint="@drawable/item_bg"
        app:itemTextColor="@drawable/item_bg" />

And the result will look something like this:

Android navigation view state list

Java 8 Language features supported in Android N: Lambdas

One of the new additions the Android N brings is support for several Java 8 language features:
Lambda expressions
Default and static interface methods, and
Repeatable annotations

In this blog post we will take a look at the lambda expressions.

Setting up development environment

In order to start using these features however, the development environment needs some adjustments. The support of Java 8 language features requires a new compiler called Jack, which currently is supported only in Android Studio 2.1. So if you want to use Java 8 language features, you need to use Android Studio 2.1.

Android documentation describes in details how to setup your development environment for Java 8 support.

Enabling support for Java 8

To enable support for Java 8 in your Android project, the build.gradle file has to be adjusted by setting the compileOptions to version 1.8 and jackOptions enabled.

android {
  ...
  defaultConfig {
    ...
    jackOptions {
      enabled true
    }
  }
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

Lambda Expressions

Lambda expressions are a new feature included in the Java 1.8. A lambda expression basically is a block of code that can be passed around to be executed later. This, in fact, is very similar to anonymous classes. However, unlike anonymous classes, lambda expressions are succinct, as a result producing less verbose code.

Consider the following 3 examples:

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Log.d(TAG, "Clicked.");
    }
});

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
         Log.d(TAG, "Position: " + position);
         Log.d(TAG, "Id: " + id);
    }
 });

Runnable runnable = new Runnable() {
    @Override
     public void run() {
         Log.d(TAG, "From Runnable");
     }
};
new Thread(runnable).start();

Converting the above pieces of code to use lambda expressions will look like this:

button.setOnClickListener(view -> Log.d(TAG, "Clicked."));

listView.setOnItemClickListener((parent, view, position, id) -> {
     Log.d(TAG, "Position: " + position);
     Log.d(TAG, "Id: " + id); 
}

Runnable runnable = () -> Log.d(TAG, "From Runnable"); 
newThread(runnable).start(); 

 

It is clear that the code with the lambdas is shorter.  Specifically, this comes from:

– removal of class instantiation, eg.: new View.OnClickListener(),
– removal of method name, return type, and access modifier, eg.: public void onClick(View view)
– as well as removal of parameter types, eg.: only view, instead of View view.

And what is left, are the actual parameters and the method body.

Lambda Expression syntax

The basic format of a lambda expression is: a list of comma separated parameters, the “->” symbol, and the body.

param1, param2, paramN -> { // body }

1. Parameter types are optional, but they can be also specified in the expression:

// without parameter types
(parent, view, position, id) -> {
      Log.d(TAG, "Position: " + position);
      Log.d(TAG, "Id: " + title);
}

// with parameter types
(AdapterView<?> parent, View view, int position, long id) -> {
      Log.d(TAG, "Position: " + position);
      Log.d(TAG, "Id: " + title);
}

2. If the body of the method has only one line, then the curly brackets can be ommited:

// with curly brackets
(parent, view, position, id) -> { processItemClick(position); }

// without curly brackets
(parent, view, position, id) -> processItemClick(position)

3. If the expression has only one parameter, then the parenthesis around parameter can be omitted:

// with parenthesis
(view) -> Log.d(TAG, "Clicked.")

// without parenthesis
view -> Log.d(TAG, "Clicked.")

4. The return keyword is optional if the body has a single expression to return the value;

// example interface
public interface Calculator {
        int calculate(int a, int b);
}

// with return
Calculator calculator = (a, b) -> {return a + b;};

// without return
Calculator calculator = (a, b) -> a + b;

5. Lambda expression that does not take any arguments:

() -> Log.d(TAG, "From Runnable")

Functional interfaces

Lambdas are treated as instances of a special interface type called functional interface. A functional interface in fact is nothing but an interface with a single abstract method. We already saw several examples of such interfaces; Runnable, Callable, OnClickListener, OnItemClickListener are all examples of functional interfaces.

A new annotation called @FunctionalInterface was introduced to mark an interface as such.

@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);
}

The annotation is optional, but it is highly recommended; it clearly communicates the intent of the interface and it also allows the compiler to perform additional checks.

@FunctionalInterface
public interface Calculator {

    int calculate(int a, int b);

    // *** this will break compilation ***
    void print(String result);
}

@FunctionalInterface
public interface Calculator {

    int calculate(int a, int b);

    // this will NOT break the compilation,
    // because print is a default method
    default void print(String result){
        Log.d(TAG, result);
    }
}

Lambda expressions might led you to the wrong idea that they are something that is going to replace anonymous classes, and now seeing that a lambda expression can be converted from an interface with only one abstract method, you might wonder how useful is this. If this is so, then I would like to recall that a lambda expression is a mechanism that allows you to pack functionality and pass it around, in a succinct way. So no, lambdas, are not intended to replace anonymous classes.

Open-sourcing Explore

Explore is a simple app I wrote in my free time some time ago. The idea behind it is to help you find a place you would like to visit (eg.: country or city), by searching for information about that place in different sources and then making a summary of this information and presenting it to you.

More specifically, it searches for information in the following sources:

  • Youtube for videos
  • Flickr for photos
  • Wikipedia for description, and
  • Google Places for attractions (eg. museums, restaurants, etc)

For anyone interested in the source code, the source code is available on Github.

 

Example usage of AppCompatActivity in Android

The latest release of android support library, 22.1, deprecates the ActionBarActivity in favor of AppCompatActivity, which promises to bring a single consistent ActionBar for all devices starting with API Level 7 and above.
Also, the new update adds the ability to tint widgets automatically when using AppCompat, and adds support for consistent material design dialogs. Use support.v7.app.AlertDialog (instead of android.app.AlertDialog), to get nice looking material dialogs across multiple versions of devices.

android-support-22.1

Example:
1. In order to benefit from all these things, the first thing you should do is to update the support library to 22.1.0.

dependencies {
   // … 
   compile 'com.android.support:appcompat-v7:22.1.0'
}
2. Then let your activity extend AppCompatActivity.
public class MainActivity extends AppCompatActivity {
  // ...
}
3. And finally, change the application theme to AppCompat or any descendants of it.
<application android:theme="@style/Theme.AppCompat">
Configuring the material color palette

Following the steps above will setup the application with the default theme, but the actionbar can be styled further by specifying the material color palette:

<style name="AppTheme" parent="Theme.AppCompat">
   <item name="colorPrimary">@color/primary</item>
   <item name="colorPrimaryDark">@color/primaryDark</item>
   <item name="colorAccent">@color/accent</item>
</style>

and then update your application theme to use AppTheme.

<application android:theme="@style/AppTheme">

Introduction to Android Espresso

Espresso is a testing framework that exposes a simple API to perform UI testing of android apps. With the latest 2.0 release, Espresso is now part of the Android Support Repository which makes it more easier to add automated testing support for your project.

But before jumping into Espresso API, lets consider what puts it apart from the other testing frameworks.

  • One of the first things you’ll notice about Espresso, is that its code looks a lot like English, which makes it predictable and easy to learn.
  • The API is relatively small, and yet open for customization.
  • Espresso tests run optimally fast (no waits, sleeps)
  • Gradle + Android Studio support

Adding Espresso to your project

1. First of all make sure you have Android Support Repository installed

android sdk manager

2. Add the following dependencies to your application build.gradle file

dependencies {
   androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
   androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0'
}

3. Finally, specify the test instrumentation runner in default config

android {

    defaultConfig {
        // ....
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
}

And that is basically what it takes “to invite your project to a cup of Espresso”!

The main components of Espresso

Espresso is built up from 3 major components.

These components are:

  • ViewMatchers – allows you to locate a view in the current view hierarchy
  • ViewActions – allows you to interact with views
  • ViewAssertions – allows you to assert the state of a view.

For simplicity, you may use these shortcuts to refer to them:

  • ViewMatchers – “find something
  • ViewActions – “do something
  • ViewAssertions – “check something

And, for example when you will need to check something (like, is some text displayed on the screen?), you’ll know you’ll need a ViewAssertion for that.

Below is an example of a test in Espresso, and where the main components find their place.

Android Espresso main components

A simple test using onView()

Suppose we have an app where the user is asked to enter his name.
After he enters the name, he taps on the “Next” button and is redirected to another activity where a greeting message is displayed.

espresso simple test

If we would write a test for this scenario, then it might look something like this:

// locate the view with id "user_name" and type the text "John"
onView(withId(R.id.user_name)).perform(typeText("John"));

// locate the view with id "next" and click on it
onView(withId(R.id.next)).perform(click());

// locate the view with id "greeting_message" and check its text is equal with "Hello John!"
onView(withId(R.id.greeting_message)).check(matches(withText("Hello John!")));

Notice that we don’t specify explicitly what kind of view we are interacting with (eg.: EditText, Button), we simply say that we are looking for a view with a specific id.
Also, when clicking on the “Next” button and later checking the text, we don’t have to write some special code to tell Espresso that we have navigated to another activity.

Now, if we want to actually run this test, then it should be put in a class. In Gradle, the location where tests are stored is: yourApp/src/androidTest/java.

This is an example of a test class, and its main characteristics:

Espresso example test class

A simple test using onData()

Whenever you have a ListView, GridView, Spinner, and other Adapter based views, you’ll have to use onData() in order to interact with an item from that list.
onData() is targeting directly the data provided by your adapter. What does this mean, we will see in a moment.

In a hypothetical application we are asked to select a country from a Spinner, once selected, the country is displayed next to the Spinner.

espresso on data

A test to check that the displayed country is equal with what was selected, might look like this:

// locate the view with id "country_spinner" and click on it
onView(withId(R.id.country_spinner)).perform(click());

// match an item that is a String and is equal with whatever value the COUNTRY constant is initialized, then click on it.
onData(allOf(is(instanceOf(String.class)), is(COUNTRY))).perform(click());

// locate the view with id "selected_country" and check its text is equal with COUNTRY
onView(withId(R.id.selected_country)).check(matches(withText("selected: " + COUNTRY)));

The Spinner you saw in the example above is backed by a simple array of strings, and because of this, we specify that we are looking for an item of type String. If, instead of a String, it were some custom object, then we would specify that instead.
To illustrate, consider the following example where a list of books is displayed:

books-adapter

Since the items in the adapter are of type Book, so will look the query:

onData(allOf(is(instanceOf(Book.class)), withBookTitle(BOOK_TITLE))).perform(click());

DataInteractions

Espresso has a few useful methods that can be used to interact with data.

atPosition() – Might be useful when the element to interact with is not relevant, or when the items always appear in a specific order, so you know every item on what position sits.

onData(...).atPosition(2).perform(click());

inRoot() – Use inRoot() to target non-default windows. One scenario where this can be used is when testing autocomplete. The list that appears in the autocomplete view belongs to a window that is drawn on top of application window.
In this case you have to specify that the data you are looking for, is not in the main window.

onView(withText("AutoCompleteText"))
        .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView()))))
        .check(matches(isDisplayed()));

onChildView() – This DataInteraction allows to further refine the query by letting you interact with a specific view from a list row.
Let say that you have a list of items and every item has a delete button. You want to click on Delete button of a specific item:

onData(withBookTitle("My Book"))
      .onChildView(withId(R.id.book_delete)).perform(click());

inAdapterView() – This allows to select a particular adapter view to operate on, by default Espresso operates on any adapter view.
You may find this useful when dealing with ViewPagers and Fragments, and you want to interact with the AdapterView that is currently displayed, or when you have more than one adapter view in your activity.

onData(withBookTitle("My Book"))
      .inAdapterView(allOf(isAssignableFrom(AdapterView.class), isDisplayed()))
      .perform(click());

Espresso and RecyclerView

RecyclerView is an UI component designed to render a collection of data just like ListView and GridView, actually, it is intended to be a replacement of these two. What interests us from a testing point of view, is that a RecyclerView is no longer an AdapterView. This means that you can not use onData() to interact with list items.

Fortunately, there is a class called RecyclerViewActions that exposes a small API to operate on a RecyclerView. RecyclerViewActions is part of a separate lib called espresso-contrib, that also should be added to build.gradle:

dependencies {
    // ...

    androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.0');
}

Since your project already includes a dependency to recyclerview, and might as well include support libs, some dependencies conflicts might appear. In this case exclude
them from espresso-contrib like this:

dependencies {
    // ...

    androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.0') {
        exclude group: 'com.android.support', module: 'appcompat'
        exclude group: 'com.android.support', module: 'support-v4'
        exclude module: 'recyclerview-v7'
    }
}

Here is how to click on an item from the list by position:

onView(withId(R.id.recyclerView))
      .perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));

Or how to perform a click on a View from an item:

onView(withId(R.id.recyclerView))
      .perform(RecyclerViewActions.actionOnItem(
                hasDescendant(withText(BOOK_TITLE)), click()));

More Espresso Examples on Github:
https://github.com/vgrec/EspressoExamples

Introducing SectionedActionBarList

This Android library allows you to replace the drop down navigation list with a custom list where the items in the list are grouped by sections. It was inspired from the Google I/O 2014 app how sessions are grouped in the ActionBar list.

While the new Material Design movement discourages the use of ActionBar lists, in some situations it can be the best available option.

At the moment the library is not available on a repository, you have to include it as a source library in your project, but that will be fixed soon.

Github project page: https://github.com/vgrec/SectionedActionBarList

Example
List
<Section> sections = new ArrayList
<Section>();
 
Section themes = new Section("Themes");
themes.add("Design");
themes.add("Develop");
themes.add("Distribute");
sections.add(themes);
 
Section topics = new Section("Topics");
topics.add("Android");
topics.add("Chrome / Web");
topics.add("Cloud Services");
topics.add("Media");
topics.add("Location");
topics.add("Performance");
sections.add(topics);
 
Section types = new Section("Types");
types.add("Sessions");
types.add("App Reviews");
types.add("Box Talks");
sections.add(topics);
 
SectionedActionBarList actionBarList = new SectionedActionBarList(this).from(sections);
actionBarList.setItemSelectedListener(new ItemSelectedListener() {
  @Override
  public void onItemSelected(AdapterView<?> parent, View view, int position, long id, String sectionName, String itemName) {
      Toast.makeText(MainActivity.this, "Section: " + sectionName + ", Item: " + itemName, Toast.LENGTH_LONG).show();
  }
});

The SectionedActionBarList accepts a list of sections List<Section> sections, and every Section has a name and some associated items.

sectioned actionbar list

Configuration

Small customizations can be done to fit with your application design:

ListConfiguration configuration = new ListConfiguration(this);
configuration.setActionBarItemColorResource(R.color.brown);
configuration.setIndicatorDrawableResource(R.drawable.spinner_indicator_dark);
configuration.setSectionTitleColorResource(R.color.teal);
configuration.setDropdownItemColorResources(R.color.light_blue, R.color.dark_grey);

SectionedActionBarSpinner actionBarSpinner = new SectionedActionBarSpinner(this, configuration).from(sections);
// ....

actionbar list

Thanks to Webucator, a provider of Android classes, for producing the below video illustrating the integration of the SectionedActionbarList.