Published: February 28, 2014
One of the great things with web development is the rich set of tools you can use to improve your workflow.
An example of one of these tools is Grunt, a JavaScript task runner that lets you define tasks to perform on your web app, from compiling Sass and starting a live server to compressing images, minifying JavaScript and running JSHint before creating a production-ready build.
Yeoman is a tool which helps create web applications, generating boilerplate, bringing in libraries from Bower and npm, and creating a Grunt file with predefined tasks.
In this tutorial, you use Yeoman to create a new basic web application, then integrate the Android Studio build system (Gradle) with Grunt, to build your web application. You'll also set up Grunt tasks to launch a local live-reload server for testing your application in the browser, so you don't have to manually refresh the page each time you change an HTML, CSS, or JavaScript file.
Prerequisites
Before getting started, you'll need to install a few prerequisites:
- Install Yeoman: https://github.com/yeoman/yeoman/wiki/Getting-Started
- Install Android Studio: https://developer.android.com/sdk/installing/studio.html
Step 1. Create a new project in Android Studio with a WebView
You can find a full instructions on how to do this in the get started guide.
Step 2. Create a subdirectory for the web app content
After you've created your project, create a new top level directory. In Android Studio, right-click the project folder and select New > Directory.
Name the directory webapp
.
Step 3. Create a Yeoman project in your new directory
In a terminal cd
to the webapp
directory in the project.
cd <path-to-project>/webapp/
Then create a new web app with Yeoman:
yo webapp
Follow the on-screen prompts to select project options. You may need to run
sudo npm install
, depending on how npm is installed on your machine.
Before proceeding to the next step, test the app by running the following command:
grunt server
A new tab should open in your browser, connecting to a local server started by Grunt. If you change one of the HTML, CSS or JavaScript files in the project, the page automatically reloads and updates.
If you run grunt build
a new directory, dist
, is created and your web app
is compressed, optimized and made into a production-ready version inside this
folder.
Step 4. Configure the Gradle build
In your webapp
directory create a new file called build.gradle
.
In your new build.gradle
file, add the following:
import org.apache.tools.ant.taskdefs.condition.Os
task buildWebApp(type: Exec) {
executable = Os.isFamily(Os.FAMILY_WINDOWS) ? "grunt.cmd" : "grunt"
args = ["build"]
}
This creates a new task called buildWebApp
with a predefined type Exec
.
Then, set the executable
variable in Exec
to the relevant grunt command
depending on the current OS. args
are set to "build"
which equates to
grunt build
being run on the command line. Finally, the import at the top is
to make use of Os.isFamily(Os.FAMILY_WINDOWS)
.
Before we can use this new task, we need to make the project aware of the new
build.gradle
file.
Open settings.gradle
in the root directory and add the following line:
include ':webapp'
Step 5. Build your web app when you build the Android app
Get the web app to build and then copy the app into the assets
directory of
our Android app.
Copy the following into the Android apps build.gradle
file:
task copyWebApplication(type: Copy) {
from '../webapp/dist'
into 'src/main/assets/www'
}
task deleteWebApplication(type: Delete) {
delete 'src/main/assets/www'
}
copyWebApplication.dependsOn ':webapp:buildWebApp'
copyWebApplication.dependsOn deleteWebApplication
android.applicationVariants.all { variant ->
tasks.getByPath(":${project.name}:assemble${variant.buildType.name.capitalize()}").dependsOn copyWebApplication
}
Let's examine each part of this.
task copyWebApplication
task copyWebApplication(type: Copy) {
from '../webapp/dist'
into 'src/main/assets/www'
}
This Copy
task copies your application from the webapp/dist
directory. We want to copy the files
to src/main/assets/www
. This task also creates the necessary file structure if any of the required
directories don't exist.
task deleteWebApplication
task deleteWebApplication(type: Delete) {
delete 'src/main/assets/www'
}
This delete task deletes all the files in the assets/www
directory.
copyWebApplication.dependsOn
copyWebApplication.dependsOn ':webapp:buildWebApp'
copyWebApplication.dependsOn deleteWebApplication
The first line of this states that the copyWebApplication
has a dependency on
the buildWebApp
task from our web app's build.gradle
file.
The second line states there is a dependency on the deleteWebApplication
task.
In other words, before we actually copy any files to the assets
directory,
make sure we build the web app and also delete the current contents of the
assets
directory.
android.applicationVariants.all
android.applicationVariants.all { variant ->
tasks.getByPath(":${project.name}:assemble${variant.buildType.name.capitalize()}").dependsOn copyWebApplication
}
This task specifies the dependencies for all of your project's builds, for each version of your app.
Here it sets a dependency on the assemble
tasks to run copyWebApplication
.
The assemble
tasks assembles the output of the project, so the web app needs to be copied over to
the Android project first.
Step 6. Make sure everything works
In Android Studio, you should have no assets
directory in your Android
applications src
folder.
Set the WebView to use the index.html
page:
mWebView.loadUrl("file:///android_asset/www/index.html");
Click Run and let your application build. You should see an assets
directory with your web application in the www
subdirectory.
Step 7. Create a live server and live reload
Live reloading can be pretty useful for making quick changes to your web applications. To enable this, you can create two "product flavors" for your app: a live server version and a static version, where the web content is packaged into the Android application.
In your Android app's build.gradle
add the following lines at the end
of the android
element:
android {
...
defaultConfig {
...
}
productFlavors {
staticbuild {
packageName "com.google.chrome.myapplication"
}
liveserver {
packageName "com.google.chrome.myapplication.liveserver"
}
}
}
Gradle now offers you the ability to create a version of your app with a liveserver package name, and one with your normal package name. To check if it worked, click Sync Project with Gradle Files (in the top bar next to the Run button).
Then view the Build Variants which are in the bottom left corner of Android Studio and essentially shows you what versions of your app you can build.
For each productFlavor
there are Debug and Release versions, which the
Android plugin for Gradle gives you by default. This determines whether the
build should be a debug build or a release build suitable to deploy on the Play
store.
Now you have two versions, but they don't actually do anything different yet.
Step 8. Load from a live server
Now, configure your application to load a different URL depending on which product flavor you build.
In your Android application, the files common to all product flavors live in
src/main
. To add code or resources specific to one product flavor, create
another directory under src
with the same name as your productFlavor
. When
you build for that build variant, Gradle and the Android plugin merge these
additional files on top of the files in src/main
.
Define the URL as a string resource, and use that resource in your code instead of a hard-coded URL.
Create the folders
src/liveserver
andsrc/staticbuild
.In the
liveserver
folder, create a new folder namedres
with a subfolder namedvalues
. Inside this create a file namedconfig.xml
. Repeat this process for thestaticbuild
folder.Inside your config files, add the following lines to
src/liveserver/res/values/config.xml
:<?xml version="1.0" encoding="utf-8"?> <resources> <string name="init_url">https://<Your Local Machine IP Address>:9000</string> </resources>
Add the following block to
src/staticbuild/res/values/config.xml
:<?xml version="1.0" encoding="utf-8"?> <resources> <string name="init_url">file:///android_asset/www/index.html</string> </resources>
Set your WebView to use the
init_url
from these config files.mWebView.loadUrl(getString(R.string.init_url));
Create a new file called
AndroidManifest.xml
inliveserver/AndroidManifest.xml
and add the following lines:<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="https://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET" /> </manifest>
This adds the Internet permission for the
liveserver
builds.Inside
webapp/Gruntfile.js
look for:connect: { options: { port: 9000, livereload: 35729, // change this to '0.0.0.0' to access the server from outside hostname: **'localhost'** }, ... }
Replace
localhost
with0.0.0.0
so your local server is accessible from the local network:connect: { options: { port: 9000, livereload: 35729, // change this to '0.0.0.0' to access the server from outside hostname: '**0.0.0.0'** }, ... }
To test your changes:
Start the live server:
grunt server
In Android Studio, in the Build Variant selection, select LiveserverDebug. Then click Run.
You should be able to edit your HTML, CSS and JavaScript content and see it reflected immediately in the browser.
You now have two versions of your application: a development version with live reload from the Grunt server; and a static version, packaged locally in the Android app.