くろニャァ ~ ToastとNotification

前置き

ClojureでAndroidアプリを開発するシリーズ。今回は、トーストと通知(Notification)について整理します。

トースト

トーストは、画面の下の方に出てくる吹き出しメッセージです。名前の由来は「乾杯」じゃなくて、食べる方のトーストみたいですね。焼けたパンがトースターから飛び出てくるようにメッセージが出てくる、と。

toast

出すのは簡単で、neko.notify/toastを使います。

(toast "やっぱ取り消すニャ" :short)

第2引数には、:longか :shortを指定します。省略時は :long扱いです。

通知

通知(Notification)は、通知領域(画面の左上)に出てくるやつですね。いくつかの要素で構成されています。

  • ticker ...通知到着時に数秒間だけ表示されるメッセージ
  • small-icon ...通知領域に表示されるアイコン
  • large-icon ...通知領域を開いたときに表示されるアイコン
  • content-title ...通知領域を開いたときに表示される通知のタイトル
  • content-text ...通知領域を開いたときに表示される通知の本文
  • action ...通知領域を開いて、通知をタッチしたときに実行されるIntent

noti1

左の画面の"Breaking news from gpsoft!"というのがtickerです。これは数秒後に消えて、右側の画面のように、small-iconだけが残ります。

通知領域を下へドラッグして開いた画面が下図です。

noti2

large-icon、content-title(「速報」)、content-text(「くろニャァの新バージョンが~」)、通知時刻、small-iconが表示されています。large-iconはオプションで、もし指定しなかった場合は、large-iconの場所にsmall-iconが出て右端には通知時刻だけが表示されます。ユーザが通知をタッチすると、actionとして通知に仕込まれたIntentが発動して、例えば別アプリが起動したりします。

通知を出すには、neko.notify名前空間の関数を使います。

  • notification ...android.app.Notificationオブジェクトを作る
  • fire ...通知を出す
  • cancel ...通知を取り消す

ただ、neko.notify/notificationが使っているNotificationコンストラクタは、API11でdeprecateされてしまいました。API19でも使えないことはないようですが、せっかくなので新スタイル(Notification.Builderを使う)で実装しなおしてみましょう。汎用関数なので、ui_helper.cljに入れます。

Notificationオブジェクトを作る関数(ui_helper.clj)
(ns jp.dip.gpsoft.clonya.ui-helper
  (:require
    [neko.context :as context]
    [neko.log :as log])
  (:import
    [android.app Activity]
    [android.content Intent]
    [android.app PendingIntent Notification Notification$Builder]))

...

(defn- construct-pending-intent-with-intent
  [[action-type ^Intent intent]]
  (case action-type
    :activity (PendingIntent/getActivity context/context 0 intent 0)
    :broadcast (PendingIntent/getBroadcast context/context 0 intent 0)
    :service (PendingIntent/getService context/context 0 intent 0)))

(defn notification-with-intent
  [& {:keys [icon large-icon ticker-text when content-title content-text action auto-cancel]
      :or {when (System/currentTimeMillis) auto-cancel true}}]
  {:pre [icon action]}
  (-> (Notification$Builder. context/context)
    (.setSmallIcon icon)
    (.setLargeIcon large-icon)
    (.setTicker ticker-text)
    (.setWhen when)
    (.setAutoCancel auto-cancel)
    (.setContentText content-text)
    (.setContentTitle content-title)
    (.setContentIntent (construct-pending-intent-with-intent action))
    (.getNotification)))

neko.notify/notificationを改造したのが、notification-with-intent関数です。引数には、small-iconやticker、actionなどの情報を指定します。

  • :iconと:actionは必須
  • :iconにはsmall-iconのリソースIDを指定
  • :actionにはアクション種別とandroid.app.PendingIntentオブジェクトからなるベクタを指定
  • アクション種別は、:activityか:broadcastか:serviceとする
  • ユーザが通知をタッチすると、PendingIntentに包まれたIntentが、startActivity、sendBroadcast、startServiceされる
  • large-iconにはBitmapオブジェクトを指定
  • ticker-text、content-title、content-textには文字列を指定
  • whenには、System/currentTimeMillisベースの日時(デフォはnow)を指定
  • auto-cancelには、通知タッチ時に通知を自動キャンセルするかどうか(デフォはtrue)を指定

private関数のconstruct-pending-intent-with-intentも、neko.notifyの関数を改造したものです。nekoオリジナルでは、関数内部でIntentを作るようになっていますが、改造版では引数でIntentをもらうようにしました。

また、notification-with-intent関数の中で使っているNotification.Builder#getNotificationは、API16でdeprecatedされました。API16以降の環境ではNotification.Builder#buildに変えた方が良いでしょう。どちらも、引数は不要です。

くろニャァのKuActivityに、通知ボタンとキャンセルボタンを追加します。

文字列リソース(strings.xml)
<?xml version="1.0" encoding="utf-8"?>
<resources>
  ...
  <string name="caption_back_bt">Back</string>
  <string name="caption_update_bt">UP通知</string>
  <string name="caption_cancel_update_bt">Cancel</string>
  ...
</resources>
レイアウト(main.clj)
(defn- ku-layout [act]
  [:linear-layout {:orientation :vertical}
   (make-header-label :ku-name)
   [:linear-layout {:orientation :horizontal}
    [:button {:text :caption_update_bt
             :on-click #(on-update-fn act %)}]
    [:button {:text :caption_cancel_update_bt
             :on-click #(on-cancel-update-fn act %)}]]
   [:button {:text :caption-back-bt
             :on-click (fn [_] (.finish act))}]
   ])
通知ボタン(main.clj)
(def ^:private uri-str "https://play.google.com/store/apps/details?id=jp.dip.gpsoft.clonya")

(defn- on-update-fn [act v]
  (->>
    (notification-with-intent
      :icon #res/drawable :ic-newversion
      :large-icon (.getBitmap (get-drawable :ic-launcher))
      :ticker-text "Breaking news from gpsoft!"
      :content-title "速報"
      :content-text "くろニャァの新バージョンが出たニャ!"
      :action [:activity
               (Intent. Intent/ACTION_VIEW (Uri/parse uri-str))]
      :auto-cancel true)
    (notify/fire :new-version))
  (toast "通知したニャ\n画面の上見てニャ" :short))

large-iconには、DrawableオブジェクトではなくBitmapオブジェクトを指定する必要があります。上記コードでは透過色の処理がマズいのですが、サンプルなのでご容赦下さい。文字列もリソース化せずに直埋めしてます。

通知をタッチすると、:actionに指定したIntentが発動し、ブラウザがGooglePlayの「くろニャァ」のページを開きます。と同時に、通知は自動キャンセルされます。

gplay

fireの第1引数に指定したキーワードは、明示的に通知をキャンセルするときに必要です。

キャンセルボタン(main.clj)
(defn- on-cancel-update-fn [act v]
  (toast "やっぱ取り消すニャ" :short)
  (notify/cancel :new-version))
Last modified:2014/05/22 18:56:56
Keyword(s):
References:[Clojure meets Android]
This page is frozen.