【Android】Toolbarの矢印をタップしたら画面を閉じる
ポイントはonOptionsItemSelected()
でandroid.R.id.home
をハンドリングしないと反応してくれない点
class MainActivity: AppCompatActivity { ... override fun onCreateOptionsMenu(menu: Menu): Boolean { // Inflate menu resource file. menuInflater.inflate(R.menu.main, menu) return true } override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { // For arrow button on toolbar android.R.id.home -> finish() } return super.onOptionsItemSelected(item) } override fun initToolbar() { val toolbar = findViewById(R.id.toolbar) as Toolbar setSupportActionBar(toolbar) val actionBar = supportActionBar if (actionBar != null) { // Show back arrow icon actionBar.setDisplayHomeAsUpEnabled(true) actionBar.setDisplayShowHomeEnabled(true) actionBar.setTitle(R.string.app_name) } } }
RxJavaのflatMap(mapper, combine)でリストデータをそれぞれ別スレッドで非同期処理する
例えばRSSのリストがあり、それを別々のスレッドで処理して全部の処理が終わったらUIを更新したいとする。
RxJavaのflatMap(mapper, combine)
を使えばリストのデータを1つ1つのObservable/Flowable
に変換して処理できる。
まず第一引数のmapper
部分でそれぞれのデータを受け取ってObservable/Flowable
を作る。
このときsubscribeOn(Schedulers.io())
などでスレッドを分けておかないと全て同じワーカースレッドで実行されてしまうので注意。
ArrayList<RSS> rssList = ... Flowable.fromIterable(rssList) .subscribeOn(Schedulers.io()) .flatMap(new Function<RSS, Publisher<? extends RSS>>() { @Override public Publisher<? extends RSS> apply(RSS rss) throws Exception { return Flowable.just(rss).subscribeOn(Schedulers.io()); } },
次に第二引数のcombine
部分で更新処理を行う
Flowable.fromIterable(rssList) .subscribeOn(Schedulers.io()) .flatMap(new Function<RSS, Publisher<? extends RSS>>() { ... }, new BiFunction<RSS, RSS, RSS>() { @Override public RSS apply(RSS rss, RSS rss2) throws Exception { Log.d("test", "BiFunction, Thread:" + Thread.currentThread().getName() + ", rss:" + rss2.title()); // RSSの更新処理 ... return rss2; } })
あとはUIスレッドで結果を受け取って処理をすればよい。
ArrayList<RSS> rssList = ... Flowable.fromIterable(rssList) .subscribeOn(Schedulers.io()) .flatMap(new Function<RSS, Publisher<? extends RSS>>() { ... }, new BiFunction<RSS, RSS, RSS>() { ... }) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<RSS>() { @Override public void onSubscribe(Subscription s) { s.request(rssList.size()); } @Override public void onNext(RSS rss) { // 進捗更新 } @Override public void onError(Throwable t) { } @Override public void onComplete() { // 終了処理 } });
全体のコード
Java(ラムダなし)
ArrayList<RSS> rssList = ... Flowable.fromIterable(rssList) .subscribeOn(Schedulers.io()) .filter(new Predicate<RSS>() { @Override public boolean test(RSS rss) throws Exception { return rss.id() > 0; } }) .flatMap(new Function<RSS, Publisher<? extends RSS>>() { @Override public Publisher<? extends RSS> apply(RSS rss) throws Exception { return Flowable.just(rss).subscribeOn(Schedulers.io()); } }, new BiFunction<RSS, RSS, RSS>() { @Override public RSS apply(RSS rss, RSS rss2) throws Exception { Log.d("test", "BiFunction, Thread:" + Thread.currentThread().getName() + ", rss:" + rss2.title()); // RSSの更新処理 ... return rss2; } }) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<RSS>() { @Override public void onSubscribe(Subscription s) { s.request(rssList.size()); } @Override public void onNext(RSS rss) { // 進捗更新 } @Override public void onError(Throwable t) { } @Override public void onComplete() { // 終了処理 } });
Java(ラムダあり)
ArrayList<RSS> rssList = ... Flowable.fromIterable(rssList) .subscribeOn(Schedulers.io()) .flatMap(rss -> Flowable.just(rss).subscribeOn(Schedulers.io()), (rss, rss2) -> { Log.d("test", "BiFunction, Thread:" + Thread.currentThread().getName() + ", rss:" + rss2.title()); // RSSの更新処理 ... return rss2; }) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<RSS>() { @Override public void onSubscribe(Subscription s) { s.request(rssList.size()); } @Override public void onNext(RSS rss) { // 進捗更新 } @Override public void onError(Throwable t) { } @Override public void onComplete() { // 終了処理 } });
Kotlin
Flowable.fromIterable<RSS>(rsss) .subscribeOn(Schedulers.io()) .filter { rss -> rss.id > 0 } .flatMap({ data -> Flowable.just(data).subscribeOn(Schedulers.io()) }) { _, rss2 -> Log.d("test", "BiFunction, Thread:" + Thread.currentThread().name + ", rss:" + rss2.title) // RSSの更新処理 ... rss2 } .observeOn(AndroidSchedulers.mainThread()) .subscribe(object: Subscriber<RSS> { override fun onSubscribe(s: Subscription?) { s!!.request(rssList.size.toLong()) } override fun onNext(t: RSS?) { // 進捗更新 } override fun onError(t: Throwable?) { } override fun onComplete() { // 終了処理 } })
Macに外付けキーボードをつなげるときの設定
自分用。
環境
- Mac OS 10.12.6
- Karabiner-Elements 11.6.0
- 日本語外付けキーボード
手順
- Karabiner-Elementsをインストールする。
- Complex Modifications -> Add rule -> Import more rules from Internet(open a web browser) -> For Japanese (日本語環境向けの設定) (rev3)をImport
- Complex Modifications -> Add rule -> コマンドキーを単体で押したときに、英数・かなキーを送信する。(左コマンドは英数、右コマンドはかな)をEnable
- escキーを押したときに、英数キーも送信する(vim用)をEnable
- Simple Modifications -> Target Device: から接続したキーボードを選択 -> Add item -> 好きなキー(PCキーボードのかなキーなど)をright_commandに設定(かなキーとなる)
kotlinのファイルをjacocoのコードカバレッジ対象に入れる
久し振りにjacocoの結果みたらあれ・・・なんか結果がおかしいなとなった。 よく見てみるとkotlinに変換したファイル達がコードカバレッジ対象から外れていた・・・
以下で対応できた
sourceDirectories
にsrc/main/kotlin
を入れる(全部src/main/java
にあるから必要ないかも)classDirectories
に$buildDir/tmp/kotlin-classes/<product flavor>Debug
を追加
build.gradle
def coverageSourceDirs = ['src/main/java', 'src/main/kotlin'] // A list of files which should be excluded from coverage report since they are generated and/or framework code def coverageExcludeFiles = [...] task jacocoTestReport(type: JacocoReport, dependsOn: ['testUiTestDebugUnitTest']) { group = "Reporting" description = "Generate Jacoco coverage reports after running tests." reports { xml.enabled true html.enabled true csv.enabled false xml.destination new File("${buildDir}/reports/jacoco/jacocoTestReport.xml") html.destination new File("${buildDir}/reports/jacoco/html") classDirectories = fileTree(dir: "${buildDir}/intermediates/classes/uiTest/debug", exclude: coverageExcludeFiles) + fileTree(dir: "$buildDir/tmp/kotlin-classes/uiTestDebug", excludes: coverageExcludeFiles) } sourceDirectories = files(coverageSourceDirs) executionData = files "${buildDir}/jacoco/testUiTestDebugUnitTest.exec" doLast { println "jacoco xml report has been generated to file://${buildDir}/reports/jacoco/jacocoTestReport.xml" println "jacoco html report has been generated to file://${reports.html.destination}/index.html" } }
参考
Nature Remoを買った
去年の11/5に注文して12/23に届いた。 13000円。 今在庫を見てみたら2~3日後配達になっていてかなり在庫が充実している・・・
(置き方がこれでいいかは疑問が残る・・・)
いわゆるスマートリモコンで赤外線リモコンで操作できる家電(エアコンとかテレビとか)をスマホやGoogle Homeなどから操作できるものである。
良かったところ
エアコンの登録が楽
他のスマートリモコンを使ったことはないのでNature Remoが特化している機能ではないかもしれない。Nature Remoのアプリを開いて1つのキーを登録するだけでエアコンの種類が判別され、全操作が自動的にできるようになった。
エアコンのオン・オフを細かく設定できる
これはNature Remoに特化している話ではない。 通常のエアコンのリモコンだと何時間後に切る or 何時間後につけるのどちらか1つしか設定できない。 Nature Remoの場合IFTTTと連携すればを下記のように細かく設定できる。
- 平日朝7時半に暖房19℃でつける
- 平日朝8時半に切る
- 平日夜6時に暖房21℃でつける
- 休日朝10時に暖房19℃でつける
- 毎日夜12時に切る
それに加えてNature RemoアプリにGeo Fencingの設定があるので、家を離れたらエアコンを切るようにしている。
悪かったところ
購入後に無線ルーターを買い替えたためSSIDが変わった。どうやらNature Remo側にSSIDを変更する機能がなく、初期化しか方法がなかったので設定が全部やり直しになってしまった。
おわりに
買ってからしばらく経つが、かなり満足している。もっと早く買っておけばよかった・・・
java.lang.RuntimeException: com.android.builder.dexing.DexArchiveMergerException: Unable to merge dex
UIテストを実行しようとしたらエラー・・・
ひとまずRun with --stacktrace
を押してみる。
... Caused by: com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536 at com.android.dx.merge.DexMerger$8.updateIndex(DexMerger.java:565) at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:276) at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:574) at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:166) at com.android.dx.merge.DexMerger.merge(DexMerger.java:198) at com.android.builder.dexing.DexArchiveMergerCallable.call(DexArchiveMergerCallable.java:61) ... 1 more
これはいわゆる65536メソッド問題のようです。
UIテスト周りのbuild.gradle
を見直したところ以下は消せそうだったので消し、hamcrest-libraryのメソッドはorg.hamcrest.CoreMatchers
のメソッドに置き換えました。
testImplementation 'org.hamcrest:hamcrest-library:1.3' uiTestImplementation 'com.android.support:support-annotations:25.3.1' uiTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2'
これでメソッド数が減ってUIテストが実行できるようになりました(なんでExpressoのテストないのに入れてたんだろ)
Google App ScriptでWebスクレイピングしたら楽だった
とある商品の価格を監視したくてなんかいい方法ないかと考えていたところ、Google App Scriptでやったという記事を見たのを思い出してやってみた。
実装
スプレッドシートを作成→ツール→スクリプトエディタで作成開始。 今回はデバッグ用に実行ボタンを追加する関数と実行関数を書いた。 中身はWebサイトのHTMLを取ってきて正規表現で価格を取り出す。前回の結果をA1のセルに保存しておいて下がったら自分のGmailに送るというシンプルな実装。
function addExecuteToMenu() { var sheet = SpreadsheetApp.getActiveSpreadsheet(); var entries = [ { name : "Execute", functionName : "myFunction" } ]; sheet.addMenu("Execute script", entries); }; function myFunction () { // Change URL var html = UrlFetchApp.fetch('http:/xxxxxxxxxx').getContentText(); // Change for the site content you want to get var reg = /<div class="price">¥([\s\S]*?)<\/div>/i; var match = reg.exec(html); var price = match[1]; price = price.replace(",", "") var book = SpreadsheetApp.getActiveSpreadsheet(); // Get previous price from A1 in sheet1 var sheetData = book.getSheetByName("sheet1"); var prevPrice = sheetData.getRange(1, 1).getValue() if (prevPrice > price) { // Send Gmail var email = Session.getActiveUser().getEmail(); var subject = "Price down"; var body = 'Price: ' + prevPrice + ' -> ' + price; GmailApp.sendEmail(email, subject, body); } // Store current price to A1 in sheet1 sheetData.getRange(1, 1).setValue(price); }
定期実行
スクリプトエディタ上で↓のボタンを押し、
↓のように設定すれば完了
参考
Overview of Google Apps Script | Apps Script | Google Developers