-
Notifications
You must be signed in to change notification settings - Fork 36
Namespaces
This page contains a detailed description of neko namespaces describing most of the possible usages for each of them. To inspect all functions and their documentation see the Marginalia docs.
NOTE: This document assumes the reader to already have some knowledge of the Android platform. Neko doesn’t try to completely hide the Java side of Android, neither it tries to replace the key Android concepts. If you start learning Android through Clojure, it is a good idea to grab a regular Java/Android book to accompany you.
Provides utilities to modify application’s action bar. For detailed information see Action bar page.
This namespace stores facilities related to the Activity class.
Since defining a new activity requires to inherit the Activity class,
defactivity
macro simplifies this work. This macro first takes optional
arguments in key-value format, and then a list of different activity event
handlers. For example:
(defactivity your.package.name.MainActivity
:key :main
:features [:no-title]
(onCreate [this bundle]
(.superOnCreate this bundle)
...)
(onResume [this]
(.superOnResume this)
...))
Any methods that override android.app.Activity methods, or implement any interfaces you provide, can be specified here. Examples include:
- onCreateOptionsMenu
- onStart
- onStop
- onActivityResult
- etc.
You must remember to manually call super methods where they are necessary.
Also keep in mind that some methods have to return boolean values, e.g.
onCreateOptionsMenu
and onOptionsItemSelected
.
Another option you can use is :features
. Followed by a vector of keywords,
it will call (request-window-features!)
on it as the first thing in
:onCreate
.
You can also use special :key
option to assign a unique keyword to
activity. The activity can be later retrieved by that keyword via
(neko.debug/*a keyword)
. You can also simply call (*a)
from inside the
namespace where activity is defined.
For example:
(require '[neko.debug :refer [*a]])
(defactivity org.bar.foo.MainActivity
:key :main
...)
(*a :main) ;=> returns MainActivity instance
(*a) ;=> returns MainActivity when in the same namespace
Keep in mind that is a debug-time feature, and you should not leave code
that relies on (*a)
in production. This macro may return a paused/stopped
activity which can lead to undefined behavior, or even yield nil. So for the
release build use the activity instance provided as first argument to
methods (this
) and properly pass it to your functions that operate on the
Activity.
set-content-view!
takes an activity and sets its content view to
be one of the following:
- neko.ui tree
- View instance
- layout ID defined in XML
If UI tree is provided, make-ui
will be called upon it
automatically.
request-window-features!
requests the given features for the activity. The
features should be keywords such as :no-title
or :indeterminate-progress
corresponding FEATURE_NO_TITLE and FEATURE_INDETERMINATE_PROGRESS,
respectively. Returns a sequence of boolean values corresponding to each
feature, where a true value indicates the requested feature is supported and
now enabled. This function should be called before set-content-view!
. You
might as well just specify a vector of features in :features
option in
defactivity
.
simple-fragment
function allows you to create a trivial Fragment
object that contains specified view. Takes optional context and a
View object or neko.ui tree.
(simple-fragment [:linear-layout {:orientation :vertical}
[:text-view {:text "Text"}]
[:button {:text "Button"}]])
You can always access the application instance (root context of the
application) in neko.App/instance
field.
(get-service :alarm) ;; or (get-service context :alarm)
is the same as calling:
(.getSystemService context Context/ALARM_SERVICE)
Provides functions to read data from Bundles, SharedPreferences and Intents.
like-map
function when called instance of the above-mentioned
classes returns a thin wrapper around it that allows treating the
object like a map (in destructuring, for example).
(defactivity ... (onCreate [this bundle]) ... (let [{:keys [sharks-count lazers?]} (like-map bundle)] ...))
Note that you can use only keywords to extract values.
So (get (like-map bundle) :foo)
is equal to (get
(like-map bundle) "foo")
.
/Warning: an instance wrapped with like-map
cannot be used as an
original one. So for example, if you need to pass bundle
to
another activity, you have to use the original instance and not
the wrapped one./
The most convenient way to work with SharedPreferences is via
defpreferences
macro. It defines an atom with a hashmap, and binds the
named SharedPreferences object to it. The atom is watched, so whenever you
make changes to it, the underlying SP is updated automatically. In other
words, you get an atom with persistent state across application restarts.
(defpreferences prefs "my_sp_file")
(swap! prefs assoc :user "dummy")
;; Later, application is restarted
(:user @prefs)
;=> "dummy"
Keep in mind that because SharedPreferences is limited in what it can keep (primitives and Strings), the same applies to the atom. Putting invalid data into the atom will lead to errors and inconsistencies. For keys you must use only Clojure keywords or Strings.
Be aware not to use a wrapped SharedPreferences file explicitly to avoid inconsistencies.
get-shared-preferences
function returns a SharedPreferences object for the
given name and access mode. Available modes: :private
, :world-readable
and :world-writeable
.
(def prefs (get-shared-preferences "my_preferences" :private))
Then you can extract primitive values from the retrieved object by wrapping
it with like-map
.
To store a value inside the SharedPreferences object put
function is used.
It takes a SharedPreferences.Editor object, a key and a value. The key
could be either a string or a keyword.
(-> (.edit prefs) (put :sharks-count 5) (put :sharks-hungry false) .commit)
Provides convenient method of working with SQLite databases. See detailed description on the dedicated page.
Contains convenience tools to be used while developing the application.
While developing it can be useful to get hold on activity Context in the
REPL, not just inside activity callbacks. *a
is a macro for that. See
neko.activity docs for usage explanation and example.
By default the application crashes if the code that was running on
the UI thread throws an unhandled exception. This greatly reduces
productivity as you have to rerun your application and re-evaluate
all new forms. Wrapping the code with safe-for-ui
macro ensures
that the missed exception won’t be so disastrous.
(safe-for-ui (/ 1 0))
neko.threading/on-ui
uses this macro to wrap its code by default, so in
most cases you can use it instead of safe-for-ui
.
This works only in debug mode, and exceptions will still crash the app in release, so you will be able to normally get crash reports in case of a bug.
When an exception or error happens in the code wrapped with safe-for-ui
,
you will see a Toast notifying you what happened. You can get the Throwable
object to examine it in more detail by calling ui-e
function.
When you develop your application, and observe the changes on the screen,
you usually don’t want your device to go to sleep. Calling (keep-screen-on
this)
in the beginning of :on-create
will ensure the screen doesn’t dim
out in debug build, however in release mode the macro will do nothing.
Contains a builder for AlertDialog. A single function alert-dialog-builder
takes a map of options, and returns a AlertDialog$Builder object. Example:
(defactivity ...
...
(onCreateDialog [this id _]
(-> (alert-dialog-builder this
{:message "Dialog message"
:cancelable true
:positive-text "OK"
:positive-callback (fn [dialog res] ...)
:negative-text "Cancel"
:negative-callback (fn [dialog res] ...)})
.create)))
This namespace contains two functions: find-view
and find-views
that allow to obtain references to child views from a parent view
or an activity by view’s :id
attributes. Second function works
just the same as the first one but it takes a variable number of
view IDs and returns found views in a vector.
Here is a simple example. Please notice that passing the UI tree
directly to set-content-view!
(without calling make-ui
manually) is essential for find-view
to work.
(ns org.bar.baz
(:require [neko.activity :refer [defactivity set-content-view!]]
[neko.find-view :refer [find-view find-views]]
[neko.threading :refer [on-ui]]
[neko.ui :refer [config]]))
(defactivity org.bar.baz.MainActivity
:on-create
(fn [this bundle]
(on-ui
(set-content-view! this
[:linear-layout {}
[:button {:id ::mybtn
:text "A button"
:on-click (fn [w]
(let [edit (find-view this ::myedit)]
(config edit :text "Clicked!")))}]
[:edit-text {:id ::myedit}]]))))
And find-views
in this example could be used like this:
(let [[edit btn] (find-views this ::myedit ::mybtn)]
...
)
For more information on :id
trait see Traits section.
intent
is a function for creating Intent objects. Has two arities:
- takes an action String and an extras map
- takes an Activity instance, classname, and an extras map.
In second arity the classname can be either a Class object, a fully qualified
symbol of the class to be resolved, or a symbol starting with .
(then the
application package name will be prepended to it.
(intent a '.MainActivity {:image-id 16})
;; is the same as
(intent a 'org.my.package.MainActivity {:image-id 16})
Extras map will be saved into extras of the Intent. put-extras
function can
also be called separately to fill extras of an existing intent object. Extras
map supports both string and keyword keys (keywords are converted into
strings during put-extras
, and back during neko.data/like-map
).
Subnamespaces in this namespace provide different utilities to create event listeners.
Every listener function takes a callback function as its argument and returns corresponding Listener object. Later you can attach the returned object to the UI element using necessary methods.
Functional versions of listeners have -call
suffix.
The number of arguments a callback function should accept for every event listener can be found in docstrings for the listeners.
Every listener is also available in macro version (without the
-call
suffix that takes body to execute as argument. It also
establishes implicit arguments in the lexical scope. You can find
arguments’ names in docstrings for listeners.
For example, here are two identical declarations:
(.setOnClickListener ok-button
(on-click-call (fn [view]
(toast (str "View clicked: " view) :long))))
(.setOnClickListener ok-button
(on-click (toast (str "View clicked: " view) ;; "view" is implicit
:long)))
Many listeners have respective traits that use them, for instance:
[:button {:on-click (fn [v] ... )}]
Uility functions and macros for setting listeners corresponding to the android.view.View class.
List of listeners in this namespace include:
- on-click
- on-create-context-menu
- on-drag
- on-focus-change
- on-key
- on-long-click
- on-touch
Uility functions and macros for creating listeners corresponding to the android.widget.TextView class.
List of listeners in this namespace include:
- on-editor-action
Uility functions and macros for creating listeners corresponding to the android.widget.AdapterView class.
List of listeners in this namespace include:
- on-item-click
- on-item-long-click
- on-item-selected
Uility functions and macros for setting listeners corresponding to the android.content DialogInterface interface.
List of listeners in this namespace include:
- on-cancel
- on-click
- on-dismiss
- on-key
- on-multi-choice-click
Contains logging macros that wrap android.util.Log
. For detailed
information see Logging page.
Provides convenient wrappers for Toast and Notification APIs.
To show user a Toast do the following:
(toast context "My message" :long)
First argument is any Context object, second is a message, third is toast
duration (could be either :long
or :short
). Duration argument can be
omitted.
Note that unlike in Java API toast
function creates a toast and
immediately shows it. If for some reason you need to create a
reusable Toast, feel free to use Toast constructor and then call
.show
on it.
To create a Notification object use notification
function. You
have to provide the following arguments to it:
- icon (optional if the default icon is set)
- ticker-text
- content-title
- content-text
- action
- when (now by default)
Action should be in the form of vector where the first element is
a PendingIntent type (:activity
, :broadcast
, :service
) and
the second one is an action to create Intent from.
You can set the default notification icon by calling
set-default-notification-icon!
that takes one argument - either
an actual image resource or a Android resource ID.
To show the created notification call fire
on it. Two-argument
version takes an ID (either integer or keyword) as the first
argument that allows to cancel
the notification in future.
(fire :new-mail
(notification {:icon R$drawable/ic_launcher
:ticker-text "You've got mail"
:content-title "One new message"
:content-text "FROM: [email protected]"
:action [:activity "my.package.VIEW_MAIL"]}))
(cancel :new-mail)
Same as in original Android, you can access any resource via Java interop,
like android.R$drawable/ic_menu_add
or org.my.app.R$string/app_name
. But
it is certainly inconvenient to specify full package name for your
application resources. To avoid this you can put
(neko.resource/import-all)
call at the beginning of your namespace (under
ns
declaration). This will import all R subclasses, like R$drawable
,
R$string
etc. so that you can drop the package name when using them.
get-string
and get-drawable
function return actual String and Drawable
objects given a resources ID. If get-string
is provided with two or more
arguments, it treats the first argument as a format, and the rest arguments
as substitutions.
(get-string R$string/app_name) ;=> returns the application name
(get-drawable R$drawable/ic_launcher) ;=> returns the application icon
Utilities used to manage multiple threads on Android.
Any code that touches the user interface has to be run on the
application UI thread. Neko provides a macro called on-ui
that
takes arbitrary code to be run it on the main UI thread.
(on-ui (.setText ok-button "OK"))
If the current thread is already the UI one, then the code will be directly executed.
Code inside on-ui
is automatically wrapped in neko.debug/safe-for-ui
, so
if the exception happened on UI thread during development it won’t crash the
application.
Functional version on-ui*
wraps a given nullary function into (on-ui)
and returns it as a new function (so you have to call it by yourself).
One more useful function is on-ui-thread?
which tells whether
the thread it is executed on is the UI one.
Alternatively you can add code on the message queue of a specific
View object you’d like this code to operate on. post
macro is
similar to on-ui
but additionally takes a View object as first
argument.
(post ok-button (.setText ok-button "OK"))
There is also post-delayed
macro that takes additional time
argument in milliseconds, and executes its body after this time
passes.
(post-delayed ok-button 3000 ;; Wait for 3 seconds (.setText ok-button "OK"))
Tools for defining and manipulating Android UI elements. See User interface page for detailed instructions.
The most important thing here is make-ui
function. It allows to
declaratively define the UI of your application. This function is
intended as a replacement for XML-defined user interface. make-ui
returns a View. Usually you don’t have to call make-ui
manually as in most
places you can pass UI tree directly (in set-content-view!
, in adapters’
new-view-fn
etc.).
make-ui
takes an activity context and a UI tree as arguments. The tree is
a vector that contains of these elements:
- element keyword
- attribute map
- & other elements
(make-ui this [:linear-layout {:orientation :vertical}
[:button {:text "A button"
:enabled true}]])
The element keyword represents the element you want to create. Thus
:button
stands for android.widget.Button, :linear-layout
— for
android.widget.LinearLayout and so on. All elements are defined in the
neko.ui.mapping namespace and new elements can be added.
The attribute map consists of key-value pairs where keys are
Clojure keywords. By default attributes are processed in such
fashion that these pairs are trasformed into setter calls. So for
instance, :editable false
is transformed into (.setEditable
edit-wdg false)
. For non-standard attributes the so
called traits can be defined. Trait is a function that takes an
attribute map and performs some actions for the attribute(s) it
represents. All traits of the element are applied to the attribute
map before the default handler kicks in. Every element has its own
traits list. In the following example a special on-click trait
will take specified anonymous function, wrap it in OnClickListener
and then generate call to .setOnClickListener
.
(make-ui [:button {:on-click (fn [w] (toast "Clicked!"))}])
Attribute values are also treated specially. If value is a Clojure
keyword, then it is looked up in the element’s value map. This map
contains a mapping of element-specific keywords to real values. If
the keyword is not present there, it is transformed into a static
field following a rule: all letters are uppercased, and dashes are
replaced with with underscores. For example, the value
:choice-mode-multiple
defined for the :list-view
element will
be transformed into ListView/CHOICE_MODE_MULTIPLE
.
You can use config
function to change existing element’s state.
It works similarly to make-ui
, but receives attributes as
optional arguments rather than in one single map.
(config ok-button
:text "OK"
:on-click (fn [_] (execute-operation)))
get-screen-orientation
returns a keyword for the current orientation of
the device. Can return :portrait
, :landscape
, :square
, or :undefined
(rare case which means something is wrong).
This namespace stores the keywords-to-elements mapping and provides utilities to define your own elements.
You can create new element using defelement
macro. The first
argument it takes is the keyword name of the element. Other
(optional) arguments:
- classname — which class the element represents (if any)
- traits — the list of traits for this element
- attributes — a map of default attributes for this element
- values — a map of specific attribute values
- inherits — see below
- container-type — see below.
(defelement :text-view
:classname android.widget.TextView
:traits [:id :layout-params]
:attributes {:text :default-text}
:values {:default-text "I am a textview"})
You also can define an element that inherits a base one. It is
achieved by providing :inherits
option to the defelement
.
Values and attributes maps provided are merged with the original
ones (the newer rewrite the older), provided traits are appended
to the original.
(defelement :long-thin-button
:inherits :button
:attributes {:layout-width :fill
:layout-height :wrap})
If you want to inherit from a container element (like :linear-layout
or
:relative-layout
) and you don’t take control over the attributes they
expect (like :layout-width
or :layout-above
This namespace contains helper functions to manipulate ListViews.
Currently it has only two functions: get-checked
and
set-checked!
, both of them operate on ListViews with multiple
check items enabled.
get-checked
takes a ListView instance and returns a vector of
numbers. These numbers represent the numbers of those items in the
list that are checked. Two-argument version takes a ListView and an
arbitrary sequence (usually the one from which ListView adapter was
constructed) and returns only those elements that are checked. Be
careful to provide a sequence not exceeding the number of ListView
elements itself.
set-checked!
takes a ListView and a sequence of numbers and sets
the respective ListView items to be checked.
This namespace provides custom adapters for ListView, GridView etc.
ref-adapter
function creates an Adapter that watches over a
reference type and updates itself when the reference is updated.
ref-adapter
takes three arguments:
-
create-view-fn
— function of context that either returns an UI tree or a new View instance that acts as single ListView item; -
update-view-fn
— function that takes four arguments: item’s position in the ListView, View instance of the item, parent container and the data for the current item. The function should update the View instance with the provided data; -
ref-type
— a ref or an atom that stores data; -
access-fn
(optional) — function that is called on the dereferencedref-type
to get the list that should be displayed by ListView. By defaultidentity
function is used.
ref-adapter implements View caching technique. If View for the
element has not been created yet, create-view-fn
is called to
create it. Then update-view-fn
is called on the View with data
provided.
access-fn
allows a reference type to store some broader data
structure (a map, for example) and to display only part of it in
a ListView.
When the reference type is updated, adapter updates automatically.
The following example demonstrates usage of ref-adapter along with ListView:
(def alphabet
(atom {:type :phonetic
:letters ["alpha" "bravo" "charlie" "delta"]}))
(defn make-adapter []
(ref-adapter
(fn [_] [:linear-layout {:id-holder true}
[:text-view {:id ::caption-tv}]])
(fn [position view _ data]
(let [tv (find-view view ::caption-tv)]
(config tv :text (str position ". " ))))
alphabet
:letters))
;; Somewhere in Activity.onCreate()
... (set-content-view! this [:list-view {:adapter (make-adapter)}])
;; Now the created ListView displays four items:
;; 1. alpha
;; 2. bravo
;; 3. charlie
;; 4. delta
(swap! alphabet update-in [:letters] conj "echo")
;; ListView automatically appended another item: 5. echo
Please keep in mind, that if you want to manually create a View instance in
create-fn (rather than return a UI tree), you have to call make-ui-element
with additional parameters instead of make-ui
. This is important because
ListView expects special kind of LayoutParams to be set for the widget.
make-ui-element
takes three obligatory arguments - an Activity context, UI
tree and the options map.
(defn make-adapter [activity]
(ref-adapter
(fn [] (neko.ui/make-ui-element activity [:text-view {}]
{:container-type :abs-listview-layout}))
(fn [position view _ data]
(.setText ^TextView view (str position ". " data)))
alphabet
:letters))
cursor-adapter
creates a special CursorAdapter instance that synergizes
with neko.data.sqlite
namespace.
It takes the following arguments:
-
context
— Activity instance -
create-view-fn
— nullary function that either returns an UI tree or a new View instance that acts as single AdapterView item -
update-view-fn
— function that takes three arguments: View to be updated, cursor and the already extracted data map for the current item. The function should update the View instance with the provided data. -
cursor-or-cursor-fn
— this can be either cursor object returned byneko.data.sqlite/query
, or a nullary function that returns such cursor when called.
Unlike ref-adapter, you have to update cursor-adapter manually by using
update-cursor
function. It takes single argument — the adapter — if
the latter was created with cursor-fn
, or adapter and new cursor if the
adapter was created by passing a cursor.
(defn get-my-cursor [options]
(neko.data.sqlite/query ... options ...))
(defn make-adapter [activity]
(neko.ui.adapters/cursor-adapter
activity
(fn [] [:linear-layout {:id-holder true}
[:text-view {:id ::caption-tv}]])
(fn [view _ data]
(let [tv (find-view view ::caption-tv)]
(config tv :text (:first-name data))))
(fn [] (get-my-cursor (options-from-somewhere))))) ;; We passed cursor-fn
;; Somewhere in Activity.onCreate()
... (set-content-view! this [:list-view {:id ::my-lv
:adapter (make-adapter this)}])
;; Somewhere in a place you want to update ListView
(let [^ListView lv (find-view activity-context ::my-lv)]
(neko.ui.adapters/update-cursor (.getAdapter lv))) ;; So we call update-cursor with 1 argument
Namespaces
- neko.action-bar
- neko.activity
- neko.context
- neko.data
- neko.data.shared-prefs
- neko.debug
- neko.dialog.alert
- neko.find-view
- neko.intent
- neko.listeners
- neko.log
- neko.notify
- neko.resource
- neko.threading
- neko.ui
- neko.ui.mapping
- neko.ui.listview
- neko.ui.adapters
User interface
Action bar
SQLite
Logging