20191012のAndroidに関する記事は4件です。

「関数型デザイン&プログラミング」でのScalaでのST sモナドの実装について

書籍「関数型デザイン&プログラミング」をマイペースに読み進めています。
この書籍の14章では、局所作用を実行するためのクラスであるSTSTRefが紹介されています。
今回はこれについてまとめてみます。

ST、STRefは以下のImmutableとみなされるための条件を満たし、状態遷移を局所化する仕組みを提供するクラスです。状態遷移をローカルスコープに閉じたものとし、それをScalaの型システムを利用して強制させることで、参照透過性を保証させます。

Immutableな型とみなすための条件

以下の条件に違反するものはコンパイルされないようにScalaの型システムを利用します。

  • ミュータブルなオブジェクトへの参照を保持しているもの以外は、そのオブジェクトが変更されていることを認識できない。
  • ミュータブルなオブジェクトを、それが作成されたスコープの外側から参照することができない。

(「関数型デザイン&プログラミング」より引用)

STの実装

上記を踏まえたSTの実装は以下のようになっています。
(Github: fpinscala/answers/src/main/scala/fpinscala/localeffects/LocalEffects.scala
より)

sealed trait ST[S,A] { self => 
    protected def run(s: S): (A,S) //protectedにすることにより、runが実行可能なスコープを制限。変更を外部に漏らさないようにする

    def map[B](f: A => B): ST[S,B] = new ST[S,B] { 
        def run(s: S) = { 
            val (a, s1) = self.run(s) 
           (f(a), s1) 
        } 
    } 
    def flatMap[B](f: A => ST[S,B]): ST[S,B] = new ST[S,B] { 
        def run(s: S) = { 
            val (a, s1) = self.run(s) 
            f(a).run(s1) 
        } 
    } 
} 

object ST { 
    def apply[S,A](a: => A) = { 
        lazy val memo = a //runが複数回呼ばれることを考慮したキャッシュ
        new ST[S,A] { 
            def run(s: S) = (memo, s) 
        } 
    } 

    def runST[A](st: RunnableST[A]): A = 
        st[Unit].run(())._1 
} 

上記で注目したいのはSTのrunメソッドです。runメソッドをprotectedで修飾することにより、外部からのアクセスを制限しています。これがpublicだと、引数のsを外部から渡せるようになってしまいます。runメソッド内で使われる可能性のあるSを外部で保持していることになるので、上記の不変条件に違反してしまいます。
また、runメソッドのシグネチャは S => (A, S)なので、runによりA型の値が新たに生成されることを示しています。

※runSTについては後述。

STRefの実装

STRefST内の計算で使用されるミュータブルな参照を表現しています。STRefをST内のみでしか参照・操作できないよう実装することにより、状態遷移を局所化します。上記の不変条件を満たすよう実装されているため、ST内でSTRefがもつ値を変更したとしても、それが外部に漏れることはありません。
上記を踏まえたSTの実装は以下のようになっています。
(Github: fpinscala/answers/src/main/scala/fpinscala/localeffects/LocalEffects.scala
より)

sealed trait STRef[S,A] { //sealedで修飾することにより、外部からSTRefを生成することはできない
    protected var cell: A 
    def read: ST[S,A] = ST(cell) 
    def write(a: => A): ST[S,Unit] = new ST[S,Unit] { 
        def run(s: S) = { 
        cell = a 
        ((), s) 
        } 
    } 
} 

object STRef { 
    def apply[S,A](a: A): ST[S, STRef[S,A]] = ST(new STRef[S,A] { 
        var cell = a 
    }) 
} 

STRefはsealedにより修飾されているので、外部から直接STRefを生成することはできません。

sealed trait STRef[S,A] { //sealedで修飾することにより、外部からSTRefを生成することはできない

外部からSTRefを生成するには、STRef.apply呼び出す必要がありますが、これはSTによりラッピングされた形で返却されます。

  //applyメソッド呼び出しにより、STRefはSTにラッピングされた形で生成される
  //ST[S, A]とSTRef[S, A]のSの型を一致させる
  def apply[S,A](a: A): ST[S, STRef[S,A]] = ST(new STRef[S,A] { 

ここで注目したい点は以下です。

・applyで生成されるST[S, A]とSTRef[S, A]のSの型が一致している
これにより、型Sを利用して、ST内でSTRefが操作できることになります。

・STRefを外部から直接生成できず、STの内部にラップされる形でしか生成できないようにしている
これにより、STRefの生存期間はSTの生存期間に束縛されます。
STRefの状態遷移をSTの中に閉じ込めることで、外部からは副作用のないようにものとして振る舞うことが可能となります。

・apply、read、writeメソッドは戻り値としてSTを返す

 //以下の3つのメソッドはSTを戻り値として返すため、STの計算が継続できる
 def apply[S,A](a: A): ST[S, STRef[S,A]]
 def read: ST[S,A]
 def write(a: => A): ST[S,Unit]

上記のようにapply、read、writeは戻り値としてSTを返します。
これにより、上記演算はSTのコンテキストに紐づくようになるため、STRefを操作する計算が継続できるようになります。

ST[S, A]とSTRef[S, A]の型パラメータSについて

お気づきかもしれませんが、上記STとSTRefの実装では型Sを使用していません。
このSはSTRefをST内で操作するためのトークンのような役割しか持っていません。
なので、このSの型は実のところ何でも良いということになります。
これは、STのアクションが型Sに関して多相であると考えられます。

RunnableSTの定義

上記のSTの実装ではrunSTメソッドが定義されていました。その実装を再掲すると以下のようになっています。

object ST {
  中略
  def runST[A](st: RunnableST[A]): A = 
        /*
        val as = st[Unit].run(()) //as: (A, S)
        val a = as._1 // a: A
        */
        st[Unit].run(())._1 //Sは多相であるため、Unitを指定しても問題ない。
}

引数にRunnableSTなるものが指定されています。
STアクションからSTRefを取り出せないように、Scalaの型システムを利用して定義したものがRunnableSTです。

RunnableSTの実装は以下のようになっています。

(Github: fpinscala/answers/src/main/scala/fpinscala/localeffects/LocalEffects.scala
より)

trait RunnableST[A] {
  def apply[S] : ST[S, A]
}

RunnableSTを作成する目的は以下です。

1. ST[S, STRef[S, A]]型のアクションが実行できないようにする
外部からST[S, STRef[S, A]]のアクションの実行を許可しまうと、その結果としてSTRef[S, A]が取得できることになってしまいます。状態遷移をローカルスコープ内に局所化するためSTRefが外部にさらされることになるので、これを防ぎます。

2. T型がS型に関与するようなST[S, T] のアクションが実行できないようにする
S型の変更により、T型も変更されるようなアクション、
T型の変更により、S型も変更されるようなアクションは
同様に状態遷移の局所化に反するので、これを実行できないよう防ぎます。

3. ST[S, A]の型Sが多相であることを表現する
上述の通り、ST・STRefでの型Sはトークンの役割しか持っていません。
この型Sが多相であることを表現します。

STのrunメソッドはprotectedで宣言されているため、外部からSTをrunするためには、このrunSTメソッドを使用しなければなりません。

具体的な使用例は以下のような感じです。

val p = new RunnableST[Int] { //A型だけを指定し、S型を指定する必要がない
  /*
  型Sがapplyにバインドしているため、new RunnableSTを指定した場合、
  型Sにアクセスすることはできない
  => 型SにSTRefを指定して、STRefを外部から取得することはできない
  */
  def apply[S] = for { 
    r <- STRef(1)
    x <- r.read
    _ <- r.write(x + 1)
    y <- r.read
  } yield y
}

val r = ST.runST(p) // r: Int = 2

このように、STRefをSTアクションの内側だけにとどめ、参照を変化させても安全な仕組みを提供しています。綺麗ですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kotlin時代のAndroid向けtooltipライブラリ「Balloon」を触ってみる

はじめに

 チュートリアルによくありがちな、「特定のViewに対して吹き出しのような表示を行って使い方などを案内する」という,いわゆるtootip的な表現がしたかったので、これを実現できるライブラリを探してみました。

 Androidでこのような要件に使えるライブラリといえばandroid-target-tooltipが定番かなと思っていましたが、調べてみると同種のものとしてBalloonと言うとてもいい感じのライブラリを発見することができました。使い勝手などを調査してみたかったので、本記事ではこれに触ってみます。

「Balloon」について

ライブラリのURL(https://github.com/skydoves/Balloon)

特徴

  • full-kotlin
    • Javaが混ざってるとぬるぽに怯えながらコードを書くことになるので個人的には嬉しいポイントです。
  • 「kotlin dsl」で記述できる
    • このライブラリでは「kotlin dsl」を活用して直感的に見た目を定義できます
  • コードだけで見た目を定義できる
    • 人によってこれをメリットと捉えるか否かは分かれそうですが、個人的にはいちいちXMLで見た目定義したりするのは面倒に感じるところがあるので、メリットにあげています。

触ってみる

まずはシンプルに触ってみる

 まずはシンプルに、「特定のViewに対してテキストを含んだtooltipを出す」というのを作ってみます。
以下のようにmain_activity.xmlを記述します。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="show tip."
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="hide tip."
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button1" />

buttob1を押したらtextViewに対してtooltip表示、button2を押したら非表示、という感じで作ると以下のようになります。

MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val balloon = createBalloon(baseContext) {
            setArrowSize(10)
            setWidthRatio(0.5f)
            setHeight(65)
            setArrowPosition(0.5f)
            setCornerRadius(4f)
            setAlpha(0.9f)
            setText("Hello Balloon.")
            setBackgroundColorResource(R.color.colorPrimary)
            setBalloonAnimation(BalloonAnimation.FADE)
            setLifecycleOwner(this@MainActivity)
        }

        findViewById<Button>(R.id.button1).setOnClickListener {
            balloon.showAlignTop(findViewById(R.id.textView))
        }

        findViewById<Button>(R.id.button2).setOnClickListener {
            balloon.dismiss()
        }
    }
}

createBalloonの部分がkotlin dslになっています。書きやすく、読みやすいですね。

これは以下のように動作します。

Peek 2019-10-07 00-34.gif

インターフェースが直感的なので、特に解説することはないかなーと思います。とても簡単にtooltipを表示することができました。

カスタムビューを表示してみる

次は独自に定義したViewを表示させてみましょう。これができると自由度が一気に広がるので捗ります。

以下のようなViewを表示させてみます。

custom_balloon.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="200dp">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="Custom View."
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView1" />

</androidx.constraintlayout.widget.ConstraintLayout>

CustomBalloobXml.jpg

コードはこんな感じに書きます。

MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val balloon = createBalloon(baseContext) {
            setArrowSize(10)
            setWidthRatio(0.5f)
            setHeight(200)
            setArrowPosition(0.5f)
            setCornerRadius(4f)
            setAlpha(0.9f)
            setLayout(R.layout.custom_balloon)
            setBackgroundColorResource(R.color.colorPrimary)
            setBalloonAnimation(BalloonAnimation.FADE)
            setLifecycleOwner(this@MainActivity)
        }

        findViewById<Button>(R.id.button1).setOnClickListener {
            balloon.showAlignTop(findViewById(R.id.textView))
        }

        findViewById<Button>(R.id.button2).setOnClickListener {
            balloon.dismiss()
        }
    }
}


setLayoutを使うことで独自に定義したViewを表示できます。また、ここでは触れませんが、セットしたViewを取り出すgetContentView()というメソッドもあり、これを利用することで細かい見た目の変更を行ったり、各種リスナの登録を行ったりできます。
注意点として、setLayoutをしても、そのサイズに合わせてtooltipが伸縮したりはしないようなので、そこはsetHeightなどを活用してマニュアルでいい感じに設定してやる必要があります。

ここまで実装できると以下のように動作します。

custom.gif

無事独自に定義したViewを表示することができました!

おわりに

 今回は「Balloon」について調べました。インターフェースが使いやすいですし、見た目もいい感じなので、今後色々活用していこうかなと考えています。
個人的に改良したいところもあるので、改良を施してコントリビュートもしていきたいところです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初めてのAndroidアプリで躓いたところ【2:リリース編】(プログラミング初学者)

前回の続きです。
初めてのAndroidアプリで躓いたところ【1:アプリ編】(プログラミング初学者)

今回はaabファイルの作成やアップロードするまで道のりで詰まったことを紹介します。
※Google Playにアプリをリリースするのに「デベロッパーアカウント」を作るのですが、
こちらは何も難しくないので今回は触れません。

aabファイルの作成

apkファイルとaabファイルの2種類ありますが、aabファイルの方を選びましょう。(Googleも推奨しているので)
aabファイルの方は「アプリサイズの縮小」や「ビルド時間の短縮」などのメリットがあります。
詳しくはこちらをご参照ください → Android App Bundle - Android Developers

パッケージ名の変更

まず、パッケージ名から先に変更する必要があります。
デフォルトのcom.example.***のパッケージ名では、
アップロードするときに「そのパッケージ名は禁止されている」と怒られるからです。
パッケージ名の一括変換は↓の記事を参考にしました。
Android アプリのパッケージ名を変更するには

aabファイルの作り方

Android Studio(3.5)で作成していきます。

まず「ビルド」→「署名済みバンドル/APKの生成…」に進みます。

ダイアログが表示されるので、
上の「Android App Bundle」にチェックを入れて「次へ」をクリックします。

ここでは署名キーを作成します。
署名キーは他の新しいアプリを公開するときにも使用するので、大切に保管しましょう。
(2回目以降は「既存選択」で署名キーのファイルパスを入力します)
「新規作成」をクリックし、

新規キー・ストアの項目を埋めていきます。

キー保管パス: .jksファイルの置き場所
パスワード: バージョンアップなどでずっと使う大事なパスワード
エイリアス: 署名キーの名前
パスワード: 上のパスワードと同じが無難
有効期間(年): 25年以上が推奨
国コード: JP (日本)
※「証明書」の項目は全部は埋めなくても大丈夫です。

必要な項目を埋められたら「OK」をクリックします。

前のダイアログに戻ったら、「キー保管パスワード」「キー・エイリアス」「鍵パスワード」を入力し、
「公開済みアプリを登録するための暗号化されたキーをエクスポートする」にチェックを入れ、
ファイルパスを指定します。(キー保管パスと同じが無難かと)
そして「次へ」をクリックします。

最後にaabファイルの宛先フォルダーを指定して、
ビルド・バリアントの「release」を指定して「完了」をクリックします。
(アプリを公開する場合は「release」です)

これでaabファイルと鍵・キーストアの作成が完了です。

Google Play Consoleでリリースの準備

画像の準備

ストアの掲載情報に最低必要な画像は以下のとおりです。

アイコン画像:512 × 512 png
スクリーンショット:1辺の長さ最小320px、最大3840px jpgかpng 2枚〜8枚
フィーチャーグラフィック 横1024 × 縦500 jpg か png

フィーチャーグラフィックとは、GooglePlayのトップに表示される画像です。
Facebookもリンクを貼るとフィーチャーグラフィックが表示されます。

アプリのリリース

次にアプリのリリースを作成します。
まず、Google Play Consoleで「アプリを作成」から掲載情報を入力していきます。サイドバーの「アプリのリリース」で「製品版トラック」の「管理」をクリックします。
「リリースを作成」で次に進みます。

「Googleでアプリ証明鍵の管理、保護を行う(推奨)」の部分は「次へ」をクリックします。

「追加する Android App Bundle と APK」で、
作成したaabファイルをアップロードします。
リリース名とリリースの新機能の説明を記入したら、
「保存」をクリックし、保存ができたら「確認」をクリックします。

これで「公開の準備が整いました」と表示されますが、
まだ「製品版として公開を開始」は押せません。
まだ左のバーで色が付いてないチェックマークの項目を完了させる必要があります。
Screenshot 2019-10-12_17-05-18-609.png

それ以降の項目は特に詰まるところは無いかと思います。

左のバーのチェックマークがすべて緑色になったら、
リリースの画面に戻り、「製品版として公開を開始」をクリックすれば完了です!

あとは致命的なバグさえ無ければ2日以内にはGoogle Playに公開されると思います。
(場合によっては数日かかることもあるらしいです)

アップデートの方法

何かバグを修正したり、機能追加をしたときは、
アプリのバージョンコードを変える必要があります。

バージョンコードの変更

バージョンコードを変えるには、build.gradleファイルの以下の数値を変更します。

build.gradle(app)
versionCode 2
versionName "1.1"

その上で、aabファイルを作成します。

aabファイルの作成

aabファイルの作成では「キー保管パス」で「既存選択」を選び、
作成した署名キーのファイルパスを入力します。
他の手順は上記のやり方と同じです。
(「公開済みアプリを登録するための暗号化されたキーをエクスポートする」のチェックははずして構いません)

aabファイルのアップロード

Google Play Consoleの「アプリのリリース」で「製品版トラック」の「管理」をクリックし、
「リリースを作成」をクリックします。
あとは上記のリリースの手順と同じです。

アップデートはこれで完了です。

おわりに

最近アプリの審査が厳しくなったのか、審査が通るのに数時間から数日かかるようです。
何か致命的なバグが残っていると数日かかったり、リジェクトされる可能性が高くなります。
自分も2回目のアップデートでバグを完全に修正したら24時間以内で通りました。
Androidアプリ開発が初めての方に参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(もしもに備えて)Android StudioのKeymapの保存方法

先日、PCを再起動したら何故かAndroid StudioのKeymap(ショートカットの設定)が消えてしまいました。:sob:

そういったもしもの時に備えて、Android StudioのKeymapを保存する方法を記載したいと思います。(復元する方法も。)

前提

自作のKeymapを MyKeymap として作成済み。
スクリーンショット 2019-10-12 15.43.31.png

保存方法

至って簡単。

File > Export Settings... を選択。
スクリーンショット_2019-10-12_15_43_57.png

どの設定をExportするかどうかの選択画面になるので、 Key maps Key maps (schemes) をチェック。
スクリーンショット_2019-10-12_15_44_38.png

OK を押すと、 settings.zip としてExportされます。

保存されたzipを確認

中身は↓のような感じになっていて、作成した MyKeymap も中に入っています。
スクリーンショット 2019-10-12 15.45.54.png

復元方法

File > Import Settings... を選択。
スクリーンショット_2019-10-12_16_40_54.png

Exportした settings.zip を選択します。

どの設定をImportするか聞かれるので、 Key maps Key maps (schemes) を選択します。
スクリーンショット 2019-10-12 15.46.30.png

あとはAndroid Studioを再起動するかどうかを聞かれるので、再起動させると設定を復元可能です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む