Namespaces

satchit8 edited this page Oct 21, 2016 · 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 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.

neko.action-bar

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

neko.activity

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

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!. You might as well just specify a vector of features in :features option in defactivity.

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

neko.context

You can always access the application instance (root context of the application) in neko.App/instance field.

Getting system services

(get-service :alarm) ;; or
(get-service context :alarm)

is the same as calling:

(.getSystemService context Context/ALARM_SERVICE)

neko.data

Provides functions to read data from Bundles, SharedPreferences and Intents.

Extracting data from objects 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 ...
  (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./

neko.data.shared-prefs

defpreferences

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.

Low-level functions

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)

neko.data.sqlite

Provides convenient method of working with SQLite databases. See detailed description on the dedicated page.

neko.debug

Contains convenience tools to be used while developing the application.

Getting activity contexts from REPL

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.

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

Keeping the screen on in debug builds

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.

neko.dialog.alert

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

neko.find-view

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.

neko.intent

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

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

Many listeners have respective traits that use them, for instance:

[:button {:on-click (fn [v] ... )}]

neko.listeners.view

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

neko.listeners.text-view

Uility functions and macros for creating listeners corresponding to the android.widget.TextView class.

List of listeners in this namespace include:

  • on-editor-action

neko.listeners.adapter-view

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

neko.listeners.dialog

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

neko.log

Contains logging macros that wrap android.util.Log. For detailed information see Logging page.

neko.notify

Provides convenient wrappers for Toast and Notification APIs.

Toasts

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.

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: foo@bar.com"
                     :action [:activity "my.package.VIEW_MAIL"]}))

(cancel :new-mail)

neko.resource

Import application resources

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.

Getting resource in runtime

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

neko.threading

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.

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.

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

neko.ui

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

Building a UI tree

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.

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

Utilities

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

neko.ui.mapping

This namespace 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
  • 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"})

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

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

neko.ui.listview

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.

neko.ui.adapters

This namespace provides custom adapters for ListView, GridView etc.

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 — 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 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 [_] [: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

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 by neko.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