Introduction
Yes you guessed it right, I'm not a huge fan of IDEs. Don't get me wrong though, I am fully aware of how powerful modern IDEs are, and all the magic that they can do for you to assist you while you are coding your application. But this is also why I don't like them, especially when I'm picking up something new.
To the date of writing, I am not an experienced Android developer. When I started developing applications (there was a time when I believed that no matter how big a project could be, you could always find the time to code it in assembly language), a plain old text editor was my friend, together with some command line tools, like assemblers, linkers, compilers, debuggers etc.... With no support at all, you had to know exactly what you were doing, you had to know the syntax, the APIs and where they were located. You also needed a minimum knowledge of what a linker is for and when and why you need one.
Whenever I tried an IDE, e.g. Android Studio, I always felt like I didn't really need to know much about the frameworks I was using, as all the built-in tools would come to my rescue. As a consequence, I started feeling like I wasn't really mastering anything and put the project I was working on aside for future me to one day resume working on it. Rather than me using the IDE, it kind of was the other way around: the IDE was using me to magically generate code.
IDEs also tend to hide all the machinery involved in the build process from the developer as well. In most cases, everything goes well, but what would you do if you suddenly come across a problem and you have no clue at which stage of the build process it is happening?
Surely, if you work on a big company project, it would be crazy to renounce entirely to IDEs, as your life might be a bit harder in everyday maintenance of your code, but for smaller projects this argument is somewhat weak, and opting for a plain text editor might have its many advantages. For once, you are in total control of the code that is going into your final product. And then again, there is also the educational aspect, which can give you the right amount of experience to tackle unexpected issues that could pop up during any stage of the development life-cycle.
All this being said, in this post we shall see how to develop an Android application by only relying on a text editor of your choice and the standard CLI tool provided by the Android SDK. The focus is on the steps required to install the Android SDK command line tools and how to organise your source code, rather than on the details of the application itself.
As with a standard Android project created with Android Studio, we are going to rely on Gradle and the Android Gradle plugin for the build process. You may rightfully think that this somehow partly defeats the point of this post, but, hey, in the end Gradle is just a command line tool, and quite a standard way to build and deploy Java projects these days.
This post is targeted to Linux users, but there is a good chance that the steps that we will go through have an equivalent on other platforms, like Windows. I'm afraid this is something that you will have to find out on your own.
Code very similar to the one presented in this post can be found in the GitHub repository androtest.
Pre-requisites
Before embarking on an adventure, it is wise to check that we are taking all that we need along the way with us. As we are trying to keep things as simple as possible, we won't need much, but there are a few preliminary steps that we need to perform in order to set up our development environment.
The first few steps couldn't be simpler: pick your favourite text editor (my laptop can still handle an application like Atom) and terminal application, and we already have almost half of what we need! The rest of the tools is provided by the Java Development Kit, the Android SDK Tools and Gradle. More details in due time.
The JDK is usually available from your distro's repositories. On Ubuntu, it can be installed with, e.g.
sudo apt install openjdk-9-jdk
The Android SDK might not be available from the official repositories. In principle the could be installed along with Android Studio, but if you are not going to use Google's official IDE then it is a bit of waste of space. The cleaner alternative is to just download the Android SDK Tools from the Android Studio download page. For simplicity, I will split the installation process of the Android SDK into different steps.
Android SDK Tools
Download the zip archive and extract it somewhere, e.g. ~/.android/sdk
, then
update your .bashrc
file to define the ANDROID_HOME
environment variable and
include the SDK tools binaries in the PATH
variable by adding the following
lines
# Android SDK Tools
export ANDROID_HOME=$HOME/.android/sdk
export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin
Note that most of the tools installed in $ANDROID_HOME/tools
are deprecated
and one should use the dedicated ones provided in the $ANDROID_HOME/tools/bin
folder. These include the fundamental
sdkmanager
and the
avdmanager
tools for respectively creating and managing different SDK versions (and other
packages too) and virtual devices (the emulators).
Android SDK Platform Tools
Throughout the Android development life-cycle, you are likely to need to
interface with the Android platform for testing your progress. In concrete terms
this means that you might want to compile your project as you develop it for
testing on an actual Android device. In order to connect to the device and look
at the log you will need the Android Debug Bridge, which is provided with the
Android Platform Tools. To install them we can use the sdkmanager
CLI tool to
pull the latest released version with the following command
sdkmanager "platform-tools"
This will install the Platform Tools into the $ANDROID_HOME/platform-tools
folder. We can then add it to the PATH
variable for easy invocation by simply
adding the following lines to .bashrc
# Android Platform Tools
export PATH=$PATH:$ANDROID_HOME/platform-tools
Android SDK Platforms
In order to compile a project we need a certain API revision to be installed. This provides all the functionalities that our application can use and are provided in the form of Java packages and classes and other useful components. For example, if our project targets Marshmallow, then we need to install the Android SDK Platform API Level 23. You can find out more at this page. To find out about all the packages available to download we can use the command
sdkmanager --list --verbose
At the moment we are interested in Android platforms, i.e. those packages that
are prefixed with platforms;
, so we can filter the output of the above command
as follows
sdkmanager --list --verbose | grep -A 3 platforms\;
Among the matching results, you should see something similar to the following
platforms;android-23
Description: Android SDK Platform 23
Version: 3
We can then proceed to installing the Android SDK Platform API Level 23 with the command
sdkmanager "platforms;android-23"
When you use the
sdkmanager
tool, you might see the following warning message
Warning: File /home/user/.android/repositories.cfg could not be loaded.
In order to get rid of it you can simply create this file with no content.
Android SDK Build Tools
Now that we have the API to compile against, we need the tools to actually be
able to build a project: the build tools. They provide utilities like
apksigner
, Jack and Jill etc..., but for the moment we don't have to worry
about the details of this package, as they will be invoked behind the scenes by
Gradle.
The Android Studio User Guide recommends that you keep the build tools updated to the latest version. To find out all the versions available for download, run the following command
sdkmanager --list --verbose | grep -A 3 build-tools\;
and locate the latest version. At the moment of writing this is 26.0.1, so the command to use in this case is
sdkmanager "build-tools;26.0.1"
Android Emulator
The installation of the Android Emulator package is not mandatory, as you can
decide to test your application on an actual Android device. However, there are
many reasons why you might want to use an emulator: you probably don't own a
huge variety of Android devices, differing not only in physical size, but also
in the API version (Kit Kat, Lollipop, Marshmallow, just to name a few of the
most recent code-names). The package can be installed through the sdkmanger
with the command
sdkmanager "emulator"
Again, we can add the emulator folder to the PATH
variable for easy access.
However, the Android SDK Tools provides a deprecated set of tools, emulator
and emulator-check
, that would collide with the ones we have just installed.
To solve this problem we can rename the deprecated executables with
chmod -x $ANDROID_HOME/tools/emulator
chmod -x $ANDROID_HOME/tools/emulator-check
mv $ANDROID_HOME/tools/emulator $ANDROID_HOME/tools/emulator.dep
mv $ANDROID_HOME/tools/emulator-check $ANDROID_HOME/tools/emulator-check.dep
and then add the others to the PATH
variables by appending the following lines
to the ~/.bashrc
file
# Android Emulator
export PATH=$PATH:$ANDROID_HOME/emulator
Gradle
Let's now proceed to the installation of Gradle, a build automation system that is also the default in Android Studio. Google has developed a dedicated Android plugin to assist with the most common tasks. The ones that I personally tend to run more frequently are collected in the following table
Task | Description |
---|---|
compileDebugJavaWithJavac |
Compiles the debug version of the java sources. Useful to check for syntax errors while coding |
installDebug |
Compiles and installs the debug version on all the devices discovered by the ADB. |
lint |
Runs a lint on the sources, producing a report in build/reports . |
Gradle is quite popular so it should be available from your distro's official repositories. On Ubuntu 17.04 though, a quite old version of Gradle is available from them, so I would recommend that you add Cheng-Wei Chien's PPA to your software sources and install Gradle from there
sudo add-apt-repository ppa:cwchien/gradle
sudo apt update && sudo apt install gradle
Gradle is quite a powerful tool, but you might find that it has a rather steep learning curve to master all of its features, especially if you are not familiar with Groovy. In this post we shall only scratch the very surface and look at only the closures that we need for this project, as a discussion on Gradle surely deserves a dedicated post on its own.
Getting back on business, the installation of Gradle was the last step that we needed to perform in order to set up the development environment, and we can now move on to creating an Android project from scratch.
Creating the Gradle Project
The first thing to do is to create a Gradle project of Java type. This involves
setting up a directory structure in the project's parent folder and creating the
Gradle build script build.gradle
. With Gradle installed, these steps can be
automated with the
init
task
gradle init --type java-library
If you are contributing to a project that is the work of many hands, it will
probably be the case that everybody is using the same build tools. As everybody
can have a different version of Gradle on their local machine, the Gradle
project recommends that one uses a
wrapper to
build a project, rather than invoke the local installation of Gradle directly.
By sharing the wrapper along with your project, every other developer working on
the same project will be able to use the same version of Gradle as everybody
else, thus getting rid of problems caused by switching between different
versions. Even though this is a sample project, we will nonetheless create and
use a Gradle wrapper to build our project. The previous command should have
created a gradlew
shell script in the project's folder. If not, run the
following command
gradle wrapper
You should also have a folder src/
containing all the sub-folders where Gradle
expects the sources and the resources that make up your project. But, most
importantly, you should also have the build.gradle
and settings.gradle
files
containing some sample build settings. This structure is slightly different from
the one generated by Android Studio, and documented on the developer
portal, where you can
notice a nested Gradle project, with the topmost one used to import the actual
project as a module, and configure the global build settings. For the case at
hand, we could to without this nested structure and only define a single build
script, since our project is made up of only one module.
The following is the content of the build.gradle
file.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
}
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion '26.0.1'
}
You can find a detailed explanation of the meaning of each closure in the
Configure Your
Build page of
the Android Developer portal. Briefly, the buildscript
closure is used to
configure Gradle itself so that it knows where to find the Android-specific
Gradle tools that we want to use. We can then import the Android Gradle plugin
and use the extensions to the DSL that it provides to configure the
Android-specific build process. In this case we specify that the compilation SDK
version that we want to use is Level 23 (Marshmallow), and that we want to use
the version 26.0.1
of the build tools.
The gradle.properties
file is used to configure the project-wide Gradle
settings, such as the Gradle daemon's maximum heap size. In fact, this is all we
will use it for in our case. Open it with your text editor and put the following
content in it
org.gradle.jvmargs=-Xmx1536m
This is our Gradle project created and configured! All the magic of the building process is hidden from us by the Android Gradle plugin so that we don't have to worry about anything else and just focus on our application code.
Writing the Application
Two essential ingredients for an Android application are the Main Activity and the App Manifest. Let's start with the latter first.
The Application Manifest
The Android Application Manifest is an XML manifest file that is used to provide
the Android system with essential information of an application, like the name
of the Java package, the activities provided, the permissions required, the
minimum SDK version supported etc... . Within a Gradle project, this file must
be located in the src/main
folder and named rigorously AndroidManifest.xml
.
In our case, this is what such manifest file would look like
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.p403n1x87.androtest">
<uses-sdk
android:minSdkVersion="21"
android:targetSdkVersion="23" />
<application
android:label="AndroTest"
android:theme="@android:style/Theme.Material">
<activity android:name="MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
The structure of the Android Application Manifest file is described on the developer portal in the form of a skeleton, as it is not based on any XML schema like DTD or XSD.
The root element is manifest
, which accepts the package
attribute. Here we
specify the name of the Java package of our application. We also specify our
target API level to 23. The minimum level supported is set to 21 for reasons
that relate to the output of the lint
Android Gradle task that we will look at
later on. It is recommended that you set this attribute to a reasonable value.
For example, if you do not set it, Android will automatically add the legacy
overflow three-dot button, even though there are no actions to show. Starting
from level 11, Android does not add this button by default.
The manifest
element must contain a unique application
element that
describes our application in may aspects, from the theme to use, to the activity
content. In this case we set the application name to AndroTest
and the theme
to Material
and we expose only one activity, i.e. the MainActivity
, which is
the entry point of our application, and the one that would be fired up when we
launch it.
The Main Activity
This takes us now to the main activity, i.e. the Java class that contains the
code to be executed when our application is launched. Within a Gradle project,
the main Java code should reside in the src/main/java
folder. Since the
package name is com.p403n1x87.androtest
, the MainActivity.java
source file
should be created within the src/main/java/com/p403n1x87/androtest
folder.
Here is the code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | package com.p403n1x87.androtest;
import android.app.Activity;
import android.os.Bundle;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.view.ViewGroup.LayoutParams;
import android.widget.TextView;
import android.widget.LinearLayout;
import java.util.List;
import static java.lang.Math.sqrt;
public class MainActivity extends Activity
{
private SensorManager mSensorManager;
private Sensor mSensor;
private TextView text = null;
private LinearLayout layoutMain = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// UI
setContentView(R.layout.main_layout);
if (text == null) text = (TextView) findViewById(R.id.text);
if (layoutMain == null) layoutMain = (LinearLayout) findViewById(R.id.layout_main);
// Sensors
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
if (mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY) != null){
List<Sensor> gravSensors = mSensorManager.getSensorList(Sensor.TYPE_GRAVITY);
int nSensors = gravSensors.size();
text.setText("Detected gravity sensors: " + Integer.toString(nSensors));
for (int i = 0; i < nSensors; i++) {
final TextView tvSensor = new TextView(this);
final int j = i + 1;
tvSensor.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
mSensor = gravSensors.get(i);
mSensorManager.registerListener(new SensorEventListener() {
@Override
public void onSensorChanged(final SensorEvent event) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
float g = (float) sqrt(x*x + y*y + z*z);
tvSensor.setText(String.format("Sensor %d: %f m/s^2", j, g));
}
@Override
public void onAccuracyChanged(Sensor sensor, int a) {}
}, mSensor, SensorManager.SENSOR_DELAY_NORMAL);
layoutMain.addView(tvSensor);
}
}
else {
text.setText("We DO NOT have gravity! :(");
}
}
}
|
The code is fairly simple and quite self-explanatory. We override the onCreate
method of the Activity
class, from which MainActivity
inherits, to set up
the UI. We use a layout resource as a basis, which we then dynamically extend
with extra TextView
widgets to hold the value of each gravity sensor that gets
discovered at run-time.
The Layout Resource
In this project we have a mixture of static layout resources and dynamic
creation of TextView
elements. This offers us the chance to see how to make
resources available to the Java code, i.e. by placing them in the place where
Gradle, and the Android Gradle plugin, would expect them. Files placed in the
src/main/res
folder are treated as resources and can be referenced in the way
described at the Android Developer portal. In the previous code block, on line
36 we have
setContentView(R.layout.main_layout);
The expression R.layout.main_layout
refers to the resource main_layout.xml
located in the layout
sub-folder of src/main/res
. The various grouping of
resources is detailed at the page Providing
Resources.
Later on, in dealing with the output of the lint
task, we will have the chance
to look at the strings.xml
resources in the values
sub-folder. For the time
being, let's have a look at what the main_layout.xml
looks like in this case:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="32px"
android:gravity="center"
android:orientation="vertical" >
<TextView android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="12px"
android:text="Gravity sensors" />
<!--
A new TextView element will be added programmatically for each on-board
gravity sensor detected.
-->
</LinearLayout>
This is a rather simple layout. We create a vertical LinearLayout
container to
display a vertical stack of TextView
elements. The first one, statically
included in the XML resource file, will hold the counter of the discovered
gravity sensors. More TextView
elements are added at run-time, one for each of
the sensors, to display their updated value.
Running the Application
Now that we have set up the Gradle project and coded our application, it is time
to build it and install it on an Android device to run it. In this section we
shall see how to invoke the Gradle build
task to generate the APK (the Android
Package Kit) package, and how to install it on our devices, being them either
virtual or physical, in two different ways. Finally, we will give a final touch
to our sample application by fixing a few of the issue reported by the lint
task.
Building with Gradle
The process of building an Android application involves many steps and tools. If we rely on a Gradle project, as we have done in this case, and as it would be if you were using Android Studio, all of the details of this process are hidden to us. In a post where we are trying to make use of only simple tools, you may think that Gradle would defeat the point. Whilst I'd agree with you, I also believe that we will have to draw a line at some point, and Gradle feels like the right place. If, at any point, you feel the need to manually build your project, you can refer to these two references for more details:
This being said, let's see how to build our sample project with Gradle. We can list all the available tasks with
./gradlew tasks --all
Among all the tasks listed by the previous command, we should see the following ones
Build tasks
-----------
assemble - Assembles all variants of all applications and secondary packages.
assembleAndroidTest - Assembles all the Test applications.
assembleDebug - Assembles all Debug builds.
assembleRelease - Assembles all Release builds.
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.
clean - Deletes the build directory.
cleanBuildCache - Deletes the build cache directory.
compileDebugAndroidTestSources
compileDebugSources
compileDebugUnitTestSources
compileReleaseSources
compileReleaseUnitTestSources
mockableAndroidJar - Creates a version of android.jar that's suitable for unit tests.
We could decide to assemble
our project, or build
it in case we also wrote
tests and want them built too. As it doesn't make much of a difference in our
case, we can run the build
task with
./gradlew build
The
build
task actually does a bit more than assemble and execute tests. It also runs thelint
task. More on this later.
If the build task was successful, you should now have the androtest-debug.apk
into the build/outputs/apk
folder, ready to be deployed on an Android device.
Creating a Virtual Device
In case you want to install the application on a physical device, you can skip
this section and go straight to the next one. Under some circumstances, you
might want to test your application on diverse hardware settings, and the best
way is probably to make use of a virtual device. If you have installed the
emulator
package as previously described, you can create a virtual machine
with the following commands. But first we need to install a system image, for
example the Intel x86 Atom_64 System Image. Since our application doesn't
make use of the Google APIs we can opt for the default image:
sdkmanager "system-images;android-23;default;x86_64"
The download and installation might take some time, so just wait for
sdkmanager
to complete. You can then proceed to creating a virtual device with
avdmanager create avd -n test -k "system-images;android-23;default;x86_64" -d 8
This will create a virtual device named test
with the system image that we
have just downloaded. The -d
switch specifies which device we want to emulate.
In this case the value 8 represents a "Nexus 5" device. A complete list of the
devices that can be emulated may be found with
avdmanager list device
Finally, to run the newly created virtual device we use the emulator
tool,
just like this:
emulator @test -skin 768x1280
where @test
specified the name of the virtual device we want to run, and the
-skin
switch defines the resolution we want to run the emulator at. Note that
a bug in the emulator
tools prevents you from running this command from any
working directory. Instead, you need to navigate to $ANDROID_HOME/emulator
in
order to start it without errors.
For more details on how to manage virtual devices, have a look at the
avdmanager
documentation page.
Installing the APK
There are at least to ways of installing the APK to an Android device. One is
via a Gradle task, and the other is more manual and involves the Android Debug
Bridge tool adb
. Let's have a look at both.
From the previous run of the ./gradlew tasks --all
, you should have seen the
following tasks
Install tasks
-------------
installDebug - Installs the Debug build.
installDebugAndroidTest - Installs the android (on device) tests for the Debug build.
uninstallAll - Uninstall all applications.
uninstallDebug - Uninstalls the Debug build.
uninstallDebugAndroidTest - Uninstalls the android (on device) tests for the Debug build.
uninstallRelease - Uninstalls the Release build.
The installDebug
task will look for all the Android devices in debugging mode
connected to your machine and attempt to install the APK on all of them. For
instance, if you have a physical device in debug mode connected to your
PC/laptop while also running a virtual device, this Gradle task will install the
APK on both of them.
If you want to install the APK on only one of the currently connected devices,
you can do so with the adb
tool. First of all, determine the identifier of
each of the connected devices with
$ adb devices
List of devices attached
* daemon not running. starting it now at tcp:5037 *
* daemon started successfully *
05426e2409d30434 device
emulator-5554 device
The device with ID 05426e2409d30434
is a physical Nexus 5 in debug mode
connected via USB to my laptop, while emulator-5554
, as the name indicates, is
a running instance of the test
virtual device we created before. We can
install the APK on e.g. the emulator with
adb -s "emulator-5554" install build/outputs/apk/androtest-debug.apk
which will now be ready to be executed on the virtual device.
The lint tasks
If you have ever used Android Studio you might wonder if there is the chance to
get all the useful code analysis and hints that the IDE brings up while you code
your application. While this feature is probably not supported by the text
editor of your choice, we can remedy by using the lint tool provided by the
Android SDK. A convenient way to invoke it is through a dedicated Gradle task.
From the output of ./gradlew tasks --all
you should also see the following
tasks
Verification tasks
------------------
check - Runs all checks.
connectedAndroidTest - Installs and runs instrumentation tests for all flavors on connected devices.
connectedCheck - Runs all device checks on currently connected devices.
connectedDebugAndroidTest - Installs and runs the tests for debug on connected devices.
deviceAndroidTest - Installs and runs instrumentation tests using all Device Providers.
deviceCheck - Runs all device checks using Device Providers and Test Servers.
lint - Runs lint on all variants.
lintDebug - Runs lint on the Debug build.
lintRelease - Runs lint on the Release build.
test - Run unit tests for all variants.
testDebugUnitTest - Run unit tests for the debug build.
testReleaseUnitTest - Run unit tests for the release build.
Notice how we can invoke the lint tool in multiple ways. As we saw before, the
build
task, as opposed to assemble
, will also run the lint tool. But we can
also choose to run it as part of a full check with the check
task, or just on
its own, on every variant of our project or simply on the debug build. As an
example, let's run the lint
task with
./gradlew lint
The output should contain something that looks like the following
...
> Task :lint
Ran lint on variant debug: 13 issues found
Ran lint on variant release: 13 issues found
Wrote HTML report to file:///home/gabriele/Projects/androtest/build/reports/lint-results.html
Wrote XML report to file:///home/gabriele/Projects/androtest/build/reports/lint-results.xml
BUILD SUCCESSFUL in 1s
25 actionable tasks: 7 executed, 18 up-to-date
This tells us that the lint tool has found 13 issues on both the debug and the
release build of our project, and that a report is available as both an HTML and
an XML file in build/reports
.
Let's open the HTML report and have a look at the reported issues. As
predictable, the largest number of issues is about internationalisation, as we
have used many hard-coded strings in our source code. So let's try to fix some
of these, starting from the HardcodedText
one. By clicking on it, we get sent
to a more detailed part of the page that suggests us to use a @string
resource
instead of the hard-coded string Gravity sensors
. Since our code will change
the string at run-time in any case, the simplest solution is to replace it with
an empty string. If we now run the lint task again, we should see only 12
issues. Good! :).
Let's now tackle a couple of TextView Internationalization issues. The first
one is about the string "Detected gravity sensors: " +
Integer.toString(nSensors)
that we have used in the MainActivity.java
source
file. The problem here is two-fold. We should use a resource string and favour
placeholders rather than concatenation. So let's create a string
resource
file. Recall from before that this should go in an XML file, say strings.xml
,
within the src/main/res/values
folder.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sensors_no">Detected gravity sensors: %d</string>
</resources>
We can now replace line 44 in MainActivity.java
with
text.setText(getString(R.string.sensors_no, nSensors));
Run the lint task again, and the count should now be down to only 9 issues. This should give you the idea of how to continue fixing the remaining findings.
Conclusions
In this post we saw that, if we want to develop Android applications by simply relying on a text editor and some command line tools, the initial set up of the development environment requires a few preliminary steps, some of which are performed when you install Android Studio. However, some of the other steps need to be performed regardless, with the only difference that, in Android Studio, you would have a GUI to assist you. The bonus of going IDE-free, however, is in the fact that we now know what operations are being performed by the IDE when, e.g., we install an Android SDK that targets a certain API level.
Creating a Gradle project is quite easy, and in this we are assisted by Gradle
itself. The Android Gradle plugin takes care of invoking the right tools to
perform the most common tasks, like building our project, or even installing it
on an Android device. One handy feature that we might miss out with a plain text
editor is the live linting of our code. However, at any stage of the
development, we can run the lint
Gradle task to generate a report with all the
issues that have been discovered within the whole project.
This post should have also showed you that, whilst Android Studio is a very useful tool, there is practically nothing that we would miss out by using the command line tools of the SDK instead. Development might at first be less smooth, and the learning curve steeper, but surely has its positives too.