イベントハンドラとラムダ

おさらい


val printHelloWorld = {
   println("Hello, world!")
}

printHelloWorld()
//printHelloWorld.invoke()

これで以下が出力される。

Hello, world!

同様に引数を渡す。


val printGreeting = { word: String, name: String ->
   println("$word, $name")
}

printGreeting("おはよう", "太郎")
printGreeting.invoke("こんにちは", "次郎")

おはよう, 太郎
こんにちは, 次郎

これまでのコールバック

inferface を使ったコールバックは kotlin では以下のように書けます。


interface CallBackListener {
    fun onHoge(foo: String, bar: Int)
}

// caller
var callback: CallBackListener? = null
callback?.onHoge("foo", 100)

// callee
val callback = object : CallBackListener {
    override fun onHoge(foo: String, bar: Int) {
        print("$foo : $bar")
    }
}

これを typealias で。


typealias CallBackListener = (foo: String, bar: Int) -> Unit

// caller
var callback: CallBackListener? = null
callback?.invoke("foo", 100)

// callee
val callback = { foo, bar ->
    print("$foo : $bar")
}

【Kotlin】 TypeAliasで関数リテラルに名前を付ける - Qiita

イベントハンドラ

例えば、RecyclerView でそれぞれのアイテムのViewHolder内のある要素のクリックイベントを拾いたい時、Activity(View)までが遠いですよね。

Activity(View)
↓↑
RecyclerViewAdapter
↓↑
ViewHolder

少しでも、コールバック記述をシンプルにして見通し良くしておきたいものです。

ここで Type Alias を使います。

typealias EventHandler = (T) -> Unit

java - Kotlin: Use a lambda in place of a functional interface? - Stack Overflow

1. ViewHolder にハンドラー記述。クラスの外。

ItemViewHolder:


internal typealias ItemHandler = (Item) -> Unit

それを拡張して、ハンドラークラスを作ります。
invoke を override します。

ShareItemHandler:


internal class ShareItemHandler(
  private val activity: Activity
) : ItemHandler {

  override fun invoke(item: Item) {
    IntentBuilder.from(activity)
        .setType("text/plain")
        .setChooserTitle("Share ${item.name} to ?")
        .setText("${$item.name} ${item.address}")
        .startChooser()
  }
}

ClipboardCopyItemHandler:


internal class ClipboardCopyItemHandler(
  private val context: Context
) : ItemHandler {

  override fun invoke(item: Item) {
    val clipboard = context.systemService<ClipboardManager>()
    clipboard.primaryClip = ClipData.newPlainText(item.name, item.address)
  }
}

2. Activity で そのインスタンスを生成して
RecyclerAdapter経由でViewHolder内にセットしていきます。

以下関係部分のみ抜粋。

MainActivity:


val onCopy = ClipboardCopyItemHandler(this)
val onShare = ShareItemHandler(this)

// ...

val layoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = layoutManager
val adapter = ItemAdapter(layoutInflater, onCopy, onShare)
recyclerView.adapter = adapter

ItemAdapter:


internal class ItemAdapter(
  private val inflater: LayoutInflater,
  private val onCopy: (Item) -> Unit,
  private val onShare: (Item) -> Unit
) : RecyclerView.Adapter<ItemViewHolder>() {

  // ...

  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
    val view = inflater.inflate(R.layout.item, parent, false)
      return ItemViewHolder(view, onCopy, onShare)
    }

ItemViewHolder:



internal typealias ItemHandler = (Item) -> Unit // ***

internal class ItemViewHolder(
  private val root: View,
  private val onCopy: ItemHandler
  private val onShare: ItemHandler,
) : ViewHolder(root), OnClickListener, OnMenuItemClickListener {

  // ...  

  override fun onClick(view: View) = when(view.id) {
    R.id.copy -> onCopy(item!!)
    R.id.share -> onShare(item!!)
    else -> throw IllegalArgumentException("Unknown menu item: $menuItem")
  }

なんだか素晴らしいですね。

RecyclerView利用時のテンプレートとして。


あの Jake Wharton さんが伝授する Android Studio を高速化する方法

直接は関係ないような気がしますが

「Kotlinを使ったら Android Studio が遅くなった」

という疑問に対して、神と呼ばれる Jake Wharton さんがコメントしています。

「プラグイン捨てれ」

I find the IDE slower when adding any plugin. Especially ones which provide a great deal of functionality such as an entire language support.

Go to preferences > plugins > and disable everything you don't need. I disabled

Android APK Support
Android Games
Android NDK
App Links Assistant
CVS
EditorConfig
Firebase (all of them)
GitHub
Google (all of them)
hg4idea
Settings repository
Subversion integration
Task management
Terminal
Test recorder
TestNG
YAML

Goes real fast now.

JakeWharton comments on Android Studio slower when using Kotlin

本体や各ウインドウの起動などかなり高速化されます。

Kotlin や Google や RxJava など多岐に渡るオープンソースプロジェクトに深く関わる Jake さんが必要ないプラグインが私たち小市民に必要なわけがありません。

一回も使ったことがないプラグインが多くあるのは間違いありません。

他の多機能アプリやソフトウェアにも同様のことがいえます。

確認してみてはいかがでしょうか。


【Android Studio】メニューバーをAndroidStudioに移動する

Mac OSのメニューバーにメニューが表示されて、他アプリのアイコンが表示できなくなって困ることがあります。

Android Studio内に移動しましょう。

[Help] - [Edit Custom Properties...]


# custom Android Studio properties

# メニューを Android Studio ウィンドウに表示
apple.laf.useScreenMenuBar=false

動画内1分23秒付近で説明されています。

ファイルの実態は以下のようです。

~/Library/Preferences/AndroidStudio3.0/idea.properties


【Android Studio】ビルド環境を安定した最新バージョンにする

様々なものに依存したAndroidアプリ開発環境は最新なものにしたいものです。

「開発環境」と一言で言ったりしますが、Android Studio は様々な「バージョン」で構成されています。

今回は、Android Studio上で、その中でもいわゆる「ビルド環境」的な部分について設定してみます。

 

Android Studio Gradle Plugin

Android Studio を標準的に、当たりまえに使うにはGradleを利用するこのプラグインが必須です。

先述のGradleのバージョンはこのプラグインに強く依存しています、

Android Plugin for Gradle Release Notes | Android Studio

よって、Gradle のバージョンアップの際にはここを確認するとよいです。

パッケージ名が「com.android.tools.build:gradle」となり、現在はGoogleのリポジトリで管理されています。

Google's Maven Repository

設定変更は、Project/build.gradle です。


buildscript {

    repositories {
        google()
    }

    dependencies {  
        classpath 'com.android.tools.build:gradle:3.0.1'
    }
}

 

Gradle

Android Studioで使う際、推奨されているのは Gradle Wrapper なスタイルです。

Gradle Distributions

以下ファイルで最新版の設定をしましょう。

gradle/wrapper/gradle-wrapper-properties


distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip

 

Kotlin Gradle Plugin / Kotlin

もうKotlinでいいですよね。

StackOverFlowでも時代遅れなコードばかりで段階的なバージョン各過程を含めたコードばかりでその確認に四苦八苦して時間を費やすことになりますがKotlinなキーワードで検索すればその手間を避けることができます。

最近は公式系ドキュメントでもKotlinが優先されており、Kotlin化はさらに加速していくのは間違いありません。

Saving Files | Android Developers

KotlinをGradleで利用するには、このプラグインを利用することになります。

Android Studio – Migration from Maven Central to JCenter | Blog @Bintray

java - Android buildscript repositories: jcenter VS mavencentral - Stack Overflow

最新版の確認は、jetbrainsが管理しているMVNRepositoryで確認することができます。


Maven Repository: org.jetbrains.kotlin » kotlin-gradle-plugin

設定変更は同じ、Project/build.gradle です。


buildscript {
    ext.kotlin_version = '1.2.20'

    repositories {
        jcenter()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

 

まとめ

以上をまとめておきます。

Project/build.gradle


buildscript {

    // kotlin / kotlin gradle plugin version
    ext.kotlin_version = '1.2.20'

    repositories {
        google()
        jcenter()
    }

    dependencies {

      // android studio gradle plugin
      // https://dl.google.com/dl/android/maven2/index.html
      classpath 'com.android.tools.build:gradle:3.0.1'

      // kotlin / kotlin gradle plugin
      // https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-gradle-plugin
      classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

gradle/wrapper/gradle-wrapper-properties


distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

# https://services.gradle.org/distributions/
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip

オープンソースを利用したプラットフォームや開発環境のバージョンの調整は

「stable最新」

にしておくことが幸せになることの王道です。

関連各所の利益や手間を考慮したしがらみが悩みのタネですよね。

Gradle Dependency バージョンのチェックを「最新」に更新する方法


GoPro をライブカメラにしてターミナルから操作する

👉 【無料】使わなくなったスマホをライブカメラにしておじいちゃんを監視したい 

GoProはパソコンとWiFi接続で接続できますよね。

このとき、
10.5.5.9 をルータとしたローカルネットワークとなり、
パソコン側は、10.5.5.10? のアドレスが割り振られます。

非SSL通信です。

録画を開始・停止する

今、このページを見ているパソコンをGoProにWiFi接続すると

以下のただのリンクをタップして操作できます。

まず、ストリーミングを(再)起動して、

[→ (再)起動]


http://10.5.5.9/gp/gpControl/execute?p1=gpStream&a1=proto_v2&c1=restart

録画開始して、

[→ 録画開始]


http://10.5.5.9/gp/gpControl/command/shutter?p=1

そして、録画停止。

[→ 録画停止]


http://10.5.5.9/gp/gpControl/command/shutter?p=0

これだけで、録画開始・停止はできるのですが、ライブ画像を見たいところです。

ライブ画像を見る

ffplay (ffmpeg) で見ることができます。


ffplay -loglevel panic -an -fflags nobuffer -f:v mpegts -probesize 8192 udp://:8554

実際には、ストリーミング接続を保持させる処理が必要となります。


#!/usr/bin/env python3

import sys
import socket
from urllib.request import urlopen
import subprocess
from time import sleep
import datetime
import signal
import http
import multiprocessing

# GPH 6/0.160
GOPRO_IP = "10.5.5.9"
UDP_PORT = 8554
KEEP_ALIVE = 2.5
MESSAGE = "_GPHD_:0:0:2:0.000000"

URL_PREFIX       = f"http://{GOPRO_IP}/gp/gpControl"
URL_STREAM       = f"{URL_PREFIX}/execute?p1=gpStream&a1=proto_v2&c1=restart"
URL_RECORD_START = f"{URL_PREFIX}/command/shutter?p=1"
URL_RECORD_STOP  = f"{URL_PREFIX}/command/shutter?p=0"

CMD_VIEW = f"ffplay -loglevel panic -fflags nobuffer -f:v mpegts -probesize 8192 udp://:{UDP_PORT}"

def live():

    urlopen(URL_STREAM).read()
    subprocess.Popen(CMD_VIEW, shell=True)
    multiprocessing.Process(target=keep_alive).start()

    print("Press Ctl+C to quit.")
    print("Press ENTER to start/stop recording.")

    on = False
    while True:
        input()
        on = not on
        record(on)

def keep_alive():

    while True:
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.sendto(bytes(MESSAGE, "utf-8"), (GOPRO_IP, UDP_PORT))
        sleep(KEEP_ALIVE)

def record(on):

    urlopen(URL_RECORD_START if on else URL_RECORD_STOP).read()
    print(f"Record {str(on)} {datetime.datetime.today()}")


def quit_gopro(signal, frame):

    sys.exit(0)

if __name__ == '__main__':

    signal.signal(signal.SIGINT, quit_gopro)
    live()

パソコン・スマホ共にアプリが公式に公開はされていますが、サクッと見たいですよね。

いろんな使い方ができそうです。

👉 【無料】使わなくなったスマホをライブカメラにしておじいちゃんを監視したい 
Live Hero5 Session with WiFi - GOPRO SUPPORT HUB