- 作者: Robin Williams,米谷テツヤ,小原司,吉川典秀
- 出版社/メーカー: マイナビ出版
- 発売日: 2016/06/30
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (3件) を見る
Android Studio 3.5にあげた
CIではテストが通るんだけど、手元では通らなくなってしまったので困っていた。
2019-09-25 15:29:48:978 (KOIN)::[e] Error while resolving instance for class 'com.google.gson.Gson' - error: org.koin.error.BeanInstanceCreationException: Can't create definition for 'Single [name='Gson',class='com.google.gson.Gson']' due to error : 2019-09-25 15:29:48:978 (KOIN)::[i] [Close] Closing Koin context com.google.protobuf.Internal and com.google.protobuf.Internal$ProtobufList disagree on InnerClasses attribute java.lang.Class.getDeclaringClass0(Native Method) java.lang.Class.getDeclaringClass(Class.java:1235) org.koin.error.BeanInstanceCreationException: Can't create definition for 'Single [name='Gson',class='com.google.gson.Gson']' due to error : com.google.protobuf.Internal and com.google.protobuf.Internal$ProtobufList disagree on InnerClasses attribute java.lang.Class.getDeclaringClass0(Native Method) java.lang.Class.getDeclaringClass(Class.java:1235)
Android Studio 3.5にしたらApply Changesでめちゃくちゃ早くなったけどAndroid Studioでテスト動かしたらこけるようになって泣いてる
— ひさいち (@hisaichi5518) 2019年9月26日
外部ライブラリによって grpc-lite と protobuf-java 両方が読み込まれていて、同じパッケージに同じクラスがあるけど、インターフェイスが違うのでこけるようになっていたぽい
— ひさいち (@hisaichi5518) 2019年9月27日
Android Studio 3.4で動いてたのは, 3.5で読み込む順番が変わったとかそういうのかなぁ
— ひさいち (@hisaichi5518) 2019年9月27日
onPageFinished が実行されないときがある
Android の WebViewClient の onPageFinished が実行されないときがあるので困った。 onPageFinished は 2回実行されることもあるので、なかなかの困ったちゃんである。
onPageFinishedが実行されないときも onPageStarted は実行されるので、onPageStartedのときにすでに document.readyStateが loading
以外なら実行. loading
なら監視してStateが変わったら実行という形にした。
onPageFinished が実行されないときは、 readyState が complete
になってないようなので、WebViewが悪いというよりサイトが原因なんだろうと思う(しかし、すべてのサイトに修正を加える力は我々にはない………)
Moneytreeで未対応のTHEOを勝手に対応する
Moneytreeは現金とかタンス預金用に手動入力のみの「その他の口座」項目がある。
それは手動でしか入力できないんだけど、チョットプログラム書けば未対応のTHEOも自動で入力できるじゃんと思ったのでやってみた。puppeteer便利すぎ
流れ
- THEOにログイン
- THEOで表示されてる額を取得
- Moneytreeにログイン
- つくっておいた口座に額を追記
これをなんらかの方法で定期実行すればよい
実装
こういう感じ
import * as puppeteer from 'puppeteer'; import {Page} from "puppeteer"; class Theo { public static get username(): string { return ""; } public static get password(): string { return ""; } } class Moneytree { public static get username(): string { return ""; } public static get password(): string { return ""; } } class LoadTheoMarketPrice { async execute(page: Page): Promise<string> { await page.goto('https://app.theo.blue/account/login'); await page.type("input[name=email]", Theo.username); await page.type("input[name=password]", Theo.password); await page.tap("#app > section > section > form > div > button"); await page.waitForNavigation(); return await page.$eval("span.number-value", (span) => span.textContent.trim()); } } class SaveTheoMarketPrice { async execute(page: Page, price: string) { await page.goto('https://app.getmoneytree.com/login'); await page.type("input[name=email]", Moneytree.username); await page.type("input[name=password]", Moneytree.password); await page.tap("#app > div > div > div.onboardingPageBody > form > div > button"); await page.waitForNavigation(); // 口座残高を表示 await page.goto("https://app.getmoneytree.com/app/vault"); await page.waitFor(3000); // THEOの項目を表示/人によって違うよ await page.tap("div > ul > li:nth-child(4) > div.row.institution-header > a"); await page.tap("#mt-webapp > section > mt-webapp-layout > div > div.content.ng-scope > mt-accounts > div > mt-accounts-personal > div > mt-link-vault > div > mt-two-column-layout > div > div.column.left.sidebar > div:nth-child(2) > left-column-body > mt-credentials > div > ul > li:nth-child(4) > div:nth-child(2) > ul > li.ng-scope > div > mt-credential > div > mt-credential-template > div > div.credential-accounts > div.credential-body > mt-list-secondary > div > ul > li:nth-child(2)"); await page.waitFor(3000); // 編集ダイアログを表示 await page.tap("#mt-webapp > section > mt-webapp-layout > div > div.content.ng-scope > mt-accounts > div > mt-accounts-personal > div > mt-link-vault > div > mt-two-column-layout > div > div.column.center.content > mt-awesome-layout > div > div > div:nth-child(1) > awesome-body > div > right-column-body > div:nth-child(2) > div.ng-scope > mt-manual-account-balances > div > div.current-account-balance-header > span:nth-child(2) > a"); // 保存されてる内容を消す await page.$eval("#mt-webapp > section > mt-webapp-layout > div > div.content.ng-scope > mt-accounts > div > mt-accounts-personal > div > mt-link-vault > div > mt-two-column-layout > div > div.column.center.content > mt-awesome-layout > div > div > div:nth-child(1) > awesome-body > div > right-column-body > div:nth-child(2) > div.ng-scope > mt-manual-account-balances > div > div.current-account-balance-header > span:nth-child(2) > mt-manual-account-balances-action > div > div:nth-child(5) > mt-amount-input > div > div.pull-right > input", value => (value as HTMLInputElement).value = ""); // priceを入力 await page.type("#mt-webapp > section > mt-webapp-layout > div > div.content.ng-scope > mt-accounts > div > mt-accounts-personal > div > mt-link-vault > div > mt-two-column-layout > div > div.column.center.content > mt-awesome-layout > div > div > div:nth-child(1) > awesome-body > div > right-column-body > div:nth-child(2) > div.ng-scope > mt-manual-account-balances > div > div.current-account-balance-header > span:nth-child(2) > mt-manual-account-balances-action > div > div:nth-child(5) > mt-amount-input > div > div.pull-right > input", price); // + にする await page.tap("#mt-webapp > section > mt-webapp-layout > div > div.content.ng-scope > mt-accounts > div > mt-accounts-personal > div > mt-link-vault > div > mt-two-column-layout > div > div.column.center.content > mt-awesome-layout > div > div > div:nth-child(1) > awesome-body > div > right-column-body > div:nth-child(2) > div.ng-scope > mt-manual-account-balances > div > div.current-account-balance-header > span:nth-child(2) > mt-manual-account-balances-action > div > div:nth-child(5) > mt-amount-input > div > div.pull-left.ng-scope > div > i"); // 更新する await page.tap("#mt-webapp > section > mt-webapp-layout > div > div.content.ng-scope > mt-accounts > div > mt-accounts-personal > div > mt-link-vault > div > mt-two-column-layout > div > div.column.center.content > mt-awesome-layout > div > div > div:nth-child(1) > awesome-body > div > right-column-body > div:nth-child(2) > div.ng-scope > mt-manual-account-balances > div > div.current-account-balance-header > span:nth-child(2) > mt-manual-account-balances-action > div > div:nth-child(7) > a"); await page.waitFor(3000); } } exports.saveTheoMarketPrice = () => { (async () => { const browser = await puppeteer.launch(); const theo = await browser.newPage(); let price = await new LoadTheoMarketPrice().execute(theo); price = price.replace(",", ""); const moneytree = await browser.newPage(); await new SaveTheoMarketPrice().execute(moneytree, price); await browser.close(); })(); };
一応書いておくとこのコードによってなにか起きても責任は取りません。
プール行ってる
もともとはジムに行ってたんだけど、時間効率を考えると水泳だな…と思い始めて区のスポーツセンターに通い始めた。
最初は、仕事が終わってダラダラして21時頃に勢いで下の水着(正確には水陸両用パンツ)だけ持っていったら帽子が必要ですってプールサイドで言われて「入り口の注意事項に書いてなかったやん」とは思ったんだけど、どうせならもろもろ揃えるかと思って、2時間分の入場料払って5分でそのまま帰った(スポーツセンターの売店で買えるらしかったけど)
次の日、大きめのスポーツ用品店で度付きのゴーグルとしっかりした水着と帽子を買って二回目。 夜は人が少なくて目立つのが嫌だったので、人間がたくさんいそうな時間帯を狙った。16時くらい。 レーンへの入り方みたいなのもわかってなかったので、最初はプールを眺めて雰囲気を理解した。このとき、変質者と思われないか不安だった。
中学生くらいまではプール教室?に行ってたので泳ぐのは出来たんですけど、まともに泳ぐのは中学生ぶりだし今は泳げるか不安だったのでまずは水中で歩いた。 水の中での歩き方にも色々あると思うんだけど、何も知らないのでそれも雰囲気でやった。
歩くのは余裕すぎるな〜という感じだったので、初心者レーン25mにシュッと入って泳いだら案外泳げて楽しい。 こりゃあいいと思って、25mを何度か往復していたらまあ疲れる。疲れたら歩いて休憩。回復したらまた泳ぐを繰り返した。
そしたら、足と胸あたりの筋肉が悲鳴を上げ始めたのでジャグジーに入って帰宅した。筋肉痛になりたくないので筋膜リリースして終わり。 想像してたより楽しかったので続ける。ジムは解約する予定
Motomo フォームローラー ストレッチローラー 筋膜リリース マッサージ 腰痛・肩コリ・筋肉痛を改善 ヨガポール グリッド トリガーポイント エクササイズ(ブルー)
- 出版社/メーカー: Motomo
- メディア: その他
- この商品を含むブログを見る
アマニタ・パンセリナ を読んだ
- 作者: 中島らも
- 出版社/メーカー: 集英社
- 発売日: 1999/03/19
- メディア: 文庫
- 購入: 6人 クリック: 34回
- この商品を含むブログ (104件) を見る
中島らものアマニタ・パンセリナ読んだ。様々なドラッグの体験が書かれてあるのだけど、「右を見ても左を見てもラリ公だらけ」「死ぬくらいなら続けりゃいい」「自己放棄の快感」「三合くらいの晩酌は薬」などいい言葉がたくさんあった https://t.co/j5QilJ69l7
— ひさいち (@hisaichi5518) August 7, 2019
中島らも自身がドラッグに関する情報について、咳止めシロップの項目でこう書いてる。
問題は情報にはなくほとんどいつも中毒者の側にある。
つまり、なるべくして中毒になるような、そういう器質の一群があるのだ。情報として重要なのは、たとえばこの文章のような「正確な」情報だろう。
こうすればこうなりますよ、という臨床例に近いような情報だ。
かくすより「知らせる」ことの方が大事なのである。
grpc-java/protobuf-lite では JsonFormat がない
Unable to find JsonFormat class in Android · Issue #276 · google/protobuf-gradle-plugin · GitHub
Androidでprotobuf-liteを利用したまま、JSONをオブジェクトにする必要があったので、Gsonで頑張ることにした。InternalなAPIを呼ぶ必要があり困っている。ちなみに grpc/grpc-swiftは頑張る必要もなくシュッと出来ます。
- protoで定義されたフィールドは、生成されたクラスのメンバー変数では最後に
_
がつく- 例えば、
name
というフィールドを持ってるとすると生成されたクラスのメンバー変数ではname_
になる
- 例えば、
- protoで定義されたフィールドでリピートするものは、ただのListではなく
Internal.ProtobufList<T>
で表現されている- TypeAdapter がないとエラーになる
以上をふまえて、以下のようなコードを書いた。
package com.google.protobuf open class ProtobufListTypeAdapter<T>(val parser: (JsonElement) -> Collection<T>): TypeAdapter<Internal.ProtobufList<T>>() { override fun write(out: JsonWriter?, value: Internal.ProtobufList<T>?) { // 利用していないので実装していない } override fun read(jsonReader: JsonReader): Internal.ProtobufList<T> { val list = ProtobufArrayList<T>() list.addAll(parser(JsonParser().parse(jsonReader))) return list } } // 型ごとに作ることになるが、リフレクションで頑張ればどうにかできそう(試していない) class DeliveryTimeListTypeAdapter: ProtobufListTypeAdapter<DeliveryTimeOuterClass.DeliveryTime>({ it.asJsonArray.map { element -> DeliveryTimeOuterClass.DeliveryTime.newBuilder() .build() } })
ProtobufArrayList
が、com.google.protobuf内でしか利用できないので、package com.google.protobuf
にしている。
class ProtoExclusionStrategy: ExclusionStrategy { override fun shouldSkipClass(clazz: Class<*>?): Boolean { return false } override fun shouldSkipField(f: FieldAttributes?): Boolean { if (f == null) { return true } // proto で定義されたフィールド以外は、JSON化しない return !isProtoField(f.name) } fun isProtoField(name: String): Boolean { // filedに _ があると proto で定義されたフィールドとする return name.contains("_") } } val gson = GsonBuilder() .setFieldNamingStrategy { it.name.replace("_", "") } .setExclusionStrategies(ProtoExclusionStrategy()) .registerTypeAdapter( object : TypeToken<Internal.ProtobufList<DeliveryTimeOuterClass.DeliveryTime>>(){}.type, DeliveryTimeListTypeAdapter() ) .create() gson.fromJson(...)
もっといい方法があったら知りたい。