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
Clojure Android Development
By Claudia Doppioslash
Clojure Android Development
- 4,151