【Android】ExpandableListViewの使い方

Kotlinが公式になったというので5, 6年ぶりにAndroid再入門。

kogoto.hatenablog.com

想定するデータセット

親子関係のデータ。ここではこんなんを想定しています。

folders

id name
1 fodler1
2 fodler2
3 fodler3

categories

id folder_id name
1 1 category1
2 1 category2
3 1 category3
4 2 category1
5 2 category2
6 2 category3

実装概要

データセットのマップリストとかマップのマップリストとか用意してアダプタに差し込みます。
この辺りを書くのがJavaだと冗長でつらたんですがKotlinはvalで宣言できるしnewもいらんのですね。

// java
List<Map<String, String>> folders = new ArrayList<Map<String, String>>();
List<List<Map<String, String>>> categoriesOfFolder = new ArrayList<List<Map<String, String>>>();
// kotlin
val folders = mutableListOf<Map<String, String>>()
val categoriesOfFolder = mutableListOf<MutableList<Map<String, String>>>()

このリストをアダプタに設定します。引数はリファレンス見てください。

SimpleExpandableListAdapter | Android Developers

val adapter = SimpleExpandableListAdapter(
        this,
        folders,
        android.R.layout.simple_expandable_list_item_1,
        arrayOf(KEY_PARENT),
        intArrayOf(android.R.id.text1, android.R.id.text2),
        categoriesOfFolder,
        android.R.layout.simple_expandable_list_item_1,
        arrayOf(KEY_CHILD),
        intArrayOf(android.R.id.text1, android.R.id.text2)
)

リストのイベントは

  • OnGroupClickListener
  • OnChildClickListener
  • OnGroupExpandListener
  • OnGroupCollapseListener

などで拾ってください。

画面イメージ

f:id:ktdk:20170627230103p:plain

このままだとリストが開いているのか閉じているのか分かりにくいので、開閉しているステートを取ってビューの色変えたりしたいですよね。ListenerでもいけそうだけどAdapterカスタマイズして書くのがスマートだった気がします。そこはまたの機会に。

ソースコード

package com.example

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.ExpandableListView
import android.widget.SimpleExpandableListAdapter

class MainActivity : AppCompatActivity() {

    companion object {
        const val KEY_PARENT = "FOLDER"
        const val KEY_CHILD = "CATEGORY"
    }

    private lateinit var mAdapter: SimpleExpandableListAdapter

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

        loadFolders()
    }

    private fun loadFolders() {
        val folders = mutableListOf<Map<String, String>>()
        val categoriesOfFolder = mutableListOf<MutableList<Map<String, String>>>()

        for (i in 1..5) {
            val category = mapOf(KEY_PARENT to KEY_PARENT + i.toString())
            folders.add(category)

            val categories = mutableListOf<Map<String, String>>()

            for (j in 1..3) {
                val todo = mapOf(KEY_CHILD to KEY_CHILD + j.toString())
                categories.add(todo)
            }

            categoriesOfFolder.add(categories)
        }

        mAdapter = SimpleExpandableListAdapter(
                this,
                folders,
                android.R.layout.simple_expandable_list_item_1,
                arrayOf(KEY_PARENT),
                intArrayOf(android.R.id.text1, android.R.id.text2),
                categoriesOfFolder,
                android.R.layout.simple_expandable_list_item_1,
                arrayOf(KEY_CHILD),
                intArrayOf(android.R.id.text1, android.R.id.text2)
        )

        val listView = (findViewById(R.id.todoListView) as ExpandableListView)
        listView.setAdapter(mAdapter)
    }
}

Kotlinスタートブック -新しいAndroidプログラミング

Kotlinスタートブック -新しいAndroidプログラミング

PostgreSQLにアクセスする

標準ライブラリであるdatabase/sqlを利用してPostgreSQLにアクセスしてみます。

チュートリアルがあるので詳細はこちらを見ると良いです。

Go database/sql tutorial

データベースドライバのインストー

SQLDrivers にドライバの一覧があります。
PostgreSQLのドライバがいくつかありますが今回はlib/pqを利用します。

$ go get github.com/lib/pq

データベースへの接続

sql.Open()でデータベースオブジェクトを生成します。この関数は*sql.DBを返します。

db, err := sql.Open("postgres", "host=localhost port=32768 user=postgres dbname=golang_dev sslmode=disable")

sql.Open()はドライバや接続文字列のバリデーションをしているだけで実際は接続しません。
db.Ping()したりクエリを発行した時点でコネクションが確立します。

クエリの発行

参照系は複数件取得するdb.Query()、1件取得するdb.QueryRow()を利用します。

rows, err := db.Query("select id, name from users where id = $1", 1)

for rows.Next() {
    err := rows.Scan(&id, &name)
}

rows.Scan()は渡した変数の型に応じてタイプキャストしてくれます。
例えばidはデータベース上bigintで定義していますが変数id stringを渡せば文字列型で取得できます。(あまりよろしくないと思いますが)

キャストできない型は当然エラーになります。

sql: Scan error on column index 1: converting driver.Value type string ("foo") to a int: invalid syntax

更新系はdb.Exec()を利用します。

db.Exec("insert into users (name) values ($1)", "foo")

プリペアドステートメントを使えるdb.Prepare()とかもあります。

関数化とかしてませんがまとめるとこんな感じ。

package main

import (
    "database/sql"
    _ "github.com/lib/pq"
    "log"
)

func main() {
    // DBオブジェクト生成
    db, err := sql.Open("postgres", "host=localhost port=32768 user=postgres dbname=golang_dev sslmode=disable")

    if err != nil {
        log.Fatal(err)
    }

    defer db.Close()

    // 疎通確認
    err = db.Ping()

    if err != nil {
        log.Fatal(err)
    }

    // 登録
    stmt, err := db.Prepare("insert into users (name) values ($1)")

    if err != nil {
        log.Fatal(err)
    }

    res, err := stmt.Exec("Foo")

    if err != nil {
        log.Fatal(err)
    }

    rowCnt, err := res.RowsAffected()

    if err != nil {
        log.Fatal(err)
    }

    log.Printf("affected = %d\n", rowCnt)

    // 取得
    var (
        id   int
        name string
    )

    rows, err := db.Query("select id, name from users")

    if err != nil {
        log.Fatal(err)
    }

    defer rows.Close()

    for rows.Next() {
        err := rows.Scan(&id, &name)

        if err != nil {
            log.Fatal(err)
        }

        log.Println(id, name)
    }

    err = rows.Err()

    if err != nil {
        log.Fatal(err)
    }
}

Amazon Route53でドメインを購入する

はじめに

Macbook Pro 2017(タッチバーなし)

スペースグレイいけてますね。バタフライキーボードも思ったより快適です。
新しいもの手に入れるとテンション上がりますよね!

f:id:ktdk:20170617092151p:plain

本題

突然ですがR53でドメインを購入してみます。

購入手順

ドメイン管理コーンソールを開いて「Register Domain」を選択します。

f:id:ktdk:20170617085224p:plain

取得したいドメイン名を入力すると利用可否の一覧が表示されます。
購入するドメインを「Add to card」すると料金が年額で表示されるのでOKなら次へ進みます。

f:id:ktdk:20170617085313p:plain

ドメインの管理者情報を入力します。

Whoisで個人情報が表示されないように「Privacy Protection」で「Hide contact information if the TLD registry, and the registrar, allow it」が選択されていることを必ず確認しましょう。

TLDによっては表示されてしまうこともあるので注意

f:id:ktdk:20170617085628p:plain

入力した情報を確認し購入手続きを完了します。

f:id:ktdk:20170617090422p:plain

手続きが完了すると「In Progress」状態になります。

f:id:ktdk:20170617090750p:plain

数十分で「Registered domains」に表示されます。
確認用のメールが飛んできているはずなので、そこからリンクを踏んで登録完了です。

f:id:ktdk:20170617090802p:plain

Whoisで確認

f:id:ktdk:20170617091855p:plain

ドメインの更新

ドメイン詳細をみると「Auto Renew: Enabled」となっているので、1年ごとに自動更新されるようです。
自動更新したくない場合は「Disabled」に切り替えましょう。

VRゴーグルを作ってみた

大晦日

何してましたか?僕は連日飲みすぎて気分悪いので、引きこもって工作してました。

VR!VR!

VR流行ってますよね、たぶん。気になってます。

Oculus RiftとかPSVRとかいろいろあると思うんですが、少々お高い!

合わない人は合わないらしいんで、買って後悔するのもあれだなーとか思いながら、都内でVR体験できるとこあるけど行くのめんどいなーとか。

そしたらこんなんあるんですね。

ダンボール製やん → 自作できるよね?ってことで調べてみたら簡単に作れるんですねー。

というわけで数十年ぶりに工作してみました。ハマったところだけ強調しときます。

いざ100均

ダイソーでミニルーペをゲッツします。

f:id:ktdk:20161231120452j:plain

売り場

老眼鏡とかサングラスの横 (10分くらいハマりました)

取り外し方

ひねれば取れるとかあったんですが、たぶん最近のやつは接着剤でがっちりくっついてるっぽいんですね。

まずマイナスドライバーでテコの原理を利用して分解します。

更にマイナスドライバーでテコの原理を利用して1枚目のレンズを分離します

そして2枚目のレンズは周りをニッパーなどでバッキバキにし、マイナスドライバーでテコの原理を利用してとり外します。

f:id:ktdk:20161231121649j:plain

箱作り

お菓子の箱でもなんでもいいんですが、懐かしい工作用紙が売ってたのでそれで作ることにしました。

f:id:ktdk:20161231124620j:plain

で、レンズ間の距離が重要ですのでこれだけはしっかりと調整します。

VR対応のアプリとか表示しながら、レンズどうしを近づけたり目やスマホとレンズの距離を調整してベストな間隔を決めます。

ちなみに僕の場合はレンズ間が3.5cm、スマホとレンズの距離が5cmくらいでいい感じに見えました。

組み立て

だいぶ雑いですが完成しました!

f:id:ktdk:20161231125949j:plain

いざVR!

VRの元動画とか見たときは何やただの立体視だかマジカル・アイみたいなもんか、思ってたんですが...

こんなダンボールのくせに!溢れ出す臨場感!!

次は天空でVR楽しんでこようかと思います。

天空でVR(バーチャルリアリティ)体験

良いお年を!!

Elixir - Agent

Agent

Agent - Elixir

Agentは状態を保持します。

# 空のリストを初期状態として生成
{:ok, agent} = Agent.start_link(fn -> [] end)

# 現在の状態(list)を引数として新しい状態を生成する
Agent.update(agent, fn list -> ["eggs" | list] end)

# 現在の状態を取得する
Agent.get(agent, fn list -> list end)

# プロセスを終了する
Agent.stop(agent)

Process.alive?(agent)
# > false

Agentを利用してキーバリューストアを実装してみます。

# kv.exs
defmodule KV do
  def start_link do
    Agent.start_link(fn -> %{} end)
  end

  def get(kv, key) do
    Agent.get(kv, &Map.get(&1, key))
  end

  def put(kv, key, value) do
    Agent.put(kv, &Map.put(&1, key, value))
  end

  def delete(kv, key) do
    Agent.get_and_update(kv, &Map.pop(&1, key))
  end
end

iexから実行してみます。

$ iex kv.exs
iex> {:ok, kv} = KV.start_link
{:ok, #PID<0.64.0>}
iex> KV.put(kv, :hello, "world")
:ok
iex> KV.get(kv, :hello)
"world"
iex> KV.delete(kv, :hello)
"world"
iex> KV.get(kv, :hello)
nil

Process間でAgentを共有してみる

一つのカウンターをマルチプロセスでカウントアップしてみます。

# counter.exs
defmodule Counter do
  def start_link do
    Agent.start_link(fn -> 0 end)
  end

  def increment(counter) do
    Agent.update(counter, fn x -> x + 1 end)
  end

  def count(counter) do
    Agent.get(counter, fn x -> x end)
  end
end

defmodule CountProcess do
  def start_link(counter, parent) do
    {:ok, spawn_link(fn -> loop(counter, parent) end)}
  end

  defp loop(counter, parent) do
    count = Counter.count(counter)

    IO.puts "#{inspect(self)} #{count}"

    cond do
      count < 10_000 ->
        Counter.increment(counter)
        loop(counter, parent)
      true ->
        IO.puts "#{inspect(self)} #{count} Completed!!"
    end
  end
end

defmodule ConcurrentCounter do
  def run(n) do
    {:ok, counter} = Counter.start_link
    Enum.each(1..n, fn x -> CountProcess.start_link(counter, self) end)
  end
end
$ iex counter.exs
iex> ConcurrentCounter.run(4)
...
#PID<0.71.0> 9999
#PID<0.70.0> 9999
#PID<0.72.0> 10003
#PID<0.69.0> 10003
#PID<0.71.0> 10003
#PID<0.70.0> 10003
#PID<0.72.0> 10003 Completed!!
#PID<0.69.0> 10003 Completed!!
#PID<0.71.0> 10003 Completed!!
#PID<0.70.0> 10003 Completed!!

複数のプロセスが起動してカウントアップしているのが確認できました。
が、同期を取っていないので10,000を超えてカウントされてしまっています。

Javaだとスレッドでsynchronizedとかして同期させた記憶があるのですが、Elixirだとsend-receive? Task? GenServer? この辺りを利用するのかな?

Elixir - Process

Getting StartedrのProcessについてサクッとまとめました。

Processes - Elixir

  • spawn ... 子プロセスを生成する
  • spawn_link ... 子プロセスを生成する(例外発生時に共倒れ)
  • send, receive ... プロセス間のメッセージ送受信
  • Task ... spawnのラップ、結果も返してくれる
  • State ... send, receiveの再帰呼び出しで実現

Processes

すべてのコードはプロセス中で実行されます。プロセスはお互いに隔離されており、並列で実行されメッセージパッシングを通じてやり取りします。並列処理のベースであるだけではなく、これにより分散型のフォールトトレラントプログラムを構築する役割があります。

ElixirのプロセスはOSのそれと混同すべきではありません。他のプログラミング言語のスレッドとは違い、メモリとCPUの観点からすると非常に軽量です。そのため、数十万のプロセスを同時に実行することも珍しくありません。

spawn

spawn/1は新しいプロセスを生成しPIDを返します。 生成されたプロセスは関数を実行し終了します。

iex> pid = spawn fn -> 1 + 2 end
#PID<0.58.0>
iex> Process.alive?(pid)
false

プロセス自身のIDはself/0で取得できます。

iex> self()
#PID<0.56.0>
iex> Process.alive?(self())
true

send と receive

プロセス間のメッセージのやり取りにはsend/2receive/1を利用できます。 以下は自分自身にメッセージを送信するサンプルです。

defmodule ProcessSample do
  def send_message(msg) do
    # 自分自身のメールボックスに保存
    send self(), msg
  end

  def receive_message do
    # 送信されたメッセージを受け取る
    receive do
      {:msg, msg} -> msg
    after
      # タイムアウトを設定
      # ※設定しない場合はパターンマッチするメッセージを受信するまで待ち続ける
      1_000 -> 'timeout'
    end
  end
end

# 送信したメッセージがパターンマッチする場合
ProcessSample.send_message({:msg, 'hello, world'})

IO.puts ProcessSample.receive_message

# マッチしない場合
ProcessSample.send_message('hello, world')

IO.puts ProcessSample.receive_message
$ elixir process.exs
hello, world
timeout

これをspawn/1を利用してプロセス間でやり取りするように書き換えてみます。

defmodule InterProcessSample do
  def run do
    parent = self()

    # PIDを送信する子プロセスを生成
    spawn fn -> send(parent, {:pid, inspect(self())}) end
  end

  def receive_message do
    receive do
      {:pid, pid} -> "I'm #{pid}."
    after
      1_000 -> 'timeout'
    end
  end
end

InterProcessSample.run

IO.puts InterProcessSample.receive_message
$ elixir process.exs
I'm #PID<0.52.0>.

Links

プロセスの生成で実際によく利用されるのはspawn_link/1です。 spawn/1は子プロセスが例外を起こしても親プロセスは実行され続けますが、 spawn_link/1は親プロセスも共に終了します。

# spawn.exs

# spawn/1は子プロセスで例外が起きてもプログラムは終了しない
# spawn fn -> raise "oops" end

# spawn_link/1はプログラムが終了する
spawn_link fn -> raise "oops" end

receive do
  :hello -> '子プロセスが例外を起こすまで待つ'
end

この仕組みはフォールトトレラントシステムを構築するのに重要な役割を果たします。 Elixirではプロセスの死活監視、生成を行うSupervisorにリンクすることが多いです。

他のプログラミング言語ではtry/catch等で例外処理をしますが、 このような仕組みがあるため、例外は放っておくのが実際のところです。

Tasks

Taskはspawnより詳細なエラーレポートを提供します。 Task.start/1, Task.start_link/1はPIDと併せて結果も返してくれるためSupervisorで利用されます。

iex> spawn fn -> raise "oops" end
#PID<0.58.0>
iex>
10:40:03.817 [error] Process #PID<0.58.0> raised an exception
** (RuntimeError) oops
    :erlang.apply/2

iex> Task.start fn -> raise "oops" end
{:ok, #PID<0.60.0>}
iex>
10:40:11.695 [error] Task #PID<0.60.0> started from #PID<0.56.0> terminating
** (RuntimeError) oops
    (elixir) lib/task/supervised.ex:94: Task.Supervised.do_apply/2
    (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Function: #Function<20.54118792/0 in :erl_eval.expr/5>
    Args: []

State

アプリケーションの設定や読み込んだファイルの内容等の状態を保持する場合にもプロセスを利用します。
以下はキーバリューストアのサンプルです。

# kv.exs

defmodule KV do
  def start_link do
    # 空のMapとともに子プロセスを生成
    Task.start_link(fn -> loop(%{}) end)
  end

  defp loop(map) do
    receive do
      {:get, key, caller} ->
        # caller(呼び出し元)にメッセージを送信
        send caller, Map.get(map, key)
        loop(map)
      {:put, key, value} ->
        loop(Map.put(map, key, value))
    end
  end
end

これをiexで実行してみます。 flush/0はメールボックス内のメッセージを出力します。

$ iex kv.exs
iex> {:ok, pid} = KV.start_link
{:ok, #PID<0.62.0>}
iex> send pid, {:put, :hello, :world}
{:put, :hello, :world}
iex> send pid, {:get, :hello, self()}
{:get, :hello, #PID<0.60.0>}
iex> flush
:world
:ok

また、pidに名称を与えて実行することも可能です。

iex> {:ok, pid} = KV.start_link
{:ok, #PID<0.63.0>}
iex> Process.register(pid, :kv)
true
iex> send :kv, {:put, :hello, :world}
{:put, :hello, :world}

大抵の場合これらのパターンを自前で実装することはなく、 例えばAgent等の抽象化されたものを利用します。

iex> {:ok, pid} = Agent.start_link(fn -> %{} end)
{:ok, #PID<0.72.0>}
iex> Agent.update(pid, fn map -> Map.put(map, :hello, :world) end)
:ok
iex> Agent.get(pid, fn map -> Map.get(map, :hello) end)
:world

Elixirのパターンマッチ

JSONを取得するサンプル

適当なAPIサーバーを立ててJSONを取得してみます。

APIサーバー

http://localhost:3000/capitals.jsonにアクセスすると首都一覧を返します。

const express = require('express')
const app = express()

app.get('/capitals.json', (request, response) => {
  response.contentType('application/json')

  const capitals = [
    { country: 'China', city: 'Beijing' },
    { country: 'Japan', city: 'Tokyo' },
    { country: 'Mongolia', city: 'Ulan Bator' },
    { country: 'North Korea', city: 'Pyhongyang' },
    { country: 'South Korea', city: 'Seoul' },
  ]

  response.send(JSON.stringify(capitals))
})

app.listen(3000)
クライアント

HTTPoisonのサンプル通りに記述してJSONを取得します。

defmodule JsonHandler do
  def main(args) do
    fetch_capitals
  end

  def fetch_capitals do
    url = "http://localhost:3000/capitals.json"

    case HTTPoison.get(url) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        IO.puts body
      {:ok, %HTTPoison.Response{status_code: 404}} ->
        IO.puts "Not found :("
      {:error, %HTTPoison.Error{reason: reason}} ->
        IO.puts "Unknown error"
    end
  end
end
実行
$ ./json_handler
[{"country":"China","city":"Beijing"},{"country":"Japan","city":"Tokyo"},{"country":"Mongolia","city":"Ulan Bator"},{"country":"North Korea","city":"Pyhongyang"},{"country":"South Korea","city":"Seoul"}]

最初のcaseにマッチするのでレスポンスが出力されました。

次にサーバーを起動しない状態で実行すると...

$ ./json_handler
Unknown error

最後のcaseにマッチするのでメッセージが出力されます。

ではマッチしないケースはどうなるでしょうか。
503を返すようにサーバーを書き換えて実行してみます。

response.status(503)
response.send()
$ ./json_handler
** (CaseClauseError) no case clause matching: {:ok, %HTTPoison.Response{body: "", headers: [{"X-Powered-By", "Express"}, {"Content-Type", "application/json"}, {"Date", "Thu, 18 Aug 2016 04:39:06 GMT"}, {"Connection", "keep-alive"}, {"Content-Length", "0"}], status_code: 503}}
    (error_handling_sample) lib/error_handling_sample.ex:9: ErrorHandlingSample.fetch_capitals/0
    (elixir) lib/kernel/cli.ex:76: anonymous fn/3 in Kernel.CLI.exec_fun/2

マッチするケースがないよ、ってことでクラッシュしてしまいます。
というわけで200, 404以外にマッチするパターンを追加します。

    case HTTPoison.get(url) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        IO.puts body
      {:ok, %HTTPoison.Response{status_code: 404}} ->
        IO.puts "Not found :("
      {:ok, %HTTPoison.Response{status_code: status_code}} ->
        IO.puts "Status code #{status_code}"
      {:error, %HTTPoison.Error{reason: reason}} ->
        IO.puts "Unknown error"
    end

これで503も拾えました。

$ ./json_handler
Status code 503

case文を関数化する

もう少しElixirらしく?、各パターンを関数に書き換えます。
要注意なのがcase文と同じく先にある関数からマッチがされていくという点です。
つまり関数の記述順序も気にしないといけません。(まぁこの場合はパターンが悪いと思いますが)

defmodule JsonHandler do
  def main(args) do
    fetch_capitals
  end

  def fetch_capitals do
    url = "http://localhost:3000/capitals.json"

    HTTPoison.get(url)
    |> process_response
  end

  defp process_response({:ok, %HTTPoison.Response{status_code: 200, body: body}}) do
    IO.puts body
  end

  defp process_response({:ok, %HTTPoison.Response{status_code: 404}}) do
    IO.puts "Not found :("
  end

  defp process_response({:ok, %HTTPoison.Response{status_code: status_code}}) do
    IO.puts "Status code #{status_code}"
  end

  defp process_response({:error, %HTTPoison.Error{reason: reason}}) do
    IO.puts "Unknown error"
  end
end

クラッシュ怖い

マッチするパターンがないとクラッシュするって言われると、あらゆるパターン(特にエラー処理)を想定しなければならず、正常系よりも異常系の処理を書くことがメインのお仕事になってきます。

そんなの辛い。

"Let it crash."

qiita.com

Elixir(とそのベースになっているErlang)のプロセスは生成のためのコストが小さいため「下手にエラー処理するコードを書いてプロセスを維持するよりはさっさとクラッシュさせて、それに続く処理の中で対策して再起動したほうがよい」という思想があります。

クラッシュ上等というわけですね。

というわけで今度はクラッシュしまくるスタイルに書き換えてみる予定です。