2017年11月23日木曜日

KOIN 使ってみた

KOIN は Android 向けのシンプルな Dependency Injection フレームワークです。Kotlin の機能を使って DI を実現しています(proxy/CGLib なし、コード生成なし、introspection(リフレクションとかバイドコードいじりとか)なし)。

使い方

interface Heater interface Pump class ElectricHeater : Heater class Thermosiphon(private val heater: Heater) : Pump class CoffeeMaker(val heater:Heater, val pump:Pump)

1. dependency

implementation 'org.koin:koin-android:0.6.0' testImplementation 'org.koin:koin-test:0.6.0'

2. AndroidModule を継承したクラスを用意し、context() メソッドを実装する

この Context は Android の Context ではなくて org.koin.dsl.context.Context です。 Context の applicationContext() を使って構成します。 class DripCoffeeModule : AndroidModule() { override fun context(): Context { return applicationContext { provide { ElectricHeater() } bind Heater::class provide { Thermosiphon(get()) } bind Pump::class provide { CoffeeMaker(get(), get()) } } } } applicationContext() は name として Scope.ROOT を指定した Context を生成します。
fun applicationContext(init: Context.() -> Unit) = Context(Scope.ROOT, koinContext).apply(init) provide はデフォルトでは singleton になります。singleton にしない場合は isSingleton に false を指定するか、provideFactory を使います。 provide(isSingleton = false) { CoffeeMaker(get(), get()) } provideFactory { CoffeeMaker(get(), get()) } provide で name を指定することもできます。name を変えることで、同じ型を返す provide を複数定義できます。 provide("Coffee") { CoffeeMaker(get(), get()) } provide("Coffee2") { CoffeeMaker(get(), get()) } context() で sub context を作ることができます。Context には Scope 名を指定することができます。 class DripCoffeeModule : AndroidModule() { override fun context(): Context { return applicationContext { context("MainActivity") { provide { ElectricHeater() } bind Heater::class provide { Thermosiphon(get()) } bind Pump::class provide { CoffeeMaker(get(), get()) } } } } } provide するインスタンスを生成するときに Android の Context が必要な場合、androidApplication で Application インスタンスを取得することができます。 provide { ElectricHeater(androidApplication) } bind Heater::class

3. アプリケーションクラスの onCreate() で startKoin() を呼ぶ

class MyApplication : Application() { override fun onCreate() { super.onCreate() startKoin(this, listOf(DripCoffeeModule())) } } startKoin() は android.app.Application の拡張関数として定義されています。

4. inject

class MainActivity : Activity() { val maker by inject<CoffeeMaker>() } inject() inline fun <reified T> ComponentCallbacks.inject(name: String = "") = lazy { (StandAloneContext.koinContext as KoinContext).get<T>(name) } なので以下と同じ、つまり lazy です。 class MainActivity : Activity() { val maker by lazy { (StandAloneContext.koinContext as KoinContext).get<CoffeeMaker>() } } lazy が嫌なら onCreate() で (StandAloneContext.koinContext as KoinContext).get() を自分で呼んで代入すればできますが、全部自動で一括でとなるとやはりアノテーションなどが必要ですね。 class MainActivity : Activity() { private lateinit var maker: CoffeeMaker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) maker = (StandAloneContext.koinContext as KoinContext).get() } }

provide で name を指定した場合、inject にその name を指定して取得できます。 class MainActivity : Activity() { val maker by inject<CoffeeMaker>("Coffee") } Scope 名を指定してインスタンスを解放することができます。 override fun onDestroy() { super.onDestroy() releaseContext("MainActivity") } ContextAwareActivity を継承して Scope 名を指定すると同じことができます。ContextDropMethod を指定しないときのデフォルトは ContextDropMethod.onPause(onPause() で releaseContext() される)です。 class TestActivity : ContextAwareActivity("MainActivity", ContextDropMethod.OnDestroy) { val maker by inject<CoffeeMaker>() }


おまけ、Daggerの場合

kapt 'com.google.dagger:dagger-compiler:2.11' implementation 'com.google.dagger:dagger:2.11' interface Heater interface Pump class ElectricHeater : Heater class Thermosiphon @Inject constructor(private val heater: Heater) : Pump class CoffeeMaker @Inject constructor(val heater: Heater, val pump: Pump) @Module class DripCoffeeModule { @Provides fun provideHeater(): Heater { return ElectricHeater() } @Provides fun providePump(pump: Thermosiphon): Pump { return pump } } @Component(modules = arrayOf(DripCoffeeModule::class)) interface CoffeeShop { fun maker(): CoffeeMaker } val coffeeShop = DaggerCoffeeShop.builder() .dripCoffeeModule(DripCoffeeModule()) .build() val maker = coffeeShop.maker() assertThat(maker.heater).isNotNull() assertThat(maker.pump).isNotNull()


2017年11月19日日曜日

Android Things をやってみよう - InputDriver 編

User-Space Drivers : input

InputDriver を使うと、タッチイベントやキーイベントをシステムに流すことができます。

これにより、例えば GPIO から入力があったときに Space キーや Enter キーなどの KeyEvent を流すことで、KeyEvent を処理する機能やライブラリをそのまま流用できます。

InputDriver を使うときは com.google.android.things.permission.MANAGE_INPUT_DRIVERS パーミッションが必要です。 <uses-permission android:name="com.google.android.things.permission.MANAGE_INPUT_DRIVERS" />

InputDriver.Builder を使って InputDriver を生成します。

UserDriverManagerregisterInputDriver() で InputDriver を登録し、最後に unregisterInputDriver() で登録を解除します。

InputDriver にキーイベントを流すときは InputDrivder.emit() に KeyEvent を渡します。 class InputDriverActivity : Activity() { private var gpio: Gpio? = null private var inputDriver: InputDriver? = null private val callback: GpioCallback = object : GpioCallback() { override fun onGpioEdge(gpio: Gpio): Boolean { val action = if (gpio.value) KeyEvent.ACTION_DOWN else KeyEvent.ACTION_UP // ドライバーに Enter キーイベントを流す inputDriver?.emit(arrayOf(KeyEvent(action, KeyEvent.KEYCODE_ENTER))) return true } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val pioService = PeripheralManagerService() try { gpio = pioService.openGpio("GPIO_174").apply { setDirection(Gpio.DIRECTION_IN) setEdgeTriggerType(Gpio.EDGE_BOTH) setActiveType(Gpio.ACTIVE_LOW) registerGpioCallback(callback) } } catch (e: IOException) { Log.e(TAG, "Error initializing GPIO", e) } // Enter キーをサポートするドライバーを用意 inputDriver = InputDriver.Builder(InputDevice.SOURCE_CLASS_BUTTON) .setName("Button") .setVersion(1) .setKeys(intArrayOf(KeyEvent.KEYCODE_ENTER)) .build() // ドライバーを登録 UserDriverManager.getManager().registerInputDriver(inputDriver) } override fun onDestroy() { if (inputDriver != null) { // ドライバーの登録を解除 UserDriverManager.getManager().unregisterInputDriver(inputDriver) inputDriver = null } gpio?.unregisterGpioCallback(callback) try { gpio?.close() } catch (e: IOException) { Log.e(TAG, "Error closing GPIO", e) } super.onDestroy() } override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { Log.d(TAG, "onKeyDown : $keyCode") if (keyCode == KeyEvent.KEYCODE_ENTER) { return true } return super.onKeyDown(keyCode, event) } override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { Log.d(TAG, "onKeyUp : $keyCode") if (keyCode == KeyEvent.KEYCODE_ENTER) { return true } return super.onKeyUp(keyCode, event) } companion object { private const val TAG = "InputDriverActivity" } }

contrib-drivers

https://developer.android.com/things/training/first-device/drivers.html#initialize_the_driver_library

contrib-drivers の button にある ButtonInputDriver を利用すると、GPIO の入力を簡単に KeyEvent に割り当てられます。 implementation 'com.google.android.things.contrib:driver-button:0.3' class ButtonInputDriverActivity : Activity() { private var inputDriver: ButtonInputDriver? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) inputDriver = ButtonInputDriver("GPIO_174", Button.LogicState.PRESSED_WHEN_LOW, KeyEvent.KEYCODE_ENTER) } override fun onDestroy() { inputDriver?.unregister() try { inputDriver?.close() } catch (e: IOException) { Log.e(TAG, "Error closing GPIO", e) } super.onDestroy() } override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { Log.d(TAG, "onKeyDown : $keyCode") if (keyCode == KeyEvent.KEYCODE_ENTER) { return true } return super.onKeyDown(keyCode, event) } override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { Log.d(TAG, "onKeyUp : $keyCode") if (keyCode == KeyEvent.KEYCODE_ENTER) { return true } return super.onKeyUp(keyCode, event) } companion object { private const val TAG = "ButtonInputDriverActivity" } }

moshi で独自の Adapter は KotlinJsonAdapterFactory より先に add すべし

Retrofit と一緒にmoshi を使っています。
moshi には Kotlin Support 機能があり、別途 moshi-kotlin を追加して、 implementation 'com.squareup.moshi:moshi-kotlin:1.5.0' KotlinJsonAdapterFactory を MoshiBuilder に add します。 val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build()

また、moshi には Custom Type Adapters 機能があり、JSON の値とオブジェクトとの変換をカスタマイズすることができます。

この CustomAdapter および、CustomAdapter の Factory は KotlinJsonAdapterFactory より先に add しなければいけません (Kotlin Support のところに書いてあるのに気づかずはまってしまった...)。 Retrofit.Builder() .addConverterFactory(MoshiConverterFactory.create( Moshi.Builder() .add(MyCustomAdapter()) // KotlinJsonAdapterFactory より先なので OK .add(KotlinJsonAdapterFactory()) .build())) ... Adapter を生成するときに、追加された順番で Factory に Adapter が生成できるか問い合わせていくため、KotlinJsonAdapterFactory を先にすると CustomAdapter に来ないので気をつけましょう。


2017年11月12日日曜日

Android Things をやってみよう - Peripheral 操作編

Peripheral の操作は PeripheralManagerService を使って行います。

GPIO の名前の一覧は PeripheralManagerService.getGpioList() で取得できます。 val pioService = PeripheralManagerService() for (name in pioService.gpioList) { Log.d(TAG, "gpio : $name") } 実行結果 gpio : GPIO_10 gpio : GPIO_128 gpio : GPIO_172 gpio : GPIO_173 gpio : GPIO_174 gpio : GPIO_175 gpio : GPIO_32 gpio : GPIO_33 gpio : GPIO_34 gpio : GPIO_35 gpio : GPIO_37 gpio : GPIO_39

PeripheralManagerService.openGpio() で GPIO を開きます。戻り値は Gpio です。引数には getGpioList() で取得した名前を指定します。 val ledPin = pioService.openGpio(name)
GPIO は General-purpose input/output のことで、GPIO pin はソフトウェアからコントロールできる集積回路上の物理的ピンです。GPIO pin は入力として使って電圧値を読んだり、出力として使って電圧値を変えたりできます。

扱えるのは論理値(true / false)のみで、low value(ピンがグラウンドと同じ電圧値)と high value(ピンが IOREF と同じ電圧値)が論理値にマッピングされます。論理値と high/low のマッピング(true が high/low どちらに対応するか)は設定で変えることができます。

GPIO pin を開くということは、システム全体でこのピンに対するオーナシップを取るということです。これにより close() が呼ばれるまで他から GPIO を開いたりアクセスしたりするのを防ぎます。close() を呼ぶのを忘れると、同じプロセス・アプリに関係なくどこからも GPIO が使えなくなるので注意が必要です。


論理値と high/low のマッピングは Gpio.setActiveType() で指定します。
true に high を対応させるときは Gpio.ACTIVE_HIGH を、true に low を対応させるときは Gpio.ACTIVE_LOW を指定します。 ledPin.setActiveType(Gpio.ACTIVE_HIGH)
ピンの方向(入力 or 出力)は Gpio.setDirection() で指定します。
ピンを入力として使うときは Gpio.Gpio.DIRECTION_IN を指定します。出力として使うときは、初期状態を high にするときは Gpio.DIRECTION_OUT_INITIALLY_HIGH を初期状態を low にするときは Gpio.DIRECTION_OUT_INITIALLY_LOW を指定します。 ledPin.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW)
値が変わったときのトリガーのタイミングを Gpio.setEdgeTriggerType() で指定することができます。
ピンを出力として使うときはトリガーの必要がないので Gpio.EDGE_NONE を指定します。
ピンを入力として使う場合、立ち上がり時(low から high になったとき)だけトリガーさせるなら Gpio.EDGE_RISING を指定します。立ち下がり時(high から low になったとき)だけトリガーさせるなら Gpio.EDGE_FALLING を指定します。立ち上がり、立ち下がり両方トリガーさせるなら Gpio.EDGE_BOTH を指定します。 ledPin.setEdgeTriggerType(Gpio.EDGE_NONE)
現在の値を変更するときは Gpio.setValue() を使います。このメソッドはピンが出力として設定されているときだけ使うことができます。 ledPin.value = isChecked
現在の値を読むときは Gpio.getValue() を使います。このメソッドはピンが入力として設定されているときだけ使うことができます。 val value = ledPin.value
ピンの値が変わった時にコールバックを受け取るには Gpio.registerGpioCallback()GpioCallback を登録します。登録した GpioCallback は Gpio.unregisterGpioCallback() で登録解除します。 private val callback: GpioCallback = object : GpioCallback() { override fun onGpioEdge(gpio: Gpio?): Boolean { Log.d(TAG, "onGpioEdge: ${gpio?.name}, ${gpio?.value}") return true } override fun onGpioError(gpio: Gpio?, error: Int) { super.onGpioError(gpio, error) /** error : [android.system.OsConstants] */ Log.d(TAG, "onGpioError: ${gpio?.name}, $error") } } override fun onCreate(savedInstanceState: Bundle?) { ... ledPin.registerGpioCallback(callback) } override fun onDestroy() { super.onDestroy() try { ledPin.unregisterGpioCallback(callback) ledPin.close() } catch (e: IOException) { Log.e(TAG, "Error closing GPIO", e) } }

Android Things をやってみよう - プロジェクト作成編



form factors に Android Things を選ぶ









以下のような build.gradle が作成される apply plugin: 'com.android.application' ... android { ... } dependencies { ... compileOnly 'com.google.android.things:androidthings:+' }


2017年11月11日土曜日

Android Things をやってみよう - セットアップ編

https://developer.android.com/things/index.html

DroidCon SF で Android Things のハンズオンに参加し、Developer Kit をもらったのでいろいろやってみています。

事前準備

- Android Studio 3.0 をインストールする


- システム Image を用意する 1. Android Things Console に行ってサインイン(初回時に Terms of Service の同意が必要)する

2. Create product ボタンをクリックし、プロダクト名と詳細を入力し、対応する SOM type (もらった kit だと NXP Pico i.MX7D) を選択して Create をクリックする



3. 作成されたプロダクトを選択する



4. FACTORY IMAGES タブを選択する

5. Bundles で Empty Bundle を選択する



6. Android Things versions で最新のバージョンを選択し、Click Create Build Configuration をクリックする



7. Build が完了するまで待って、Build configuration list に追加されたアイテムをダウンロードし、解凍する




- Flashing the image 1. ボードと USB ケーブルで接続する (NXP i.MX7D なら USB-C cable が必要)

2. Android SDK の platform-tools/ にパスを通す

3. macOS が High Sierra (10.13) の場合、別の fastboot バイナリをダウンロードする

macOS が High Sierra (10.13) の場合、platform-tools/fastboot に既知の Issue があります。
https://issuetracker.google.com/issues/64292422
#comment8 に添付されているバイナリが使えます。

4. 以下のコマンドを実行してボードが Fastboot モードで起動していることを確認する $ fastboot devices Fastboot モードで起動していない場合は以下のコマンドでデバイスを再起動する $ adb reboot bootloader

5. ダウンロード&解凍した Image 内の flash-all.sh または flash-all.bat を実行する

6. 成功したら再起動するので、以下のコマンドで Android が動いていることを確認する $ adb devices

- WiFi に接続する $ adb shell am startservice -n com.google.wifisetup/.WifiSetupService -a WifiSetupService.Connect -e ssid [network_ssid] -e passphrase [network_pass]

サンプルで遊ぶ

https://developer.android.com/things/sdk/samples.html

ハンズオンでは をやりました。他にもサンプルたくさんあります。

ボードと USB で接続して、普通に Android Studio で開いて実行すれば OK です。別のものをインストールする前に今インストールされているものをアンインストールする必要があるので、忘れないように注意です


コードラボで学ぶ

https://codelabs.developers.google.com/io2017?cat=IoT


2017年11月6日月曜日

Kotlin for Android What's New メモ

Update on Kotlin for Android : Android Developers Blog

Support Library 27 から API に nullability annotations をつけはじめた。

Android Kotlin Guides を公開。Kotlin for Android の style と interop のためのガイド。
  • 1. Style guide - Kotlin for Android でコーディングするときの Google が推奨するルールと coding standards の詳細。命名規約、フォーマット、ソースの構成などについて言及している。
  • 2. Interop guide - Java および Kotlin で API を作成するときに、もう一方の言語で慣例的に利用できるようにするためのルールセットを提供している。
Android samples のいくつかを Kotlin に porting。official documentation に Kotlin を追加することも開始。

Android Kotlin page

Kotlin on Android FAQ

Resources to Learn Kotlin

2017年11月4日土曜日

KotlinConf 2017 に参加してきました。

KotlinConf 2017 | Kotlin Programming Conference in SF, USA

KotlinConf は Kotlin のカンファレンスです。今回が第1回目で、1200のチケットは完売したそうです。



1日目

キーノート前。人いっぱいです。



Coroutines



experimental は unstable ということではない。まだ API を変更する自由度を持っておきたいので experimental にしている。coroutines は production ready。Kotlin 1.3 で experimental が外れる予定。





Ktor

読み方はケイター、100% Kotlin の web framework
http://ktor.io/



Kotlin/JS





Android

Play Store で Kotlin を使っているアプリの数が 2.5倍に(いつからかは不明)



Android Studio Project の 17% が Kotlin を使っている(具体的な話は不明)



Android Studio
- Bundled Kotlin Plugin
- Kotlin Lint Support (in-IDE)
- Kotlin Templates for Projects and Activities



Android Support library 27.0.0 Kotlin Annotations



Android Kotlin Guides でました。
android.github.io/kotlin-guides/

Kotlin の Style Guide はこちら。
https://android.github.io/kotlin-guides/style.html

Java 呼ばれることを意識した Kotlin の書き方、Kotlin から呼ばれることを意識した Java の書き方はこちら。
https://android.github.io/kotlin-guides/interop.html



Android Kotlin Docs & Samples
https://developer.android.com/samples/index.html?language=kotlin



iOS

Kotlin で iOS きたー。





Multiplatform Projects

expect と actual 修飾子で共通の部分の Kotlin コードから .class と .js を生成。





Server, iOS, Android, Browser 全部 Kotlin!すごい世界だ。



Summary / Today

- Kotlin 1.2 RC available (keynote recap at blog.jetbrains.com)
- Multiplatform Projects (KotlinConf アプリが参考になるよ https://github.com/jetbrains/kotlinconf-app)
- Common modules
- expect/actual fun/class
- More in the blog post



Summary / Future

- Multiplatform libraries
- I/O, networking, serialization, dates...
- Kotlin/Native : compiler and IDE



Gradle Kotlin DSL

gradle の kotlin DSL よさげだった
GitHub - gradle/kotlin-dsl: Kotlin language support for Gradle build scripts
https://github.com/gradle/kotlin-dsl
↑まだ情報古そう

2日目キーノート

話は面白かった。あんまり Kotlin 関係なかった。





Kotlin/Native Demo

Swift の interop は in progress。現状は Objective-C の interop。CLion と XCode でデモしてた。





Kotlin Types

個人的にこのセッションが一番よかったです。おすすめ。





その他

Coding Conventions : the current coding style for the Kotlin language.

Udacity に Kotlin コースくるらしい



お昼ご飯おいしかった。でも初日のパーティのご飯はいまいちで、結局外に食べに行った。

おやつの Kotlin カップケーキかわいかった。すごく甘くてアメリカを感じた。



会場がちょっと寒いからかすぐコーヒーなくってしまってた。

1日目の最後、Party Keynote。なんかずっと手品してた。



1日目夜。中心部のビルの夜景がきれい。



2日目昼ごはん