Clojure Android Development

Clojure Dojo Liverpool - 2 September 2014

by Claudia Doppioslash

Overview

Tools

Lein droid

Neko

The wrapper for Android's Java API

A plugin for our trusty lein

"0.2.3"

"3.0.2"

Both developed by Alex Yakushev

Add to lein profiles.clj

Add to project.clj

Lein profiles

Add lein droid to your lein profiles:

{:user {:plugins [ [lein-droid "0.2.3"] ]

On unix-like systems the file is at 

~/.lein/profiles.clj

On Windows it should be at 

%USERPROFILE%\.lein\profiles.clj

Also the path to your Android sdk:

:android {:sdk-path "/path/to/androidsdk"}}}

Create new app

$ lein droid new AndroidClojureTutorial \
    com.doppioslash.androidtut \
    :activity Reader \
    :target-sdk 17 \
    :app-name AndroidClojureTutorial
com.doppioslash.androidtut

bundleID: a unique identifier for your app on the google play store

activities: units of functionality in android, roughly equivalent to views in ios

target sdk: the minimum version of the android os that the app will run on

app name: the name the app will show on the device home screen

Lein droid automatically added a whole bunch of things to the project.clj, including:

Project.clj

  • the newest version of Neko as a dependency
  • a release profile for Android
  • an android key for further options
  • the DEX options: since DEX for me crashes due to Out of memory I always uncomment the dex options.

Time to connect your device to the pc with the USB cable 

Whenever in doubt use DEBUG=1 before the lein droid command to see verbose output:

$ DEBUG=1 lein doall

Run REPL remotely on device

Use

$ lein droid doall


​to run the app on your device. The repl on the pc will execute the code on the actual device.

ADB troubleshooting

Setting up ADB for the first time might be troublesome. Some possible issues:

  • ADB can't find your device
  • ADB is not on the path

Connecting the REPL

If you have emacs with cider do M-x cider and answer 127.0.0.1 and 9999

Depending on what version of cider and cider nrepl you have there could be some trouble.

A lein repl :connect 9999 is the equivalent..

The code you're inputing is actually running on the Android device (magic! =))

So let's try to change the activity we see on screen.

Tip: minimal .emacs file that runs this project correctly (on Mac)

(require 'package)
(add-to-list 'package-archives
  '("melpa-stable" . "http://melpa-stable.milkbox.net/packages/") t)
(package-initialize)
(unless (package-installed-p 'clojure-mode)
  (package-refresh-contents)
  (package-install 'clojure-mode))
(unless (package-installed-p 'cider)
  (package-refresh-contents)
  (package-install 'cider))
(require 'cider)
(add-hook 'cider-mode-hook 'cider-turn-on-eldoc-mode)
(setq cider-repl-wrap-history t)
(add-hook 'cider-repl-mode-hook 'paredit-mode)
(setq nrepl-buffer-name-show-port t)
(setq cider-known-endpoints '(("android" "127.0.0.1" "9999"))) 

cider-nrepl

Add it to :plugins [[cider/cider-nrepl "0.7.0"]] in project.clj

=> (in-ns 'your-ns)

You can change the text in the text view, change the string and then C-x C-e on the ending parentesis of on-ui.

You should see it change it in real time on device.

A few Android programming concepts

Making layouts on Android looks like this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical" >
    <TextView android:id="@+id/text"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Hello, I am a TextView" />
    <Button android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello, I am a Button" />
</LinearLayout>

Yes, it's as painful as it looks.

Luckily enough Neko makes it looks like this:

(def main-layout [:linear-layout {:orientation :vertical
                                 :id-holder true
                                 :def `mylayout}
                  [:edit-text {:hint "Write here",:id ::edit}]
                  [:button {:text "Add text" :on-click (fn [_](add-text))}]
                  [:text-view {:text @lines,:id ::articles}]])

No extra xml files sprinkled in a hundred directories,

easy map syntax.

Layout Types

  • Linear Layout
  • Relative Layout
  • ListView
  • GridView

We'll use linear layout, which:

"is a view group that aligns all children in a single direction, vertically or horizontally. You can specify the layout direction with the android:orientation attribute."

With Neko that would look like:

[:linear-layout {:orientation :vertical}]

Our first Layout

(declare ^android.widget.LinearLayout mylayout)



(def main-layout [:linear-layout {:orientation :vertical,
                                  :id-holder true
                                  :def `mylayout}])

an :id-holder flag

:def attribute

 

a forward declaration

(also for AOT compilation)

To access the layout by name we need:

(declare ^android.widget.LinearLayout mylayout)
(declare add-text)
(def lines  (atom ""))
(def main-layout [:linear-layout {:orientation :vertical
                                 :id-holder true
                                 :def `mylayout}
                  [:edit-text {:hint "Write here",:id ::edit}]
                  [:button {:text "Add text" :on-click (fn [_](add-text))}]
                  [:text-view {:text @lines,:id ::articles}]])

We'll use the neko.ui namespace which wraps the native android java api ui.

Let's make something useful

A basic text editor

...(make-ui main-layout)...

C-x C-e as before and it will show the new UI.

Helper functions

(defn get-elmt [elmt]
  (str (.getText (elmt (.getTag mylayout)))))

(defn set-elmt [elmt s]
  (on-ui (config (elmt (.getTag mylayout)) :text s)))

(defn update-ui []
  (set-elmt ::lines @lines)
  (set-elmt ::edit ""))

(defn add-text []
  (swap! lines str (get-elmt ::edit) "\n")
  (update-ui))

​

Result

Hopefully the basic editor works and you can see how interactive developing Android with Clojure is.

References

Made with Slides.com