Skip to content
This repository has been archived by the owner on Apr 25, 2024. It is now read-only.

Namespaces

Alexander Yakushev edited this page Apr 16, 2014 · 38 revisions

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 be already proficient with the Android platform. Neko doesn’t try to completely hide the Java side of the Android, neither it tries to replace the key Android concepts. So even if you want to develop for Android in Clojure, you still need to study how it is developed in Java. Fortunately there are lots of educational materials to get you started.

neko.action-bar

Provides utilities to modify application’s action bar. For detailed information see Action bar page.

This namespace stores facilities related to the Activity class.

Defining an activity

Since defining a new activity requires to inherit the Activity class, defactivity macro simplifies this work. As well as defapplication this macro takes optional arguments to specify different activity events. For example:

(defactivity your.package.name.MainActivity
  :on-create (fn [this savedInstanceState]
               ...))

Other event handlers that you can define in the macro:

  • on-create-options-menu
  • on-options-item-selected
  • on-activity-result
  • on-new-intent
  • on-start
  • on-restart
  • on-resume
  • on-pause
  • on-stop
  • on-destroy

If :on-create option is used, it also internally saves the Activity object so it can be later retrieved via (neko.activity/*a). Calling (*a) from a namespace where activity is defined will return its instance.

In case you have multiple activities in the same namespace, you can assign unique “keys” to all of them and then call (*a key) to get the object. For example: You can provide a custom alias for activity :key option:

(use '[neko.activity :only [defactivity *a]])

(defactivity org.bar.foo.MainActivity
  :key :main
  ...)

(defactivity org.bar.foo.OptionsActivity
  :key :options
  ...)

(*a :main) ;=> returns MainActivity instance
(*a :options) ;=> returns OptionsActivity instance

Of course for it to work the activity should be in focus (you might also get the instance of an activity that was paused/stopped, but Neko does not control the lifetime of activities as it uses WeakHashMap to store the references).

In finished code intended for the release please use the activity instance provided as first argument to methods (this) and properly pass it to your functions that operate on the Activity.

NOTE: As of Neko 3.1 =:def= parameter in =defactivity= is deprecated. It will be removed in the next major release. Please use =(*a)= instead.

You can also specify event handlers for your activity like for any gen-classed class. defactivity macro sets the default prefix to be ActivityName-.

(defactivity your.package.name.FooActivity
  ...)

(defn FooActivity-onResume [this]
  ...)

Other useful functions

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!.

Fragments

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"}]])

Functions in this namespace are responsible for instantiating the dynamic compilation. Unless you are not satisfied with neko’s default behavior with regard to dynamic compilation, you won’t probably need to use this namespace.

This namespaces contains utilities to aid in working with a context.

Storing context instance

Inside the onCreate of the Application its context object is caught and bound to the context var. This allows many neko functions to implicitly use it as a singleton rather then require it being passed everywhere.

You can use this var in your code too instead of getting the application context out of the activity context. Another advantage is that context is already type-hinted so you don’t need to do it yourself.

Getting system services

Alternatively to calling .getSystemService on context instance you can use this macro to get services more conveniently. Please keep in mind, that it is a macro with all its implications. You must provide the keyword argument denoting the service directly to the macro.

(get-service :alarm)

Inflating layouts

You can use this function to inflate a layout given the resource ID. It returns an inflated View instance.

(inflate-layout R$id/item1)

Provides functions to save and restore the application data (Bundle, SharedPreferences) and transfer data across applications (Intent).

Extracting data from classes like from maps

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 ...
  :on-create (fn [this bundle])
               (let [{:keys [sharks-count lazers?]} (like-map bundle)] ...))

Note that you can use either strings or 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./

Working with SharedPreferences

First of all, 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 assoc! function is used. It takes a SharedPreferences.Editor object, a key and a value. The key could be either a string or a keyword.

The number of types SharedPreferences support is very limiting. But since it includes String you can serialize and save any Clojure structure in it (as long as this structure can be parsed by the reader). Here’s the example of storing an integer value and a vector:

(-> (.edit prefs)
    (assoc! :sharks-count 5)
    (assoc-arbitrary! :shark-weights [300 350 330 320 250])
    .commit)

To get a complex value out use get-arbitrary function:

(get-arbitrary (like-map prefs) :shark-weights)

Contains useful tools to be used while developing the application.

Safely handling UI exceptions

By default the application crashes if the code that was running on the UI thread throws an unhandled exception. This greatly disturbs 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.

do-ui uses this macro to wrap its code by default.

(safe-for-ui (/ 1 0))

Warning: do not directly use the functional version available by the name =safe-for-ui*= because it will always be compiled regardless of the build type.

Examining UI exceptions

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.

TODO: this namespace will be changed.

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
  (:use [neko.activity :only [defactivity *a set-content-view!]]
        [neko.find-view :only [find-view find-views]]
        [neko.threading :only [on-ui]]
        [neko.ui :only [config]]))

(defactivity org.bar.baz.MainActivity
  :on-create
  (fn [this bundle]
    (on-ui
     (set-content-view! (*a)
       [:linear-layout {}
        [:button {:id ::mybtn
                  :text "A button"
                  :on-click (fn [w]
                              (let [edit (find-view (*a) ::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 (*a) ::myedit ::mybtn)]
  ...
  )

For more information on :id trait see Traits section.

Contains functions for neko initialization and setting runtime flags. Functions in this namespace are responsible for Neko initialization and starting the nREPL server, and depend on the build type of the project (debug or release). The only useful thing for you here is init which takes application context and some optional arguments. This function already gets called in the SplashActivity.java (if you created the project with lein-droid) and by every activity created using defactivity. Otherwise you have to call this function manually to ensure Neko is initialized in a proper way.

neko.listeners

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)))

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 utilities. You can use the following macros directly from this namespace:

  • d - debug
  • e - error
  • i - info
  • v - verbose
  • w - warning

Each logging macro takes variable number of arguments which it concatenates. You can also provide optional keyword arguments - :exception and :tag. The former, when followed by Exception object, will print its stacktrace. The latter allows you to set the custom tag for the log message (by default the current namespace is used as a tag). Examples:

(require '[neko.log :as log])

(neko.log/d "Some log string" {:foo 1, :bar 2})
(neko.log/i "Logging to custom tag" [1 2 3] :tag "custom")
(neko.log/e "Something went wrong" [1 2 3] :exception ex)

You can also omit certain log types from being executed (for example, to drop all debug logs from your release build). In order to do so, you have to specify :ignore-log-priority option in your project.clj.

Provides convenient wrappers for Toast and Notification APIs.

Toasts

To show user a Toast do the following:

(toast "My message" :long)

First argument is a message, second is the toast duration (could be either :long or :short).

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.

Notifications

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)

neko.resource

Provides utilities to resolve application resources.

Find any resource

get-resource macro provides an ability to find any resource by the given resource type and the resource name. Both type and name are denoted by keywords. Keyword name is transformed into a static field using the default convention (dashes and dots are replaced by underscores).

(= (get-resource :drawable :ic-launcher)
   org.my.app.R$drawable.ic_launcher)

By default the root application package is used. You can use a namespace-qualified keyword to choose another package for R class.

(= (get-resource :layout :android/simple-list-item-1)
   android.R$layout/simple_list_item_1)

Some attributes (such as View’s :text or ImageView’s :image allow specifying resource keywords directly, without calling get-resource.

[:linear-layout {}
 [:text-view {:text :app-name}]
 [:image-view {:image :ic-launcher}]]

Special resource functions

There are a few shortcut macros for different kinds of resources. They take only name keyword as argument.

The list includes:

  • get-id - same as (get-resource :id ...)
  • get-layout - same as (get-resource :layout ...)
  • get-drawable - like (get-resource :drawable ...), but returns Drawable object for the given resource keyword
  • get-string - like (get-resource :string ...), but returns string for the given resource keyword. Can also take optional format values to the resource string.

Compile-time resource resolution

The described functions use reflection to go from keywords to integer IDs. In case this becomes a bottle-neck, Neko provides macros that mirror functions above in compile-time. get-resource becomes resolve-resource, all other functions have resolve instead of get in them. The difference is in resolve-drawable and resolve-string macros that return just the resource ID rather than final object.

Please note, that since resolve-... are macros, you have to provide keywords to them directly (i.e. not through a variable).

Special compile-time reader syntax

You can use special data readers for turning keywords into integer IDs in compile-time. They are: #res/id, #res/layout, #res/string and #res/drawable. Using these data readers before resource keywords is equivalent to calling resolve macros manually. The benefit of these readers is that you don’t have to import anything into your namespace to use them (and also less parentheses).

[:linear-layout {}
 [:text-view {:text #res/string :app-name}]
 [:image-view {:image #res/drawable :ic-launcher}]]

Utilities used to manage multiple threads on Android.

Operating on UI thread

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. on-ui automatically establishes *activity* binding for the code being run if this var was bound in the surrounding dynamic scope.

Code inside on-ui is automatically wrapped in try..catch block so if the exception happened on UI thread it won’t crash the application.

You can also use on-ui* function which takes a zero-argument function as argument.

One more useful function is on-ui-thread? which tells whether the thread it is executed on is the UI one.

Posting code on View’s message queue

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"))

Functional version are available as post* and post-delayed*.

Tools for defining and manipulating Android UI elements. See User interface page for detailed instructions.

The most important thing here is make-ui macro. It allows to declaratively define the UI of your application. This macro is intended as a replacement for XML-defined user interface. make-ui returns a View and its return value should be set as a content view for the acitivity, usually in the activity’s onCreate method.

Building a UI tree

make-ui takes a UI tree as an argument. The tree is a vector that contains of these elements:

  • element keyword
  • attribute map
  • & other elements
(make-ui [:linear-layout {:layout-height :fill}
          [: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.

Attributes

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.

Changing element after it is created

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)))

This namespaces stores the keywords-to-elements mapping and provides utilities to define your own elements.

Define a new element

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.
(defelement :text-view
  :classname android.widget.TextView
  :traits [:def :layout-params]
  :attributes {:text :default-text}
  :values {:default-text "I am a textview"})

Define an element based on an existing one

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})

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 and Spinner.

ref-adapter

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 - a function of zero arguments that either returns an UI tree or a new View instance that acts as single ListView item;
  • update-view-fn - a 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 is a ref or an atom that stores data;
  • access-fn (optional) - a function that is called on the dereferenced ref-type to get the list that should be displayed by ListView. By default identity 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 [] [:text-view {}])
   (fn [position view _ data]
     (.setText ^TextView view (str position ". " data)))
   alphabet
   :letters))

;; Somewhere in Activity.onCreate()
... (make-ui [: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 - a context (either an application context or 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))