heihei blog

Blog icon by Unsplash https://unsplash.com/@virussinside

2020年5月を振り返る

もうすぐ梅雨ですね。引き続きリモートしています。少なくとも6月いっぱいはリモートかなと思っています。

5月を振り返る

バジル

f:id:shaunkawano:20200515002630p:plain

先月はネギを育て始めたけど調子に乗ってルッコラとバジルが追加された。ルッコラには虫がわんさかついてしまって断念。自分はたまに水やりするだけ。バジルもうまく育てれるかわからないけど、バジルは本当に美味しい匂いがする。

散歩

f:id:shaunkawano:20200515002634p:plain

ちょこちょこ就業前に散歩をしているけど気分が変わっていいなと思っている。ちゃんと起きれないとできないけど。これは代々木公園。

テトリス

今更感ありますがNintendo Switchを心優しい後輩さんがすでに持っているけど抽選あったということで譲ってくれました(ありがとう、ありがとう🙏) リングフィットアドベンチャー気になっているんですがまだ購入できていないのでとりあえず無料で遊べるものを、ということでテトリスやっています。

www.nintendo.co.jp

99人同時対戦するというやつです。面白い。一回だけまぐれで一位になってから面白くてやってます。

技術

リーダーの役割に関しては、チーム内での反省点とかは無限に尽きないんですけど、今月はどっぷり新しい技術をやれた月でした。DFMまわりについてちょっとだけ詳しくなれたのでどこかで発信せねばと思っています。

内容うっっっっっっっすいけどまあいっかとひらきなおっています。

以上です。5月もお疲れさまでした!

2020年4月を振り返る

進んでいくー

4月を振り返る

3月後半同様、自粛もあり家にいるように意識した月。

特にここ行った!とかはない。最近印象深かったのを並べます。

University of the Peopleはじめた

4月から、CS基礎の授業のみを試しに受けてみている。 土日にまとめたやったり、始業前にやったりしてる。 CSの授業はPythonの本当に基礎という感じ。 Python触ったこと無かったこともあり、自分には本当にちょうどいい感じ。 英語とCSの本当に初歩のほうを両方いい感じにゆるくやっているイメージ。

掲示板形式でのディスカッションだったりコード書く宿題の提出内容を生徒同士で採点し合う仕組みがあったり、ただ課題を説いていくだけって感じではまったくない。英語を書く量は必然的に増えるのでいいなと思うのと、モチベーションが維持される?感じもあっていい仕組みだなと思う。レベルが上ったりするとかなり大変になりそうかも。

  • (翻訳を介してでも)普段から英語を読むのを定期的にやっている
  • Writingもやらないとなーって思っている人
  • 土日とか、定期的にまとまった時間がとれる人

がもしいたら特訓の方法の一つとしてはありかもしれない。(お金はちょっとは払うので、ちゃんとPayしなきゃって意識も芽生えるので。) あんまり仕組みはちゃんと調べてなくて学位は取ろうと思えばとれるってかんじくらいにしかわかってないけど以下にいろいろ情報はまとまってそうです。(もしやってる人とか興味ある人いたら連絡くれると嬉しいです)

www.englishpedia.jp

my.uopeople.edu

デカフェコーヒー豆買った

f:id:shaunkawano:20200510185120p:plain

Amazonで買ってみたデカフェコーヒー☕豆。Amazon's Choiceだった。 まえまで買っていた☕屋さんが営業休止となったので。 前飲んでいたものよりもすっぱめ。

ネギ育てはじめた

f:id:shaunkawano:20200510184956p:plain

食べたネギの根っこを、水につけて陽のあたる窓際に置いているだけなんですけど、これが結構育つ。(入れ物はペットボトルの底の部分を切り取ったもの)

↓数日〜1週間くらいでこれくらい育った印象(盛ってるかも・・) f:id:shaunkawano:20200510185749p:plain

育ちきったのはチョキチョキして味噌汁に入れたりしておいしくいただいてます。自分もネギみたいにぐんぐん成長したい。

以上です。4月もお疲れさまでした!

2020年3月を振り返る

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

3月を振り返る

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

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

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

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

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

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

井の頭公園行った

久しぶりに行った!

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

戸越銀座行った

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

そのほか

映画

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

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

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

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

www.youtube.com

  • The Peanut Butter Falcon

www.youtube.com

  • Maleficent: Mistress of Evil

www.youtube.com

そのほか

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

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