Android: Gradle First Look


Introduction

Gradle favors convention over configuration. It means, Gradle provides default values for settings and properties. This makes Gradle very easy to get started with. However, if you would like to change/override Gradle default settings and properties, you can do that easily.

Gradle uses Groovy DSL (Domain Specific Language) as it’s configuration lanugage. Groovy is a dynamic language for the Java Virtual Machine (JVM).

Gradle supports three different kinds of repositories.

  1. Maven
  2. Ivy
  3. Static files (mostly *.jar or .aar)

Dependencies are fetched from repositories during the execution phase. Gradle keeps a local cache, so a particular dependency will be downloaded for once only.

Gradle also allows you to build other than Java projects. If you want to manage your JS/C++ projects you can do that using Gradle.

We only need to say what we need, not how to achieve it and Gradle will make it happen. For example, we can add a single line to our build file and Gradle will download the dependency from a remote repository and also sub dependencies and it will make sure all the classes are available to our project.

Installation

Download Gradle from, https://gradle.org/gradle-download/
Make sure to add the Gradle/bin path to your Environment variable.

In MacOS, if you have homebrew installed, then you can run the following command,

~$ brew update
~$ brew install gradle

First task

Let’s create our first task in gradle.

task firstTask {
	println "Hello Gradle"
}

Now we will run our first task.

~$ gradle firstTask
Starting a Gradle Daemon (subsequent builds will be faster)
Hello Gradle
:firstTask UP-TO-DATE

BUILD SUCCESSFUL

Total time: 3.838 secs

All available tasks

~$ gradle tasks
Hello Gradle
:tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Build Setup tasks
-----------------
init - Initializes a new Gradle build. [incubating]
wrapper - Generates Gradle wrapper files. [incubating]

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'Temp'.
components - Displays the components produced by root project 'Temp'. [incubating]
dependencies - Displays all dependencies declared in root project 'Temp'.
dependencyInsight - Displays the insight into a specific dependency in root project 'Temp'.
help - Displays a help message.
model - Displays the configuration model of root project 'Temp'. [incubating]
projects - Displays the sub-projects of root project 'Temp'.
properties - Displays the properties of root project 'Temp'.
tasks - Displays the tasks runnable from root project 'Temp'.

Other tasks
-----------
firstTask

To see all tasks and more detail, run gradle tasks --all

To see more detail about a task, run gradle help --task <task>

BUILD SUCCESSFUL

Total time: 1.008 secs

Build a simple Java project

  1. Create this project structure, /SimpleJava/src/main/java/com/genericslab/simplejava
  2. Create a Java file inside simplejava folder
  3. Create a build.gradle file inside SimpleJava fodler, parallel with src folder.
package com.genericslab.simplejava;

public class Main {
	public static void main (String... args) {
		System.out.println("Hello Java!!!");
	}
}
apply plugin: 'java' 

After this, run the ~ $ gradle tasks command again and see the avialable tasks.

~$ gradle tasks
:tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.

Build Setup tasks
-----------------
init - Initializes a new Gradle build. [incubating]
wrapper - Generates Gradle wrapper files. [incubating]

Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the main source code.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'SimpleJava'.
components - Displays the components produced by root project 'SimpleJava'. [incubating]
dependencies - Displays all dependencies declared in root project 'SimpleJava'.
dependencyInsight - Displays the insight into a specific dependency in root project 'SimpleJava'.
help - Displays a help message.
model - Displays the configuration model of root project 'SimpleJava'. [incubating]
projects - Displays the sub-projects of root project 'SimpleJava'.
properties - Displays the properties of root project 'SimpleJava'.
tasks - Displays the tasks runnable from root project 'SimpleJava'.

Verification tasks
------------------
check - Runs all checks.
test - Runs the unit tests.

Rules
-----
Pattern: clean<TaskName>: Cleans the output files of a task.
Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration.
Pattern: upload<ConfigurationName>: Assembles and uploads the artifacts belonging to a configuration.

To see all tasks and more detail, run gradle tasks --all

To see more detail about a task, run gradle help --task <task>

BUILD SUCCESSFUL

Total time: 1.657 secs

As you can see, we have got a number of additional Build Tasks because we had added java plugins.
Let’s run ~ $ gradle build

~$ gradle build
:compileJava
:processResources UP-TO-DATE
:classes
:jar
:assemble
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build

BUILD SUCCESSFUL

Total time: 1.448 secs

A build directory has been generated. You can find a Main.class file in the following directory,
~SimpleJava/build/classes/main/com/genericslab/simplejava

Let’s run it.

~$ java -cp build/classes/main/ com.genericslab.simplejava.Main
Hello Java!!!

Run build with a specific version of Gradle (Gradle Wrapper)

You need to have Gradle already installed.

apply plugin: 'java'

task wrapper(type: Wrapper) {
	gradleVersion = '2.6'
}
~$ gradle wrapper

After running the wrapper task, you will see a folder named gradle and a script file named gradlew and a .bat file named gradlew.bat

From now on, you need to use gradlew instead of gradle to use the particular gradle version 2.6. Any further command started with gradlew will first install the Gradle version 2.6 (if not installed already).

If you have built an application that need to use a particular Gradle version, or you might want to run the application without any hassel in other machine then it is a preferable way to use a wrapper.

Tasks

Task is the unit that Gradle executes. Each task has a lifecycle and may contain properties. Task can also contain dependencies of other tasks.

Let’s create a task

// project level task 
project.task "TaskA"


// in theory, it is a local level task 
// however, practically it is same as before 
// because we are now in project context 
task "TaskB"

// We can add description of a task 
TaskB.description = "This is a description for TaskB"

// Action of a task 
TaskA {
	doLast {
		println "Action#1 for TaskA"
	}
}

// We can define the task and define action together
task "TaskC" {
	doLast {
		println "Action#1 for TaskC"
	}
}

// If we define multiple actions inside a task, 
// actions will be appended
TaskA {
	doLast {
		println "Action#2 for TaskA"
	}
}

// define, description and actions all in one 
task "TaskD" {
	description "This is a description for TaskD"
	doLast {
		println "Action#1 for TaskD"
	}
}

Run ~$ gradle tasks in command line.

~$ gradle tasks
:tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Build Setup tasks
-----------------
init - Initializes a new Gradle build. [incubating]
wrapper - Generates Gradle wrapper files. [incubating]

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'Temp'.
components - Displays the components produced by root project 'Temp'. [incubating]
dependencies - Displays all dependencies declared in root project 'Temp'.
dependencyInsight - Displays the insight into a specific dependency in root project 'Temp'.
help - Displays a help message.
model - Displays the configuration model of root project 'Temp'. [incubating]
projects - Displays the sub-projects of root project 'Temp'.
properties - Displays the properties of root project 'Temp'.
tasks - Displays the tasks runnable from root project 'Temp'.

Other tasks
-----------
TaskA
TaskB - This is a description for TaskB
TaskC
TaskD - This is a description for TaskD

To see all tasks and more detail, run gradle tasks --all

To see more detail about a task, run gradle help --task <task>

BUILD SUCCESSFUL

Total time: 0.852 secs

Task Dependencies

// define, description and actions all in one 
task "TaskD" {
	description "This is a description for TaskD"
	doLast {
		println "Action#1 for TaskD"
	}
}

// TaskA is dependent on TaskC
// means, TaskC will auto executed when we run TaskA 
TaskA.dependsOn TaskC 

// one task must execute after another task 
TaskB .mustRunAfter TaskA 

// one task should execute after another task 
// avoids circular dependencies 
TaskB.shouldRunAfter TaskA, TaskB 

/*
Remember, mustRunAfter and shouldRunAfer will not kick in 
automatically. If we run both tasks together, or, the tasks
have dependencies on one another, only then those two 
keywords will be meaningful. 

What if we would like to run a task after another task, 
even we don't call it separately, or don't have dependencies.
Similar to final block, we can use finalizedBy 
*/

// might be very usefule for db migration 
// after the migration you might want to run a task for cleanup 
TaskB.finalizedBy TaskC 

Run ~$ gradle TaskA

~$ gradle TaskA
:TaskC
Action#1 for TaskC
:TaskA
Action#1 for TaskA
Action#2 for TaskA

BUILD SUCCESSFUL

Total time: 0.908 secs

Define properties

 
def buildName = "Awesome-20160902a"
println "Build name: $buildName"

// global scope
project.ext.versionName = "Awesome=20160902b"
println "Version name: $project.ext.versionName"


<h3>Use built-in task: Copy</h3>


task copyFiles (type: Copy) {
	exclude 'file1.txt', 'file2.txt' // do not copy these files
	from 'src'  // define the source folder 
	from 'dest' // define the destiantion folder 
}

// this can be re-written to 

def spec = copySpec {
	exclude 'file1.txt', 'file2.txt' // do not copy these files
	from 'src'
}

task copyFilesAgain (type: Copy) {
	with spec 
	into 'dest' 
}

// another spec variable 
def spec = copySpec {
	exclude {
		// iterator: it 
		// exclude all pngs 
		it.file.name.endsWith("png")
	}
	from 'src'
}

Sourceset

Gradle Android projects follows this folder structure:

src/main/java
src/main/res
src/main/jniLibs
src/androidTest/java
src/test/java

if you do not like to change your current folder structure then you can tell Gradle where to look for specific files

android {
    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }

        androidTest.setRoot('tests')
    }
}

Gradle daemon

Everytime we run ~$ gradle build, Gradle launches JVM and starts from the beginning. Instead if Gradle could run as a service and could reuse already runned JVM then it would have been great!

Voila! Gradle can do that and it's called daemon.

Run ~$ gradle --daemon build and you might be prompted to give a permission and gradle daemon will be started.

If you would like to run --daemon all the time, instead of per command basis then write the following code,

or.gradle.daemon=true

in your project's gradle.properties file. That will ensure daemon will run on your project.

If you would like run daemon irrespective of projects, that means for all projects, then add the line in your ~/.gradle/gradle.properties file.

Android: Instrumentation Testing using Espresso


Introduction

Android provides a number of extra components beyond Java, for example, Activity, Service etc. Testing these extra components you will be required Instrumentation testing and it needs to be run on device/emulators. Espresso is an official instrumentation testing framework by Google.

Installation

Add this line in app level build.gradle in dependencies

androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'

add this line inside defaultConfig

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

For more information,

  1. Android dev
  2. Google github

Project Structure

An androidTest folder should automatically be generated when you created the Android application. For example,

In Android view,

Screenshot at Sep 15 11-44-37.png

Or, in Project view,

Screenshot at Sep 15 11-46-31.png

If not,

  1. Create a folder named androidTest inside src
  2. Create a folder named java inside androidTest

Now we will create our first test class.

  1. Go to the MainActivity.java class and click on MainActivity class name
  2. Select Navigate (from menu)
  3. Select Test (Shift + Cmd + T)
  4. Select, Create New Test…

Select JUnit4 and the dialog should look like this,

Screenshot at Sep 15 11-56-52.png

Click OK.

Check the correct directory is selected. We need to select androidTest directory,

Screenshot at Sep 15 11-57-34.png

Click OK.

Now Sync Gradle.

If you get the following error,

Error:Conflict with dependency 'com.android.support:support-annotations'. Resolved versions for app (24.2.1) and test app (23.0.1) differ. See http://g.co/androidstudio/app-test-app-conflict for details.

The add the following line in dependencies

androidTestCompile 'com.android.support:support-annotations:24.2.1'

Note that the above line version number need to match with the expected version number indicated with the error.

Now let’s go to the newly created MainActivityTest.java


package com.genericslab.espressodemo;

import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
public class MainActivityTest {

    // This will launch the MainActivity before testing each method/case.
    @Rule
    public ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<>(MainActivity.class);

}

Now we will write our first test case

@RunWith(AndroidJUnit4.class)
public class MainActivityTest {

    // This will launch the MainActivity before testing each method/case.
    @Rule
    public ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<>(MainActivity.class);

    @Test
    public void testCase1() {
        // Find a view with id = R.id.text
        // Check the text of that view matches with string = R.string.app_name
        onView(withId(R.id.text)).check(matches(withText(R.string.app_name)));
    }
}

Now Right click on the file MainActivityTest.java from project explorer and select “Create MainActivityTest…” -> OK.
Click on Run.

Now we will add our second test case

@Test
public void testCase2() {

    // Open options menu, whether as overflow or using a physical button
    // This will open the option menu as a pop up menu. Next we will click on an item.
    // If you need context then pass, InstrumentationRegistry.getTargetContext()
    openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getTargetContext());

    // From the options menu, we are going to click on an item having text = R.string.label_help
    onView(withText(R.string.label_help)).perform(click());

    // alternatively, you can leverage id property.
    onView(withId(R.id.menu_help)).perform(click());

    // verifying
    onView(withId(R.id.text)).check(matches(withText(R.string.label_updated)));
}

Android: SharedPreferences


There are several data storage options in Android. Such as,

  1. Shared Preferences
    • Stores primitive data types (int, String, float, boolean)
    • Stores as key-value pair
    • Not accessible to other applications
  2. Internal Storage
    • Stores data inside apps private storage
    • Not accessible to other applications
  3. External Storage
    • Stores data on shared external storage (SD Card)
    • Accessible to other applications/user
  4. SQLite Database
    • Stores structured data in apps private storage
    • Not accessible to other applications in general, but can be shared using ContentProviders.
  5. Network
    • Stores data in your own/company server
    • You need an active network connection for accessing/storing the data

We have two options to get the SharedPreferences object,

  1. getPreferences(int mode)
  2. getSharedPreferences(String name, int mode)

There is only one valid value for int mode and that is Context.MODE_PRIVATE

Previously, there were other valid values, such as Context.MODE_WORLD_READABLE and Context.MODE_WORLD_WRITEABLE, but these values got deprecated in API 17 and will throw a SecurityException from Android N.

If we use Context.MODE_PRIVATE then it means, no other apps will be allowed to access our shared preference values.

getPreferences

If we use getPreferences(int mode) then an XML file will be created by name of the Activity and it’s access level will be within the Activity. Separate XML files will be created for each Activity.

getSharedPreferences

If we use getSharedPreferences(String name, int mode) then an XML file will be created by name of String name and it’s access level will be within the Application (i.e any Activity can access the values). If you use a different name then a different XML by that name will be created.

Eaxmple#1

private void saveDataInActivitySharedPreferences() {
    SharedPreferences sharedPrefs = getPreferences(Context.MODE_PRIVATE);
    SharedPreferences.Editor editor = sharedPrefs.edit();

    editor.putString("key_string", "value_string");
    editor.putBoolean("key_boolean", true);
    editor.putInt("key_int", 5);

    editor.apply();
    // apply will not return any value and work asynchronously

    // or you could call, editor.commit();
    // commit returns boolean based on un/successful operation and works synchronously
}

private void loadDataFromActivitySharedPreferences() {
    SharedPreferences sharedPrefs = getPreferences(Context.MODE_PRIVATE);

    String valueString = sharedPrefs.getString("key_string", "Default value");
    boolean valueBoolean = sharedPrefs.getBoolean("key_boolean", false);
    int valueInt = sharedPrefs.getInt("key_int", 0);

    String logMsg = valueString + " " + valueBoolean + " " + valueInt;

    Log.d("DEBUG_TAG", logMsg);
}

You can see the XML file content by selecting Tools -> Android -> Android Device Monitor -> (Tab) File Explorer -> data -> data -> APP_PACKAGE_NAME -> shared_prefs -> ACTIVITY_NAME.xml

File content should like below,

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="key_string">value_string</string>
    <boolean name="key_boolean" value="true" />
    <int name="key_int" value="5" />
</map>

If you tap on CLEAR DATA from Settings then this fill will be deleted. DB and Cache files will be deleted as well.

We can not load the values from another Activity because the created file has Activity level access. If we want to access the values from another Activity then we need to call getSharedPreferences instead of getPreferences. Remember that, in case of getSharedPreferences too we have only one value value for int mode = Context.MODE_PRIVATE

Example#2

editor.remove("key_string");
// the key and the associated value will be deleted.
editor.apply();
// do not forget to call apply() method.

editor.clear();
// clear all the data, but the XML file will not be deleted.
editor.apply();
// do not forget to call apply() method.

Demo contacts for Android emulator/genymotion


When we create an emulator or genymotion device, it is kind of blank. What if we need a list of contacts to experiment with. In that case we have two options

  1. We can use our PERSONAL device. urgh!
  2. We can insert a bunch of demo contacts into emulator/ genymotion.

Cons

What if we accidentally delete/call/send text to our personal contacts? shoot!

Or, we might need to discard the emulator/genymotion and create a new one. In that case all our demo data will be gone!

Besides, our demo data will always look like a DEMO data. I mean, anybody can take a look at our contacts list and can identify the data are dummy.

Solution

I have created some dummy data (which would look like real data to most of sane human beings, hopefully) and exported the contacts into a .vcf file format. Anybody can transfer the file into your emulator/genymotion and import the file from contacts app.

And voila! you will get a bunch of contacts (52 contacts as of now). I have also post the file in github repository and of course you will get the latest data from there.

Github repo: github.com/tausiq/demo-contacts-for-Android

Contacts.vcf: https://github.com/tausiq/demo-contacts-for-Android/blob/master/contacts.vcf

 

 

screenshot_1488355668

screenshot_1488355704

Data source: http://www.fakenamegenerator.com and http://pngimg.com

Android Library: Gson (Usage and Example)


Serialization

Serialization is a process of writing the state of an object into a byte stream. Similarly, deserialization is the process of turning the byte stream into an object.

Installation

You can get the latest version of Gson library from here.
Right now the latest version is 2.7. Click on the version from the link and you will get the instruction to add the library for your favorite build system.

For Gradle, I would use,

compile group: 'com.google.code.gson', name: 'gson', version: '2.7'

this can be simplified to,

compile "com.google.code.gson:gson:2.7"

Example#1

We have a class named User

public class User {

    private String firstName;

    private String lastName;

    private int age;

    private List<String> emails;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public List<String> getEmails() {
        return emails;
    }

    public void setEmails(List<String> emails) {
        this.emails = emails;
    }
}

Now let’s create an object of class User and convert the object to String and after that we will create an User object from the String.


User user = new User();
user.setFirstName("Android");
user.setLastName("Studio");
user.setAge(3);
user.setEmails(Arrays.asList("android.studio@gmail.com", "android.studio@android.com"));

// converting the User object to string
Gson gson = new Gson();
String jsonStringUser = gson.toJson(user, User.class);

// Creating the User object from string
User returnedUser = gson.fromJson(jsonStringUser, User.class);

Example#2

Now, suppose, we have a generic class.

public class Employee<T> {

	private T person;

	public T getPerson() {
		return person;
	}

	public void setPerson(T person) {
		this.person = person;
	}
}

Now let’s create an object of class Employee and convert the object to String and after that we will create an Employee object from the String.

User user = new User();
user.setFirstName("Android");
user.setLastName("Studio");
user.setAge(3);
user.setEmails(Arrays.asList("android.studio@gmail.com", "android.studio@android.com"));

Employee<User> employee = new Employee<>();
employee.setPerson(user);

Gson gson = new Gson();

// This will not work
String jsonString = gson.toJson(employee, Employee.class);

We could have put any class object inside Employee. There is no way the JVM will know the object type until runtime. There is a concept of Java type erasure that will explain why the simple Employee.class will not work with Gson. To resolve the issue we need to get the type at runtime using TypeToken

User user = new User();
user.setFirstName("Android");
user.setLastName("Studio");
user.setAge(3);
user.setEmails(Arrays.asList("android.studio@gmail.com", "android.studio@android.com"));

Employee<User> employee = new Employee<>();
employee.setPerson(user);

Gson gson = new Gson();

Type type = new TypeToken<Employee<User>>() {
}.getType();

String jsonString = gson.toJson(employee, type);

// load the value
Employee<User> returnedEmployee = gson.fromJson(jsonString, type);
User returnedUser = returnedEmployee.getPerson();