ひらい ぶらり Hi-Library

ぷろぐらみんぐについて。ときどきどうでもいいことについて。

Scootman S8 と V6 レビュー

Scootman V6 を愛用して1年とちょい位通勤に使っていたのだけれども、いい感じにトラックにドツかれて公道はしれなくなったので修理に出すことに。 結果、修理に出すまでの間に充電を怠っていたのが災いして、バッテリーがおしゃかに。その他の修理費用を合計するといい感じにお値段になってしまった。 そんな中、修理を依頼してScootmanJapanは諸々コミコミで値段を抑えたS8のオファーを出してくれたのでそれに乗っかることに。 バッテリーが空輸できないので船便になったり、クリスマスや年末年始で関税で止まったり、運送業者と連絡がつかず通関後のステータスが不明になりましたがようやく届きました。

AK-1がな!

AK-1とは、ScootmanのOEM元のマシンで、CHANSONという会社が製造している。 それのエンブレムとステッカーを差し替えて日本向きにはScootman S8として売ってるわけなんですが おもくそ CHANSON AK-1 ってエンブレムのままでした。

まぁ、走行には影響ないのでいいんですけど。 バッテリーが不良なのか、最高速度が20km/hしかでてないのは困ってます。 リチウムイオンバッテリーは活性化させる必要がないし、ギア(単純な速度制限機能?)による変速も一定の割合で減速してるので、なんかどこかに抵抗が挟まてってる感じ。 最高速度30km/hの原付きにスピードリミッターが付いてる気もしないし、原因はなんじゃろ・・・。 追記:スピードリミッターがついていたので、ニッパーで切ったら30km/h弱でるようになりました。ただ30km/hはでないんだよなぁ・・・( ˘ω˘)

そんなわけで、V6とS8両方に乗ってみて、せっかくなのでそれぞれ所感をば。

V6

タイヤ:S8に比べると太め。安定感あり。 ハンドル:折りたたみ時に真横にするため、90度まで曲がる。狭い場所だとこのありえない角度のハンドリングが便利だったりする。 足置き:かなり後方にあるので、ちょっと変な角度で乗ることになる 折りたたみ:手動。自立するけど、ちょっとぶつかったり強風で倒れかねないレベルの安定感。 充電:取り外し不可。折りたたみ時充電不可。 全長:S8よりは短い。 ミラー:しょぼい。調整し辛い。ネジで止まっているため、転倒でもしようものなら1撃でネジ穴が馬鹿になる。 ナンバープレート:よく緩む。一回走行中に外れたことに気づかずナンバーをなくした。 見た目:不思議 鍵:物理+リモコン

という感じで、不満点はいろいろあったけど便利は便利でした。

S8

タイヤ:細くなった。怖い。 ハンドル:45度くらいまで?90度曲がらなくなった。普通になったといえば普通になった。 足置き:いい感じの場所にある。 折りたたみ:自動。安定感あり。ただし、うるさいし遅い。当然バッテリーが装着されてないと折り畳めない。そして折りたたむとバッテリーは外せない。手動でよかったんだけどなぁ。 充電:取り外し可。ただし、フックの部分がしょぼいのでそのうち折れそう。折りたたみ中は充電も取り外しもできない。不便。 全長:V6より少し長くなった。ハンドル幅も長くなったので、運転中はいいけどしまうとき邪魔。 ミラー:いい感じ。調整しやすい。 ナンバープレート:きちんとロックできるようになった。安心。 見た目:オサレ 鍵:リモコンのみ

とまぁ、改善された点もあったけど、そのままでよかったのに・・・という点も変わってしまった感じ。 目下最高速度が20km/hというのが不具合なのかどうかが一番の問題。 自転車に抜かれる悲しさ。 バッテリー外せる!これで置き場所が選べる! と思ったけど、これが電動自電車のように気軽に外せる感じではなく、結構慎重かつ力を込める必要があるので結構だるい。 あとほんとタイヤ細い。怖い。 なにより、物理キーが無くなってしまったので、電源を切る度に「ピュイピュイ!」ってけたたましい音がなる。 近所迷惑なので勘弁してほしい。

設定ボタンとギア変更ボタンを同時に長押しすると謎のモードに入って項目が20個位変更できたけど、マニュアルがないと何の項目なのかわからない。 いじりたい気持ちでいっぱいだけど壊すのもあれなので現在検討中。

3週間で届く、と言われたものが2ヶ月近くかかったり、ScootmanじゃなくてAK-1だったり、関税のことが後出しだったりと 丁寧ではあったけど対応がガバガバだった。 おかしな点を聞けばきちんと対応策を教えてくれるけど最初からやっておいてほしかった感はある。 市場の価格よりは遥かに安いオファーだったので仕方ないといえば仕方ない。

おわり

Illustraorで出力したSVGがTCPDFでスタイルが正しく反映されない

おはよーこんちわーこんばんわーおやすみーおきてー! しんばしです。

結論:出力時に「スタイル属性」を指定して、インラインでスタイル属性を埋め込めばよさそう

以下経過

タイトルの通り、Illustraorで出力されたSVGをTCPDFでPDFに埋め込んだところ 「画像が潰れて、全部真っ黒になっちゃうんだけど」 とのこと。 調べてみたトコロ、パスは正しく出力されているので、どうもスタイルが反映されずに白色の指定が無視されてしまって真っ黒になっている模様。

手持ちの「Graphics」で貰ったSVGを再出力したところ正しくPDFに埋め込まれたので、2つのSVGを比較したところ

  • 失敗する方のSVG
    • style要素でcssが指定され、classで参照していた
  • 成功する方のSVG
    • 各要素にインラインでcssが指定されていた

という違いを発見。どうもTCPDFはclassの参照がうまくできていないっぽい

そんなわけで、Illustraorでどんな設定にしているのか見てみた。 スタイルの項目をみたところ「スタイル要素」になっていて、ここを「スタイル属性」に変えるとインラインで出力される模様。

以上。とてもニッチな内容でした。

AWS SDK for PHP 3.x のS3のCopyObjectで署名エラー

getObjectとかlistObjectは特に問題なく動作しているのに、CopyObjectの時だけ署名エラーがでる。

SignatureDoesNotMatch

はい。 色々検証してみた結果、CopySourceの項目に日本語が使われているとエラーになるようだ。 Keyには使用しても問題ない。

仕方ないので以下のようにしてエラーを回避することにした。

<?php
$info = pathinfo($content['Key']); // listObjectとかで取ってきたデータだと思いねぇ

$s3client->copyObject([
    'Bucket' => $bucket,
    'Key' => $key,
    'CopySource' => $bucket . '/' . $info['dirname'] . '/' . rawurlencode($info['filename']) . '.' . $info['extension'],
]);

はい。日本語が使われているファイル名の部分だけURLエンコードしておけばとりあえず大丈夫。

IAMとかでS3のarnを指定するときも同じようにURLエンコードすればイケるかと思ったけどダメだった。 日本語を使わない方が健全な気もするけど、そうも行かないのが悲しいところですよね。 終わり。

AndroidTestCaseでsetUpが呼ばれた後にApplicationクラスが生成されてハマった

解決策:InstrumentationTestCaseにして、setUpメソッドsuper.setUp した後にgetInstrumentation.waitForIdleSync() を呼ぶべし

期待していた動き

- Appicationクラスが生成される
- TestクラスのsetUpメソッドが呼ばれる
- Testクラスのtestメソッドが呼ばれる
- TestクラスのtearDownメソッドが呼ばれる
- TestクラスのsetUpメソッドが呼ばれる
- Testクラスのtestメソッドが呼ばれる
- TestクラスのtearDownメソッドが呼ばれる

実際の動き

- TestクラスのsetUpメソッドが呼ばれる
- Appicationクラスが生成される <------
- Testクラスのtestメソッドが呼ばれる
- TestクラスのtearDownメソッドが呼ばれる
- TestクラスのsetUpメソッドが呼ばれる
- Testクラスのtestメソッドが呼ばれる
- TestクラスのtearDownメソッドが呼ばれる

何が困ったかと言うと、Singletonなクラスの初期化処理をsetUpメソッド内で行っているわけだけれども Applicationクラスの中でも初期化処理をしている。その為テスト用の初期化処理が、通常のアプリケーション で使う初期化処理に上書きされてしまい、テストがコケた。

d.hatena.ne.jp

Thread.sleep で対処したという話もあったけど、setUpの度にスリープされるのは嫌だし、sleepしたところで 環境に依ってはsleepが足りなくて結局コケたりする(実際ローカルではうまく動いたけど、Travis CI上ではコケた)

stackoverflow.com

どうやら InstrumentationTestCase で取得できる Instrumentation には waitForIdleSync なるメソッドがあるらしい。

Instrumentation | Android Developers

Synchronously wait for the application to be idle. Can not be called from the main application thread -- use start() to execute instrumentation in its own thread.

なるほど、わからん。 英語読めないなりに呼んでみると、「アプリケーションと同期するために待つよ。だからメインスレッドじゃ呼べないよ(メインスレッドを待ってるわけだから)。startメソッドを実行したらInstrumentationが実行されるよ」 ということだろうか。わからん。特に後半わからん。 本来の使い方はtestメソッド内で、runOnUiThread の実行が完了するのを待つために使っているので、多分looperが空になるのを待つっていう認識で良い・・・ハズ。多分。

実際に試してみたところ、setUpメソッド内で superを呼んだ直後にwaitForIdleSyncを実行するように書いたところapplicationクラスが生成されて一通りの処理が終わってから処理が再開された。 ローカルでテストして見たところ、期待通りの動作をしてくれたので、TraviceCI上でも試してみたところうまくどうさしているっぽい。

Singletonなんかにしているからテストで苦労するんだ、なんて言われそうな気もするけどSingletonはSingletonで便利な場面もあるので実に悩ましい話でした まる

FragmentStatePagerAdapterを使えと言ったな、あれは嘘だ

第二の結論

  • データの更新をしようなんて考えない。FragmentStatePagerAdapterごと作りなおす。

注意

  • FragmentPagerAdapterではAdapter作りなおしても意味がない

チラ裏

必ずしもFragmentStatePagerAdapterを使えば、notifyDataSetChangedで期待通りに変わるかと言われればそうじゃない場合も全然あるということに気付いた。

パターンが多すぎて具体的に書けないので、あと眠いので細かい説明は端折るがAdapter内で生成されるFragmentには必ずsetInitialSavedStateでsavedStateが渡されるため、データが更新されても必ず1回は更新される前のsavedStateが渡される。

つまり、Fragmentがまともに実装されていれば一度attacheされたFragmentは一回だけ更新前のデータで表示されてしまうということになる。

なので、データだけ入れ替えてnotifyDataSetChangedしても、savedStateが残っている限り完全に更新はされない。savedStateを消すメソッドもないので、Adapterごと作りなおしてViewPagerにセットし直すのが良さそう。notifyDataSetChangedなんてなかった。っていうかnotifyDataSetChanged送ったらAdapterのメンバ変数全部クリアしてほしい。

余談だけど、FragmentPagerAdapterを作り直すじゃダメなの?と思うかもですが、FragmentPagerAdapterはfragmentManagerにFragmentを保持させているのでAdapterを作りなおしてもfragmentManagerがfragmentを保持している為作りなおされない。 FragmentStatePagerAdapterはメンバ変数にFragmentのリストを保持しているのでAdapterを作りなおせば消える。

ん?てことは新しいAdapterを作った時に渡すデータのリストが、前のデータリストよりも短くなった場合かつ、Adapterによって削除されていない場合はFragmentはManagerに残り続けることになるのか・・・。きちんと消すならdestoryAllItem的なメソッドを実装して、adapterをセットし直す前に消すのが行儀良さそう。おわり。

FragmentPagerAdapterでnotifyDataSetChangedを呼んでも更新されない

結論

FragmentStatePagerAdapterを使え、でも問題はたくさんあるから油断をしてはいけない

※追記あり

FragmentStatePagerAdapterを使えと言ったな、あれは嘘だ - ひらい ぶらり Hi-Library

解決する内容

FragmentStatePagerAdapterは、destroyItemでfragmentをremoveしているので、notifyDataSetChangedを呼べば新しくgetItemで呼ばれたデータを元にFragmentが生成される。

解決しない内容

ただしsavedStateは残り続ける。以下のような場合カオスなことになる。

  1. Fragment内部にEditViewがあり、1ページ目に『ほげ』と入力された。
  2. notyfyDataSetChanged()
  3. Fragmentが再生成される。ただしsavedStateを渡して再生成されるので、Fragment内部に1と同じIDを持つEditViewがあれば『ほげ』が入力された状態で生成される

書いてて思ったけど、Fragmentの挙動としては正しい・・・のか? ともかく、気にしていないとわけも分からず想定しない動きをするのでFragment周りはソースをよく見ておいた方がいいということだけは肝に銘じておこうと思った。 viewのIDを変えれば回避できることだし、fragment的に考えたら再生成されたら復元されるのだろう。(大体余計なお世話であることのほうが多いけど)

結論その2

保持するアイテム数が少なく、絶対に更新されない場合→FragmentPagerAdapterを使う それ以外→FragmentStatePagerAdapterを使う

メモリを圧迫せず、データも更新されないならばFragmentPagerAdapterを使ったほうが、動作は早くなるハズ。でも大体FragmentStatePagerAdapterを使うことになると思う。

ここからチラシ裏

問題を解決するために色々見てたら以下の記事を見つけた。

outofmem.hatenablog.com

この記事はFragmentAdapterの使い方と言うよりは、FragmentPagerAdapter is クソみたいな話をします。

全面的に同意で、思ったことは自前でmakeFragmentNameメソッドを作らずにinstantiateItemメソッドを使えばいいんじゃないかなぐらい。書かれた時期が1年以上前なので何を今更感はあるかも。

あと、会社のパイセンと話していたらsupport libraryがバージョンアップして新たに getItemIdメソッドが実装されていることを教えてもらった。(4.1.2_r1から追加されてた) getItemIdでタグの生成をviewPager.getId() + position から、getItemで取れるなにがしかのIDをに変えてやれば問題は解決した。でもFragmentはマネージャーによって保持され続けるので、よっぽどのことがなければFragmentStatePagerAdapterを使うほうがよさそう。

Fragment周りはしょっちゅうトラブルが起きて、理解が浅いんだなーと思って調べれば調べるほどキモい気もするし、もしかしてこういうこと?あれ?ひょっとしてFragmentってちゃんと使えばすごく便利なん・・・そんなことねーやの繰り返しなきもしますが、これからも頑張ってFragmentと付き合っていこうと思います。

特定の辺だけにstrokeを付ける場合の補足

qiita.com

こちらの記事で丁寧に書かれて居るのだけれども、Javaで書いた場合とXMLで書いた場合に微妙な差異があって、機種によってはJavaで書いた場合に意図しない挙動を起こす可能性があるので補足。

元記事

private int BORDER_WEIGHT = 2;

/* 中略 */

// 白(半透明),太さ2pxのボーダーをつける
GradientDrawable borderDrawable = new GradientDrawable();
borderDrawable.setStroke(BORDER_WEIGHT, 0x55ffffff);

// LayerDrawableにボーダーを付けたDrawableをセット
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{borderDrawable});
// ボーダーがいらない辺にオフセット(負値)をつける
layerDrawable.setLayerInset(0, 0, -BORDER_WEIGHT, 0, -BORDER_WEIGHT);

// ボーダーを付けたいViewにセットする
view.setBackground(layerDrawable);

修正後

private int BORDER_WEIGHT = 2;

/* 中略 */

// 白(半透明),太さ2pxのボーダーをつける
GradientDrawable borderDrawable = new GradientDrawable();
borderDrawable.setStroke(BORDER_WEIGHT, 0x55ffffff);
// ボーダーの背景を透明にする 
borderDrawable.setStroke(0x00000000);

// LayerDrawableにボーダーを付けたDrawableをセット
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{borderDrawable});
// ボーダーがいらない辺にオフセット(負値)をつける
layerDrawable.setLayerInset(0, 0, -BORDER_WEIGHT, 0, -BORDER_WEIGHT);
// ボーダーを付けたいViewにセットする
view.setBackground(layerDrawable);

元記事のXMLの方ではsolidで透明を指定していたけど、Javaの方にはなかったのでこのままだと京セラの端末とかで背景が真っ黒になる為、律儀に透明を指定してやる必要がある。指定しないの端末が勝手に設定してくださるので真っ黒と書いたけど、そこは機種依存になるので注意。