2020年3月を振り返る

今年も1/4くらい終了したらしい。((((;゚Д゚))))

3月を振り返る

後半の方はあんまり外に出なかったので、基本3月前半ー中ばまでのものです。

すごく変わった居酒屋に行った

居酒屋というかなんというか。

とりあえずこんなところでした。

コロナが落ち着いたら、またいつか行きたいなあ。 f:id:shaunkawano:20200331221814g:plain

ご飯は気持ち足りなかった。のでこの後餃子食べに行った

井の頭公園行った

久しぶりに行った!

そしたら春って感じだった。

戸越銀座行った

ウマウマだった。写真ないけど、焼き小龍包×🍻が最高だった。

そのほか

映画

  • 劇場版SHIROBAKOみた

アニメもっかい見直さなきゃって感じでした。

最近は弊社でもリモートワークが始まり、家で過ごす時間がめっきり増えました。 皆様も体調管理等には気をつけてください!

今月もお疲れさまでした!!

RoomでDBの値更新時に、更新通知がこない!というときはDIの設定に原因があるかも

「RoomでDBの値更新時に、更新通知がこない!」こんなことがもし起こったら、DI周りをしっかり見直しましょう、という話になります。

TL;DR

RoomDatabaseやDaoクラスがアプリ内の複数箇所で扱われている場合に、同一インスタンスが利用されているかを確認しましょう。 (特にDatabaseBuilderで作成するDatabaseをDIしている際などには、それがアプリ内でSingletonで扱われるようになっているかどうかを確認しましょう。)

たとえば、以下のような同一クラスのDaoなのだけど、以下の処理をそれぞれ別インスタンスで行ってしまうと、更新通知が即座に発火されなかったりします。

  • (Insert / Updateなど)更新処理を行う
  • LiveDataなどを返す関数を持つような、変更通知を行う

DIしている場合

特に、一つのDaoを複数箇所で利用する場合 and/or DIする場合などには気をつけましょう。

RoomDatabaseをセットアップしアプリをビルドすると、XXXDatabase_Implクラスのコードが生成されます。 このImplクラスは、daoを作成する関数を持ちます。たとえばLocalArticleDaoというDaoを扱うとして、コードをビルドすると以下のような関数が生成されます。

@Override
  public LocalArticleDao articleDao() {
    if (_localArticleDao != null) {
      return _localArticleDao;
    } else {
      synchronized(this) {
        if(_localArticleDao == null) {
          _localArticleDao = new LocalArticleDao_Impl(this);
        }
        return _localArticleDao;
      }
    }
  }

このように、XXXDatabase_Implクラスのコードを読むと、一度生成されたDaoはフィールドに保持され、以後、生成されたDaoを返すような実装になっていることがわかります。 なので、Databaseクラスがアプリ内でSingletonであれば、基本的には問題ないと考えます。

DI(ここではDagger)の話になりますが、以下のようにAppDatabaseをSingletonではない形でProvideしていると、 Injectされる箇所ごとに、新しいAppDatabaseが初期化され渡されます。DaoはAppDatabaseに紐づくに形で作成されるため、新しいAppDatabaseが都度渡されるということは、 複数箇所でInjectされているDaoも、すべて別インスタンスとなります。

@Provides // Singleton指定がないため、都度インスタンスが生成される
fun provideAppDatabase(application: Context): AppDatabase {
  return Room.databaseBuilder(
    application.applicationContext,
    AppDatabase::class.java,
    "app-db"
  )
    .build()
}

@Provides
fun provideArticleDao(db: AppDatabase): LocalArticleDao = db.articleDao()

まとめ

なんか当たり前のことしか書いてない気がしますが、生成されたコードだったりを深く読み解くのも大事ですが、基本的なところもしっかり確認していきましょう。。ということで。 自戒しかないメモでした。

2020年2月を振り返る

2月を振り返る

2020年はあと300日!2月を振り返っていきます。

2月半ばに「休んで5」という弊社の制度でまるっと一週間くらい休んだ。入社月によるけど、自分の場合は4月になるとまた新しく付与されてなくなっちゃうので。(連休と重ねたのでわりと長かった)

🏂に行った

ぷーちゃんと焼肉食べたりしたときに行こう!!という流れで。 一泊二日、夕食・朝食付き、移動もコミコミで1万5000円くらいだった。

www.nikkoyumoto-ski.com

ボート(ゲレンデ受付の方も激おこのコンディションの悪さだったので数時間だけ)して、ご飯食べて飲みして日光東照宮とかの観光もして満足でした。

f:id:shaunkawano:20200307163628p:plain

山梨に行った

数年ぶりに運転しました。サイドブレーキの位置がわからず見つけれなくてレンタカー屋から出るのに時間がかかった。

f:id:shaunkawano:20200307163639p:plain

富士山見えた

f:id:shaunkawano:20200307163650p:plain

白秋蒸留所にもいけた

もろもろ料理

ちょこちょこ継続してできている。

妻の誕生日だったのでご飯と🎂作ってみたが、特にケーキの生地の焼き具合と見た目はやばかった。 ケーキはまたリベンジしたい。ミキサー買ったし。

f:id:shaunkawano:20200307163707p:plain

これはコートデメイアン(Mayan)

そのほか

KITASANDO COFFEE

しほしゃんがいるKITASANDO COFFEEにお邪魔した。ラテたくさん飲ませてくれたw🙏 美味しかったのはもちろん、オシャでした。

映画

  • 「パラサイト 半地下の家族」みた。賛否両論あるらしい。自分の感想としては、面白かったけど、二回目は見に行かないかも、って感じでした〜!面白かったけど。

我ながら、ゆるい。以上。

BottomNavigationItemView長押し時の挙動を制御する

BottomNavigationViewとは

f:id:shaunkawano:20200216145526p:plain

アプリ内の主要な遷移先への移動を可能にするコンポーネントです。

上記スクリーンショットのような、画面下部にボタンを配置するという特徴から"Bottom"Navigationと呼ばれているのではないかと考えています。

BottomNavigationの詳細については、公式サイトをご覧ください。

material.io

BottomNavigationItemView長押し時の挙動を制御したい

特別な処理を行わず、シンプルにBottomNavigationを用いてMenuをinflateさせ表示すると、アイテムを長押しした際に、Tooltipが表示されます。

f:id:shaunkawano:20200307153644p:plain

(上記スクリーンショットはGoogle Payアプリの画面です。)

レアケースかもしれませんが、(というか、アクセシビリティ観点などから見ると非推奨なのかもしれません。) たとえばどうしてもこのTooltipを非表示にしたかったり、ユーザーがアイテムを長押しした際に独自の処理を行いたい場合には、このようにできますよ、という雰囲気のメモです。

ちなみに今確認したところ、Twitterアプリは長押し時にTooltipが表示されていないようなので、もしBottomNavigationViewを利用している場合は制御を行っている可能性があります。

先にコードだけ書いておくと、以下のようにすることで制御が可能です

bottomNavigation.menu.forEach {
  val view = bottomNavigation.findViewById<View>(it.itemId)
  view.setOnLongClickListener {
    // よしなに行いたい処理を記述
    true
  }
}

この記事の後半では、BottomNavigationViewの内部のコードなどを見ていきます。

Tooltipはどのように表示されているのか

冒頭にも書きましたが、BottomNavigationViewを用いると、長押しした際の標準の挙動としてこのTooltipが表示されます。 このTooltipが、どのように表示されているのか、ざっくり処理を追っていきます。

処理を追うBottomNavigationViewが入っているMaterial Componentsのバージョンです: com.google.android.material:material:1.2.0-alpha05

BottomNavigationView#inflateMenu

まず、BottomNavigationViewをXML上で定義する際に、menuを指定します。(もしくはコード上からinflateMenu関数を呼び出すことも可能です。)

 <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavigation"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        ...
        app:menu="@menu/bottom_navigation_menu" // menuResを指定
        />

こうすると、BottomNavigationViewのコンストラクタ内で、inflateMenu関数が呼ばれます。

public void inflateMenu(int resId) {
    presenter.setUpdateSuspended(true);
    getMenuInflater().inflate(resId, menu);
    presenter.setUpdateSuspended(false);
    presenter.updateMenuView(true);
}

BottomNavigationPresenter#updateMenuView

BottomNavigationView.inflateMenu関数内部では、MenuInflaterクラスのinflate関数を呼び出しMenuをinflateしています。 また、BottomNavigationPresenterというPresenterクラスの関数をいくつか呼び出しています。

注目したいのは、BottomNavigationPresenter.updateMenuViewです。この関数を見ていきます。

@Override
  public void updateMenuView(boolean cleared) {
    if (updateSuspended) {
      return;
    }
    if (cleared) {
      menuView.buildMenuView();
    } else {
      menuView.updateMenuView();
    }
  }

BottomNavigationView.inflateMenu関数内部では、

presenter.updateMenuView(true);

という呼び出しをしているため、引数のclearedの値はtrueが入っています。そのため、(もしupdateSuspendedというフィールドの値がtrueでなければ、)以下のブロックが実行されます。

menuView.buildMenuView();

BottomNavigationItemView#initialize

BottomNavigationMenuView.buildMenuView関数の内部では、BottomNavigationViewで表示する各アイテムのView(=BottomNavigationItemView)を初期化します。 一部抜粋すると、以下のようになっています:

BottomNavigationItemView child = getNewItem();
      ...
      child.initialize((MenuItemImpl) menu.getItem(i), 0);
      child.setItemPosition(i);
      child.setOnClickListener(onClickListener);
      if (selectedItemId != Menu.NONE && menu.getItem(i).getItemId() == selectedItemId) {
        selectedItemPosition = i;
      }
      setBadgeIfNeeded(child);
      addView(child);

ここでは、setOnClickListenerなどを呼んでいる箇所はありますが、長押し時のためのリスナーをセットするsetOnLongLickListenerなどは呼び出ししていません。 ということで、BottomNavigationItemView.initializeのコード内部を読みます。まるっと抜粋すると・・

@Override
  public void initialize(@NonNull MenuItemImpl itemData, int menuType) {
    this.itemData = itemData;
    setCheckable(itemData.isCheckable());
    setChecked(itemData.isChecked());
    setEnabled(itemData.isEnabled());
    setIcon(itemData.getIcon());
    setTitle(itemData.getTitle());
    setId(itemData.getItemId());
    if (!TextUtils.isEmpty(itemData.getContentDescription())) {
      setContentDescription(itemData.getContentDescription());
    }

    CharSequence tooltipText = !TextUtils.isEmpty(itemData.getTooltipText())
        ? itemData.getTooltipText()
        : itemData.getTitle();
    TooltipCompat.setTooltipText(this, tooltipText);
    setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
  }

ここでtooltipTextという文字が見えます。何やらここでいろいろやってそうです。

TooltipCompat.setTooltipText

CharSequence tooltipText = !TextUtils.isEmpty(itemData.getTooltipText())
  ? itemData.getTooltipText()
  : itemData.getTitle();
TooltipCompat.setTooltipText(this, tooltipText);

TooltipCompat.setTooltipTextというのは、それっぽい名前ですね。内部を見ると、

public static void setTooltipText(@NonNull View view, @Nullable CharSequence tooltipText) {
        if (Build.VERSION.SDK_INT >= 26) {
            view.setTooltipText(tooltipText);
        } else {
            TooltipCompatHandler.setTooltipText(view, tooltipText);
        }
    }

SDKバージョンが26以上の場合はView.setTooltipTextを呼び出しています。

TooltipCompatHandler.setTooltipText

if (Build.VERSION.SDK_INT >= 26) {
  view.setTooltipText(tooltipText);
} else {
  TooltipCompatHandler.setTooltipText(view, tooltipText);
}

elseの分岐時に実行される、TooltipCompatHandler.setTooltipTextの処理を見ていきます。

if (TextUtils.isEmpty(tooltipText)) {
            if (sActiveHandler != null && sActiveHandler.mAnchor == view) {
                sActiveHandler.hide();
            }
            view.setOnLongClickListener(null);
            view.setLongClickable(false);
            view.setOnHoverListener(null);
        } else {
            new TooltipCompatHandler(view, tooltipText);
        }

なにやらtooltipTextが空の場合はviewに対してsetOnLongClickListener(null)しています。tooltipTextが空ではない場合は、TooltipCompatHandlerクラスを初期化しています。 このクラスのコンストラクタでなにかやっていそうです。

TooltipCompatHandler

private TooltipCompatHandler(View anchor, CharSequence tooltipText) {
        mAnchor = anchor;
        mTooltipText = tooltipText;
        mHoverSlop = ViewConfigurationCompat.getScaledHoverSlop(
                ViewConfiguration.get(mAnchor.getContext()));
        clearAnchorPos();

        mAnchor.setOnLongClickListener(this);
        mAnchor.setOnHoverListener(this);
    }

実行されているのは上記のコンストラクタです。 引数に渡ってきたViewのsetOnLongClickListenerを呼び出し、自身をListenerとしてセットしています。お察しかもしれませんが、TooltipCompatHandlerはView.OnLongClickListenerを実装しています。

@Override
    public boolean onLongClick(View v) {
        mAnchorX = v.getWidth() / 2;
        mAnchorY = v.getHeight() / 2;
        show(true /* from touch */);
        return true;
    }

SDK26未満のバージョンの場合には、こちらのコードが実行されていそう、ということがわかりました。

つまり、もともとやりたかったことに話を戻すと、ここに渡ってくるViewに対してOnLongClickListenerを上書きでセットすればよさそうです。 ここに渡ってくるViewというのは、BottomNavigationMenuView.buildMenuView関数の内部で初期化していたBottomNavigationItemViewです。 これらのViewは、BottomNavigation.getMenu関数で取得したMenuのgetItem関数にてアクセスできます。一つのMenuアイテムを取得する場合にはこれでもよいですが、たとえばすべてのMenuにたいしてアクセスしたい場合などには、androidx.corecore-ktxライブラリに便利なKotlin拡張関数が用意されているため、こちらを活用できます。

/** Performs the given action on each item in this menu. */
inline fun Menu.forEach(action: (item: MenuItem) -> Unit) {
    for (index in 0 until size()) {
        action(getItem(index))
    }
}

上記の拡張関数を利用すると、最終的には以下のようなコードで、BottomNavigationItemView長押し時の処理を制御できます。

bottomNavigation.menu.forEach {
  val view = bottomNavigation.findViewById<View>(it.itemId)
  view.setOnLongClickListener {
    // よしなに行いたい処理を記述
    true
  }
}

以上です。 もしなにか間違いやご指摘等ありましたら、コメントやTwitterのDM等いただけますと幸いです🙏

参考リンクなど

  • 参考にしたStackoverflow

stackoverflow.com

stackoverflow.com

2020年1月を振り返る

振り返りはゆるく

さて、今年も10%くらいきったっておもうっと驚愕すぎる。

初詣に行った

f:id:shaunkawano:20200104234217p:plain

吉でした。去年よりよくなった。

f:id:shaunkawano:20200205220228p:plain

丸秘展に行った

妻が行きたい〜!と行っていてよく知らなかったけど行ってみた。 「丸秘」って何のことだったのかはよくわからなかったけど、座りやすい椅子が展示されていたりMercariのフォントについての展示があったりもして身近に感じるものも多くあって楽しかった。

f:id:shaunkawano:20200205220233p:plain

熊本に帰省した

熊本に帰省した。阿蘇山の大観峰とか久しぶりに行けてよかった。運良く火山灰も降っていなくて行けた。硫黄の匂いがすごくして、小学生の頃遠足で来ていたころのイメージとはまったく様変わりしていて寂しい気持ちにちょっとだけなった。写真は💪感。

アナと雪の女王2を見直した

f:id:shaunkawano:20200104234246p:plain

カナダでも見たのですが、改めて日本で見直し。 いくつかの歌を聞くのにハマっている。

日記を買った

6年以上前から3年連用当用日記というのを2冊くらい書いてて3冊目の途中で書かなくなってたんですけど、2020ということで懲りずに一冊買った。今度は5年もの。続くかわからないですけどゆるく書いていきたいなと思っています。ということでブログと日記とで重複している感じもしますが、うすくでも書き続けていければと思っています。

読んだ本

  • 知らないと恥をかく世界の大問題10 転機を迎える世界と日本 (角川新書)

ハイライト3選

さまざまな問題の中から市民革命が起き、何百年もかけて民主主義を築いてきました。民主化というのは、短期間で一気にやろうと思っても無理なのだという、ある種〝冷淡〟な見方も、どこかで求められている... 結局は、 民主主義は上から押し付けられてもダメ なのです。一人ひとりが「本当に民主主義が大事なのだ」と理解して、はじめて民主化が成功する

ブータンでは国内総生産(GDP)に代わる尺度として、国民総幸福(GNH=Gross National Happiness)という独自の考え方を国家の指標として打ち出している

データを蓄積し、ビッグデータを用いて犯罪を防ぐ。そういう技術だけでは不十分なのです。 いわゆる「社会」がそもそもどうなっているのかを知ること、これもまたとても大切なこと なのです。人間を知る、社会を知るということは、AIには不可能

見た映画

旅行の往復の飛行機の中だったりで見たものがメイン。

  • The Internship

www.youtube.com

  • Frozen 2

www.youtube.com

  • Official Secrets

www.youtube.com

  • The Peanut Butter Falcon

www.youtube.com

  • Maleficent: Mistress of Evil

www.youtube.com

そのほか

あとは、1月で28歳になりました。今年は英語を誰かにマイペースに教えられないかなとか、今年こそ個人アプリ出したいなとか、いろいろ思っていますが、やっぱり何より健康第一で、初心忘れず過ごしたいと思います。

ゆるくと書いたけど年初めの振り返りなので項目は多めでした。次の月からはまた平常運転になると思われます。では!

Androidのmenu.xmlでactionLayoutを指定しているレイアウトが表示されないとき

f:id:shaunkawano:20200105183941p:plain

今後忘れた頃にまた踏みそうだなと思ったので、備忘録として。

前提

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/some_id"
        android:title="@string/some_menu_item_title"
        android:actionLayout="@layout/some_action_layout"
        app:showAsAction="always" />
</menu>

このようなコードがあったとき、なぜかtitleで指定している文字が表示され、actionLayoutで指定しているレイアウトが表示されなった。

解決策

android:actionLayout="@layout/some_action_layout"

↑これを

↓こうする

app:actionLayout="@layout/some_action_layout"

以下に書いてあります: stackoverflow.com

android:とapp:の違い?

stackoverflow.com

stackoverflow.com

android:はAndroid SDKが提供するUIのattributeの値指定に利用できるもので、app:は基本的にはカスタムViewに対してattributeを用いて値を変更したいときなどに利用できるものらしそう、というところまではわかる。が、カスタムネームスペースというのは開発者がカスタムViewなどを定義したときのみに利用できてほしいところが、実際のところ、今回のようなmenu xmlのactionLayoutの指定といった、Androidプラットフォームが提供しているAPIに対しても使うことがある。他にも、app:srcCompatなど。

AndroidプラットフォームのAPIを利用する際、いつandroid:を使い、いつapp:を使うのか。対応がまとまっていると嬉しいなと思ったが、そのようなリソースは見つけれていない。

もし知っている方がいたら教えていただけると嬉しいです🙏

2019年を振り返る

今年も去年同様、2019年をブログ記事でかんたんに振り返ります。

1月

blog.shoheikawano.com

Matching Dev Meetup#2 at Eureka, Inc.さんでやったり、熊本帰省したり、文喫行ったりしていたみたいですね。文喫最近行っていないなあ。

matching-dev-group.connpass.com

2020年の1月も、去年と似たような動きをしそうな予感。

2月

blog.shoheikawano.com

DroidKaigiで登壇したり、今は休止していますがAndroid Weekendsやったり、東京タワー登ったりしたらしい。

3月

blog.shoheikawano.com

慌ただしく一瞬で過ぎた月だった模様。映画見たとかしか書いてない。(ほかもそんなもん)

4月

blog.shoheikawano.com

オフィス変わったり、EMの優しい先輩とチェアリングしたり。高校の友達のバスケを見にいったり。

5月

blog.shoheikawano.com

大阪旅行いったり、Android Weekendやったり。オリンピック申し込んだり・・(友人がチケット当ててくれた!!)

6月

blog.shoheikawano.com

Android Weekendやったり、アラジン見たり。

7月

blog.shoheikawano.com

天気の子数回見たり、福岡観光いったりした!Matching Dev Meetupも参加した。

8月

blog.shoheikawano.com

箱根旅行行って、プロポーズした。今年一番の大きなイベントだった。 Yogiboも買った!!Yogibo良い買い物だった。

9月

blog.shoheikawano.com

入籍して指輪作った。Bit Valleyとか技術書典とかに参加した。

10月

blog.shoheikawano.com

採用イベント&友人の結婚式で函館いったり、天気の子展行ったりした。

11月

blog.shoheikawano.com

香川行ったり、トランポリンで遊んだり!

12月

blog.shoheikawano.com

カナダ行ったり!!


結婚して、旅行をたくさんした。楽しい。 もっといろいろなところに行きたい。 エンジニアリングと仕事に関しては、初心に戻って丁寧にしていきたい。

このブログは、引き続きゆるーく、続けれたら続けます。内容ないけど。

以上です。2019年も、お疲れさまでした!これから空港!

f:id:shaunkawano:20191231152841p:plain