My Note Pad

エンジニアリングや日々の雑感を書いていきます

SPGアメックスカードを申し込んだ理由と手元に届くまで

先日SPGアメックスのカードを申し込み無事審査に通り手もとに届いたので、これからSPGアメックスのカードを発行しようとしている方の参考になればと思い、申込みしてから手元に届くまでの流れと日数を時系列で紹介していきます。

SPGアメックスとは

www.americanexpress.com

アメリカン・エキスプレスが発行する、Marriott Bonvoyとの提携カードです。

主な特徴は

  • Marriott Bonvoyゴールドエリートの資格が得られる
    • チェックアウト時間を14:00まで延長可能
    • 部屋が空いていればアップグレードされる可能性
    • ウェルカムギフトポイント
    • 対象レストランで15%OFF などなど
  • 毎年更新のタイミングでMarriott Bonvoyの無料宿泊券が貰える
  • カードを利用している限りはMarriott Bonvoyポイントの有効期限は実質なし
  • ポイントを40社もの航空会社のマイルに交換可能

www.marriott.co.jp

その代わり年会費は高く、税込み 34,100円となっています。

申し込んだ理由

  • 以前の記事でも触れましたが、最近は自分でもストレスが溜まっているなということで旅行なんかにいってリフレッシュしたい。
  • 今まではあまり旅行に行けていなかったので、毎年もらえる無料宿泊券を使って強制的に旅行に行ってリフレッシュする状態をつくる。
  • 高級ホテルに泊まってみたい ←興味。
  • 実質ポイント無期限なので、じっくりとマイルを貯められる。

申し込みから手元に届くまで

想像していたより時間がかかりました。

申し込み 3/3(火)

私の場合は普通にWebから申し込みをしました。
紹介してもらった場合などはさらにポイントボーナスもあるそうですが、知り合いにホルダーがいなかったので・・。
申し込みする際には、Marriott Bonvoy会員番号が必要となるため予めMarriott Bonvoyの登録をしておく必要がありました。

www.marriott.co.jp

早い人だと申し込み完了時点で審査結果が出るそうですが、私の場合は持ち越しでした。

入会特典についてのメール・アメックスから電話 3/4(水)

まずは入会特典についての紹介メールがアメックスより届きました。
また、同日にアメックスから電話が職場にかかってきました。
本当に職場にかかってくることもあるのか・・と思いつつ簡単な質問に答えて終了。

Marriott Bonvoyのステータスがゴールドに 3/7(土)

Marriott Bonvoyのステータスがゴールドに変化していました。
この地点で審査には通過したと思って良さそうです。

Welcomeメール 3/8(日)

アメックスより、Welcomeメールが届き審査に通ったことが通知されました。

本人限定受け取りの連絡 3/11(水)

Welcomeメールの翌日からソワソワしながら毎日ポストを確認していましたが、本人限定受け取りの通知が入っていたのは3/11(水)でした。
楽天カード等とは違い、カードとは別で通知が来るので郵便局に電話をし配達日を翌日にしてもらいました。
※通常の再配達はWebでの申込みが可能ですが、今回は電話もしくはFAXとなっていました。
この際、受け取りなどで身分証明書の提示が必要であることが知らされます。
また、受け取りが円滑に行われるように予め免許証の番号を電話口で伝えました。

受け取り 3/12(木)

受け取る際には、前日電話した際に伝えた免許証番号を参照され本人確認となります。
このあたりは普通のカードと違ってしっかりしているところですね。

申し込んだら忘れずにやること

アメックスは他のカードとは違い、手元にカードが届いてから暗証番号の登録などが必要でした。

  • アメックスオンラインの登録
  • 暗証番号の登録
  • 引き落とし口座の登録
  • 利用可能限度額の確認

今後

メインのカードをSPGアメックスに切り替えて色々なところに旅行に行けるよう、コツコツとポイントを貯めていきます。
次回更新でもらえる無料宿泊券への飛行機をマイルでまかなうようにできるよう頑張っていきます。

エンジニアの心を整える技術2を読んだ

先日Webで見かけた「エンジニアの心を整える技術2」という本が気になって読んでみたので、その感想です。

エンジニアの心を整える技術2とは

BOOTHというpixiv社が運用している、クリエイターが作品を自由に販売できるサイトで購入できる書籍です。

booth.pm

本来は技術書典8で販売される予定だったらしいですが、残念ながらコロナウィルスの影響で中止になってしまいました。
そのため、PDF版のみですがBOOTHにて購入できました。

techbookfest.org

なぜ購入しようと思ったのか

紹介文を読んでいて、以下の部分がまさに当てはまるなぁと思ったからです。

あなたは、いま、このような想いの中に、いるのかもしれません。

* プロジェクトの終わりの見えない忙しさ
* 残業続きで抜けない疲れにぐったり
* 最新技術やプログラミング言語習得の流行り廃りの虚しさ
* 職場でのやり場のない怒りや悲しみ
* 未来に対する漠然とした不安

自身に当てはめると以下のような感じでした。
少しでも状況や心の持ちようを改善できないかと思い、思わず手が出ました。

  • 業務負荷が上がってきており、週末では疲れが取れない
  • そこそこユーザーの増えてきたSaaSのサービスを主担当のエンジニアとしては自分1人だけなので心理的につらい
  • ロールが少し変わったため、自分の生産性を発揮できてはいない
  • 設計やコードに携わることが減ってきており、最新技術についていけなくなるおのではという不安

読んでみた感想

SHIROBAKOの名言が所々散りばめられていて良い

SHIROBAKOとは、アニメ制作現場をアニメ化したもので、2020年2月29日に劇場版も公開されています。
色々な部分で、「あるある」と共感できる内容になっており心に染みる名言も多い作品になっています。
もちろん劇場版も観ました!

shirobako-anime.com

まえがきの段階から、確信を突かれるようなSHIROBAKOのセリフが載っており一気に引き込まれました。

Slackとの付き合い方を見直すきっかけになった

3.6.4 Slack をミュート・退出せよ。心に「ゆとり」を取り戻せ

上記の章には、「はっ」とさせられました。
Slackは便利な反面、様々な情報が飛び交いメンションがついたものは「すぐに見なければ」という意識になってしまっていたなと思います。
この章を読んで、Slackとの付き合い方を少し見直したいなと考えさせられました。

自己肯定感について考えるきっかけになった

第4章 自己肯定感を整える技術

この章については丸々、グサりとくる内容でした。
とくに自己否定に関する部分は自分に当てはまる部分が多く、心の持ちようを変えたいと考えさせられます。

全体を通して

ページ数はそれほど多くないので、小一時間で読める内容になっていました。
そのため、一気に最後まで読み進められ、ビジネス書などにあるような寄り道もほぼないため、スッと内容が頭に入ってきました。
一部は実践的な内容も書かれていたので、試してみたいなと思います。

BOOTHにて購入できるので、気になった人は読んでみてください。

ETORENでGalaxy Note10+(SM-N9750)を購入したので注文から届くまで

今回、ETORENという海外のサイトで、Galaxy Note10+を購入しました。

届くまで意外に時間がかかったので他の方の参考になるかと思い、購入した背景と注文してから届くまでを書いていきます。

なぜGalaxy Note10+ を購入したのか

今まではPixel3を購入していました。
写真は綺麗に撮れるし、Android OSを開発しているGoogleフラグシップスマホということで、アップデートも早いので基本的には満足していました。
では何が買い替えの決め手になったかというと、以下の点になります。

バッテリーが致命的に持たない

それほどハードに使っているわけでもなく、Pixel3でゲームをするわけでもないのですが、充電は1日に朝出社してからと夜帰宅してからの2回必要でした。
寝る前に充電して、少し使ってから朝起きたら80%くらいになっていることもしばしば・・

たまにメモリが不足する

2018年のフラグシップとしては少ないメモリの4GBがPixel3に載っています。
しかし、利用するアプリによってはもっさりすることもありメモリが足りてないなーと感じることがしばしばありました。
SoCはSnapdragon 845が載っているので、メモリが足りている場合はサクサク動いてくれました。、

画面が小さい

軽くて持ち運びがしやすい反面、画面の小ささが気になるようになりました。
ここは好みの問題ですが、次は画面サイズの大きいものを買おうと前々から考えていました。

Galaxy Note10+について

www.galaxymobile.jp

上記の問題を全て解決してくれる上で、候補はある程度に絞れました。

  • Pixel4 XL
  • Galaxy Note 10+
  • Galaxy S20+ (Note10+ 購入時点では未発表)
  • iPhone 11 Pro Max

などです。
ただ、花粉症シーズンなどマスクを使う場合には顔認証は使い物にならないので、基本的には指紋認証に対応している機種でということになりました。
Galaxy S20シリーズの発表まで待つべきかとも考えましたが、リーク情報を見ている限りではNote 10+でもよさそうという結論に至り、Note 10+に決定しました。
2020年3月に5Gがスタートするということで、気がかりではありましたが5G対応のiPhoneが発売するまではあまり普及しないだろうと考え、今回の購入に至りました。

ETORENについて

jp.etoren.com

海外のスマートホンなどを扱うオンラインショップになります。
Expansysと並んで有名ではないでしょうか。
ETORENの特徴は、輸入時にかかる関税がインクルードされていることです。
そのため、サイト表記の価格 + 送料が実際に支払う総額になります。

また、日々価格が変動するのも海外サイトっぽいところです。
私が購入したときは、93,500円 + 送料でした。 f:id:yuki10k:20200209172029p:plain

購入から届くまで

ETORENは基本的にはページが日本語化されているため、通常のネットショッピングと同じ感覚で購入できると思います。
支払い方法はクレジットカードやPayPalなどが選べました。
また、住所は英語で入力するのですが、以下のサイトなどを使えば困ることはないです。

JuDress | 住所→Address変換

実際のオーダーになります。
日本のキャリアモデルより3万円くらい安いです。
技適なし / Felicaも搭載されていないためご購入を検討される場合は自己責任でお願いします。

f:id:yuki10k:20200209171827p:plain

実際に注文をしたのが2/2(日)で、発送されたのが2/6(木)でした。
発送予定日は 2/3(月) ~ 2/5(水)となっていたので、2/5(水)の夜になっても発送されず不安になったので問い合わせしたところ、2/6(木)に返信があり、当日中に発送されるということで実際にちゃんと発送されました。
このあたりは日本のネットショッピングに慣れているとソワソワする部分です。

実際に発送されてからの流れですが、DHLに引き渡されて、シンガポールから東京まで1晩のうちに届いていました。
DHLから佐川急便に引き渡されて、2/8(土)に無事到着しました。
日本の物流もすごいと思いますが、国際物流も洗練されているなと感じました。

こちらがDHLでの履歴になります。 f:id:yuki10k:20200209172811p:plain

到着したので開封

早速開封していきます。
ちゃんとしたダンボール箱で届きました。

f:id:yuki10k:20200209171022j:plain

中もプチプチでしっかりと梱包されています。

f:id:yuki10k:20200209171029j:plain

箱にはペンの絵が書かれており、非常にシンプルです。

f:id:yuki10k:20200209171041j:plain

箱を開けたところ。

f:id:yuki10k:20200209171052j:plain

蓋側には、SIMピンと付属の透明ケース。

f:id:yuki10k:20200209171105j:plain

本体の下には説明書

f:id:yuki10k:20200209171115j:plain

さらにイヤホン、ACアダプター、USB-Type Cケーブルなどが入っています。

f:id:yuki10k:20200209171121j:plain

本体は透明なシールで包まれていました。

f:id:yuki10k:20200209171124j:plain

オーラグローの背面は、見る角度で表情を変える、非常に美しい仕上がり。

f:id:yuki10k:20200209171127j:plain

最後に

スマホの買い替え後は移行が大変でした。。
まだ少ししか触れていませんが、上の方で上げたPixel3の不満は全て解消してくれる素晴らしい端末だと思います。
これから、初のSペンなど色々試していきたいと思います。

Air PodsProをGET!

ようやく念願のAir Pods Proを入手しました。
発売直後は割とすぐに入手できたそうですが、現在ではApple Storeの発送予定日はほぼ1ヶ月待ちという人気ぶり。
発売直後には3万円は高い!と思い見送っていましたが、

といった理由などにより購入に踏みきりました。

開封

というわけで早速開封していきます。

パッケージの表面はこんな感じ。
AppleのWebページに載っているものと同じように見えます。

裏面もシンプル。

蓋を開けるとお決まりの「Designed by Apple in California 」

その下にはバッテリーケース。

バッテリーケースの下にはつまみがあり

つまみを引き上げると中からLightning to USB-Type Cケーブルが出てきました。
なにげにLightning to USB-Type Cケーブルは嬉しいところ。

つまみの正体はイヤーチップでした。

バッテリーケースは横に長くなっています。

蓋を開けると、Air Pods Proがお目見えします。

Air Podsのケースと比較するとこんな感じ。

本体を比較するとこんな感じ。
やはりAir Pods Proの方が短くなっています。

使ってみた感想

着けた瞬間にノイズキャンセリングが効き、フッと静かになるので衝撃を受けました。
また、Air Pods Proの細長い部分を長押しするとノイズキャンセリングが解除され、現実に引き戻されます。
急に騒がしくなるので、普段は何とも思っていなかった音は実はかなり大きく、絶えず耳に入っていたんだと痛感します。
電車の中は特に、普段何とも思っていませんでしたがノイズキャンセルを解除した瞬間、騒音に包まれました。

音質については、正直よくわかりません・・

が、Pixel3でも問題無くノイズキャンセリングが利用でき、Air Podsと同様に複数デバイスでの接続切り替えもとてもスムーズにできるので非常に満足度の高い買い物だったと思います。

OPPO Reno Aを購入したので開封!

OPPO Reno A購入の背景

楽天モバイルの無料サポータープログラムに当選したためです。 無料サポータープログラムでは対象の機種を購入する必要があり、今回はOPPO Reno Aを購入したので開封していきます。

当選のメールが到着してから、申込みのメールがなかなかこなかったのでソワソワしていましたが、OPPO Reno Aの在庫があるタイミングだったのでそのまま購入しました。
※ブルーが良かったなと思いつつ、ブルーは在庫がなかったのでブラックを購入しています。

実際に注文してからは3日後に届きました。

開封

早速開封していきます。
ダンボールの中身は、OPPO Reno Aの箱と、SIMカード、スタートガイドでした。
写真だとわかりにくいですが、箱のサイズはかなり大きいです。

f:id:yuki10k:20191020214621j:plain

iPhone 11と比較してみると、かなり大きいことが分かります。

f:id:yuki10k:20191020214631j:plain

早速開封していきます。
蓋を開けた直後は、本体とはご対面できず・・

f:id:yuki10k:20191020214643j:plain

説明書の下に本体が入っていました。
ぱっとみこれは裏面か?と思いましたが、フロントパネル側でした。

f:id:yuki10k:20191020214654j:plain

さらにその下には透明なケースと、USB-Type Cのケーブル。
このケースは柔らかい素材のものでした。

f:id:yuki10k:20191020214706j:plain

ケースの下に充電器と、イヤホンが付属しています。
OPPO Reno Aには最近のスマホには珍しくイヤホンジャックがあります。

f:id:yuki10k:20191020214717j:plain

iPhone11との画面サイズ比較です。
OPPO Reno Aの方が若干ですがサイズが大きいことが分かります。

f:id:yuki10k:20191020214739j:plain

数日使ってみての感想

メインで使っている端末はPixel3なので、そちらとの比較になります。

電波状況

※10月に開始された楽天モバイルのMNO回線です

  • 家で使っていると窓から離れると圏外になってしまう
  • 都心にあるオフィスでは基本的には繋がる
  • 地下鉄ではau回線に繋がるため快適
  • 利用者がまだ少ないからか、MVNO回線に比べると昼や夜は繋がりさえすれば快適

OPPO Reno A

  • Pixel3と比較すると、画面がかなり大きく動画やKindleを見るのも快適
    • Pixel3では漫画を読む場合はたまに拡大が必要でした。
  • バッテリー持ちは良好
    • Pixel3と比べると安心感がかなりあります。 ※Pixel3のバッテリー持ちが良くないからというのもありますが・・
  • ゲームをするには若干スペック不足
    • ドラゴンクエストウォークをプレイしてみた感じでは、標準画質ではモタつきが発生しストレスフルでした。
    • プレイを再開しようとしても起動画面からになる (メモリは6GBあるのになぜ・・・
  • Pixel3に比べてモタつきが発生する

といった感じです。
基本的にはゲームをプレイしなければ普段使いには全問題ない印象でした。

また、若干のもたつきに関しては、以下の 高パフォーマンスモード に設定することでかなりマシになりました。
バッテリーの持ちがどうなるか若干気がかりですが、それでもPixel3よりは持つ印象です。

f:id:yuki10k:20191023130138p:plain
高パフォーマンスモード

まとめ

  • 楽天の回線はまだまだこれからという印象で通常のサービス開始までに良くなってくれればいいなと思います。
  • OPPO Reno Aは普段使いでは全く問題なし。
  • コスパ良すぎる。
  • Felicaは試していないですが、おそらく問題なく使えると思います。
  • 今までハイエンドな端末を使っていた人は、違和感を感じると思います。
  • 今後はメインはPixel3を引き続き使いつつ、OPPO Reno A + 楽天回線も様子を見ていきたいと思います。

Pixel3 aが発売されたこのタイミングでPixel3を購入・・!

タイトルの通りですが、Pixel3aが発表されたにもかかわらず、Pixel3を購入しました。

store.google.com

前々からPixel3は気になっていましたが、以下の記事を見て雑誌を購入して、

japanese.engadget.com

「Pixel3すごい(語彙力・・)」
「このカメラ欲しい」

という感じで、ずっと悶々としていました。 しかし、定価9万5千円はちょっと高いのでバレンタインセールのようにディスカウントが発生するタイミングを虎視眈々と狙っていました。

Googleストアで「Pixel 3」のNot Pinkが2万円オフのセール、Pixel 3 XLなら2万5000円オフ - Engadget 日本版

全く安くなる気配は無く、月日が流れ。。

Pixel3aという廉価版が出るらしいというので、常に情報をウォッチしていたところ、 5月7日のGoogle I/Oでついに発表されました。(眠い目を擦りながらリアルタイムで視聴

store.google.com

値段も5万円以下ということで、速攻でポチり、17日発売を待つのみという状態に。
その後もソワソワしながら色々調べていると、Pixel3aには「Pixel VisualCore」が載っていないという情報が。

カメラに関しては素人なため、正直気にならないだろうと思っていましたが、 ラクマを眺めていたところ、

が58,000円で販売されていました。

ラクマで3,000円引きのクーポンを配布していたため、55,000円で購入できることに。

Pixel3aの定価が48,600円なので、Pixel3a + 6,000円ほどでPixel3のSIMフリーが手に入ることになります。しかもUSB Type-Cのイヤホン付き。

個人間取引ということで赤ロムやSIMロック未解除といった不安がありましたが、 IMEI番号やSIMロック解除画面の画像も載せてくれていたので信用して購入し、Pixel3aはキャンセルしました。
※本記事を参考にフリマアプリなどで購入し、不利益を被られても一切の責任を負いかねます。

購入したPixel3は当日中に発送していただき、翌日には到着。

早速開封していきます。

丁寧にプチプチで梱包されていました。 f:id:yuki10k:20190520142911j:plain

Pixel3の箱が登場
角が少し潰れていましたが特に問題なし。

f:id:yuki10k:20190520142916j:plain

蓋を開けると、フィルムに包まれたままのPixe3が登場。

f:id:yuki10k:20190520142920j:plain

イヤホンや充電ケーブルも未開封のまま。

f:id:yuki10k:20190520143006j:plain

f:id:yuki10k:20190520143013j:plain

SIMもアンロックされており、IIJmiodocomo SIMでも問題なく通信することができました。

早速色々撮影していきます。
まずは東京駅

気持ちキレイに撮れている気がします。

f:id:yuki10k:20190520143053j:plain

夜バージョン。
近未来感があります。
Night Sightは未使用です

f:id:yuki10k:20190520143056j:plain

続いて食べ物系。
ラーメンや

f:id:yuki10k:20190520143051j:plain

ハンバーガ

f:id:yuki10k:20190520143057j:plain

お肉もお手のもの

f:id:yuki10k:20190520143111j:plain

鮮やかな肉の塊

f:id:yuki10k:20190520143100j:plain

まだこれくらいしかPixel3で写真の撮影をできていませんが、現時点では非常に満足です。
iPhone Xで撮影した写真と比較しても鮮やかに撮れるなーという印象です。

とはいえiPhoneのエコシステムからはなかなか抜け出せないので、しばらくはiPhone Xをメイン端末として引き続き使用しつつ、Pixel3はカメラ中心で0simで運用していこうと思います。

CodelabsのAndroid Room with a ViewをKotlinでやってみた

Codelabsについて

Googleが提供している、様々なチュートリアルやハンズオンのコースです。 https://codelabs.developers.google.com/

Android Room with a View

AndroidRoom ViewModel LiveDataについて学ぶことができるコースです。

https://codelabs.developers.google.com/codelabs/android-room-with-a-view/index.html?index=..%2F..%2Findex#0

GoogleがKotlinで実装した良いサンプルとして、こちらも公開されています。 https://github.com/googlesamples/android-sunflower

motivation

Android開発の学習をする上で、LiveData について押さえておきたかったので、Room with a Viewのcodelabsを見つけました。 手を動かしながら学べるのでちょうど良いと思いましたが、実装がJavaだったので置き換えつつ進めてみることにしました。

ゴールはCodelabのアプリがKotlinコードで動作することです。 Javaの元コードを見て、Kotlinだとこんな感じかな〜という風に書き換えて実装していったものになるので、正しい書き方とは異なる可能性もあります。

Environment

各セクション毎の変更点

2. Create your app

特になし。 Kotlinでアプリを作成します。

3. Update gradle files

kotlin-kaptプラグインを有効にし、annotationProcessorの定義をkaptに変更しています。

apply plugin: 'kotlin-kapt'
・
・
・
    // Room components
    implementation "android.arch.persistence.room:runtime:$rootProject.roomVersion"
//    annotationProcessor "android.arch.persistence.room:compiler:$rootProject.roomVersion"
    kapt "android.arch.persistence.room:compiler:$rootProject.roomVersion"
    androidTestImplementation "android.arch.persistence.room:testing:$rootProject.roomVersion"

    // Lifecycle components
    implementation "android.arch.lifecycle:extensions:$rootProject.archLifecycleVersion"
//    annotationProcessor "android.arch.lifecycle:compiler:$rootProject.archLifecycleVersion"
    kapt "android.arch.lifecycle:compiler:$rootProject.archLifecycleVersion"

4. Create the entity

Entityの定義方法自体がJavaとKotlinで大きく異なり、かなり楽に記述できます。

import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey

@Entity(tableName = "word_table")
class Word(@PrimaryKey @ColumnInfo(name = "word") var mWord: String)

5. Create the DAO / 6. The LiveData class

  1. ではgetAllWords() の戻り値はList<Word>ですが、6.でLiveData<List<Word>>に修正する
import android.arch.lifecycle.LiveData
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Insert
import android.arch.persistence.room.Query
import com.example.hoge.roomwithaview.entity.Word

@Dao
interface WordDao {
    @Insert
    fun insert(word: Word)

    @Query("DELETE from word_table")
    fun deleteAll()

    @Query("SELECT * from word_table ORDER BY word ASC")
    fun getAllWords(): LiveData<List<Word>>
}

7. Add a Room database / 12. Populate the database

getDatabaseメソッドはcompanion objectで定義していますが、package-level functionで定義するのが正しいかも。 ※sunflowerサンプルアプリケーションでも実装はcompanion objectで実装されていました。

PopulateDbAsyncクラスは12. Populate the databaseでの実装になります。 ここはCallbackの書き方で少しハマりました。

import android.arch.persistence.db.SupportSQLiteDatabase
import android.arch.persistence.room.Database
import android.arch.persistence.room.Room
import android.arch.persistence.room.RoomDatabase
import android.content.Context
import android.os.AsyncTask
import com.example.hoge.roomwithaview.dao.WordDao
import com.example.hoge.roomwithaview.entity.Word

@Database(entities = [Word::class], version = 1)
abstract class WordRoomDatabase: RoomDatabase() {

    abstract fun wordDao(): WordDao

    companion object {
        @Volatile
        private var instance: WordRoomDatabase? = null

        fun getDatabase(context: Context): WordRoomDatabase {
            if (instance == null) {
                synchronized(WordRoomDatabase::class.java) {
                    if (instance == null) {
                        instance = Room.databaseBuilder(context.applicationContext,
                                WordRoomDatabase::class.java, "word_database")
                                .addCallback(object : RoomDatabase.Callback() {
                                    override fun onOpen(db: SupportSQLiteDatabase) {
                                        PopulateDbAsync(instance!!).execute()
                                    }
                                })
                                .build()
                    }
                }
            }
            return instance!!
        }
    }
}

class PopulateDbAsync(db: WordRoomDatabase): AsyncTask<Void, Void, Void>() {
    private val mDao: WordDao = db.wordDao()

    override fun doInBackground(vararg params: Void?): Void? {
        mDao.deleteAll()
        var word = Word("Hello")
        mDao.insert(word)
        word = Word("World")
        mDao.insert(word)
        return null
    }
}  

8. Create the Repository

import android.app.Application
import android.arch.lifecycle.LiveData
import android.os.AsyncTask
import com.example.hoge.roomwithaview.dao.WordDao
import com.example.hoge.roomwithaview.db.WordRoomDatabase
import com.example.hoge.roomwithaview.entity.Word

class WordRepository(application: Application) {
    private val mWordDao: WordDao
    private var mAllWords: LiveData<List<Word>>

    init {
        val db = WordRoomDatabase.getDatabase(application)
        mWordDao = db.wordDao()
        mAllWords = mWordDao.getAllWords()
    }

    fun getAllWords(): LiveData<List<Word>> {
        return mAllWords
    }

    fun insert(word: Word) {
        InsertAsyncTask(mWordDao).execute(word)
    }
}

class InsertAsyncTask(wordDao: WordDao): AsyncTask<Word, Void, Void>() {
    private val mAsyncTaskDao: WordDao = wordDao
    override fun doInBackground(vararg params: Word?): Void? {
        mAsyncTaskDao.insert(params[0]!!)
        return null
    }
}

9. Create the ViewModel

import android.app.Application
import android.arch.lifecycle.AndroidViewModel
import android.arch.lifecycle.LiveData
import com.example.hoge.roomwithaview.entity.Word
import com.example.hoge.roomwithaview.repository.WordRepository

class WordViewModel(application: Application) : AndroidViewModel(application) {
    private val mRepository: WordRepository = WordRepository(application)
    var mAllWords: LiveData<List<Word>>

    init {
        mAllWords = mRepository.getAllWords()
    }

    fun insert(word: Word) {
        mRepository.insert(word)
    }
}

10. Add XML layout

ここはcodelabの通りに実装します。

layout/activity_main.xml の変更部分で + ボタンが、ぱっとは見つからなかったので、@drawable/ic_android_black_24dp で代用

11. Add a RecyclerView

import android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.example.hoge.roomwithaview.R
import com.example.hoge.roomwithaview.entity.Word

class WordListAdapter(context: Context): RecyclerView.Adapter<WordListAdapter.WordViewHolder>() {

    class WordViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
        val wordItemView: TextView = itemView.findViewById(R.id.textView)
    }

    private val mInflater: LayoutInflater = LayoutInflater.from(context)
    private var mWords: List<Word>? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder {
        val itemView = mInflater.inflate(R.layout.recyclerview_item, parent, false)
        return WordViewHolder(itemView)
    }

    override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
        if (mWords != null) {
            val current = mWords!![position]
            holder.wordItemView.text = current.mWord
        } else {
            holder.wordItemView.text = "No Word"
        }
    }

    fun setWords(words: List<Word>) {
        mWords = words
        notifyDataSetChanged()
    }

    override fun getItemCount(): Int {
        return if (mWords != null) {
            mWords!!.size
        } else {
            0
        }
    }
}

13. Add NewWordActivity

レイアウトファイルについては、codelabの通りに実装します。

import android.app.Activity
import android.content.Intent
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.text.TextUtils
import android.widget.Button
import android.widget.EditText

class NewWordActivity : AppCompatActivity() {

    companion object {
        const val EXTRA_REPLY: String = "com.example.hoge.roomwithaview.REPLY"
    }

    private var mEditWordView: EditText? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_new_word)
        mEditWordView = findViewById(R.id.edit_word)
        val wordText = mEditWordView!!.text

        val button = findViewById<Button>(R.id.button_save)
        button.setOnClickListener {
            val replyIntent = Intent()
            if (TextUtils.isEmpty(wordText)) {
                setResult(Activity.RESULT_CANCELED, replyIntent)
            } else {
                val word = wordText.toString()
                replyIntent.putExtra(EXTRA_REPLY, word)
                setResult(Activity.RESULT_OK, replyIntent)
            }
            finish()
        }
    }
}

14. Connect with the data

import android.app.Activity
import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import com.example.hoge.roomwithaview.adapter.WordListAdapter
import com.example.hoge.roomwithaview.entity.Word
import com.example.hoge.roomwithaview.model.WordViewModel
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    companion object {
        private const val NEW_WORD_ACTIVITY_REQUEST_CODE = 1
    }

    private var mWordViewModel: WordViewModel? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)

        val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
        val adapter = WordListAdapter(this)
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(this)

        mWordViewModel = ViewModelProviders.of(this).get(WordViewModel::class.java)
        mWordViewModel!!.mAllWords.observe(this, Observer {
            adapter.setWords(it!!)
        })

        fab.setOnClickListener {
            val intent = Intent(this, NewWordActivity::class.java)
            startActivityForResult(intent, NEW_WORD_ACTIVITY_REQUEST_CODE)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (requestCode == NEW_WORD_ACTIVITY_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
            val word = Word(data!!.getStringExtra(NewWordActivity.EXTRA_REPLY))
            mWordViewModel!!.insert(word)

            mWordViewModel!!.mAllWords.value!!.forEach {
                Log.i("hoge", it.mWord)
            }
        } else {
            Toast.makeText(
                    applicationContext,
                    R.string.empty_not_saved,
                    Toast.LENGTH_LONG).show()
        }
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.menu_main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        return when (item.itemId) {
            R.id.action_settings -> true
            else -> super.onOptionsItemSelected(item)
        }
    }
}

以上で一通り手順に沿っての実装が完了しました。 早速動作確認してみます。

トップページが表示され、PopulateDbAsyncクラスのdoInBackgroundメソッドによって、HelloWorld という文字が表示されています。

f:id:yuki10k:20181014193133p:plain

Droid君のアイコンをタップすると次の画面に遷移し、文字を入力できます。

f:id:yuki10k:20181014193222p:plain

testと入力し、SAVEボタンをタップするとTOPページに戻り、testの項目が追加されています。

f:id:yuki10k:20181014193319p:plain

一通りの動作確認ができました。 引き続きAndroid開発に携われるように色々学んでいこうと思います。