ほりすのぶろぶろぶろぐ

ほりすのぶろぶろぶろぐ

非情報系から最高のエンジニアを目指す旧帝大学生

DroidKaigiのIssueに思い切って手を挙げてみたらすごく学びが深かった

はじめに

いきなり結論ですが、

Android開発初心者も思い切ってIssueを担当してみると圧倒的成長につながるよ

これがこの記事で1番言いたいことです。

具体的にはどういうことか、自分はどんなことをしてその結論に至ったのかを書いていきたいと思います。

対象の読者は、DroidKaigiをご存じない方、Androidにそれほど自信はないけどOSSへのContributeをしてみたい方です。 自分は初めてのOSS Contributorになれて嬉しさ満載なので、その気持ちをこの記事にぶつけます。

バックグラウンド

自分の経歴や開発経験は過去記事を読んでいただければ分かるのですが、ざっくりまとめると、

と言う感じです。

DroidKaigi

droidkaigi.jp

DroidKaigiはエンジニアが主役のAndroidカンファレンスです。

日本国内で最大級のAndroid技術カンファレンスです。

自分は1年前はDroidKaigiの存在をしらなかったため、参加するのは今回が初めてですが、各地の最強Androidエンジニアが長年培ってきた技術をとてもわかりやすく共有する場って、かなりワクワクしますね。

開催日は2020年2月20日(木)〜2020年2月21日(金)で、まだチケット販売もおこなっているそうなので、まだの方は是非申し込んでみることをお勧めします。

OSSGithub Repository

github.com

こちらは、カンファレンス当日のタイムスケジュールやフロアマップをスマートフォンからみることができるアプリのソースコードです。

毎年、DroidKaigiのアプリを各地のAndroidエンジニアが有志で作っています。

例年、カンファレンスの約1ヶ月強前までにある程度実装されたこのRepositoryがpublicになります。

まだ一部機能が実装されていなかったり、バグが存在する状態なため、これまた有志のAndroidエンジニア達がContributorとして追加開発していく流れです。

2020年1月22日(水)現在では、合計100名ほどのContributorがいらっしゃるらしいです。

この、全体で知見を共有しつつ、最高のアプリを作っていく感じ、素晴らしいコミュニティ/カンファレンスだと思います。

そして1番何が良いって、

その年の最新のトレンドをふんだんに使用したコードなため、知見の宝庫なのです。

エンジニアという職業柄(まだ就職していませんが)技術のトレンドは毎年変わっていきます。流行り廃りが激しく、最近までのデファクトスタンダードが今はすでにレガシーになってしまったり。

毎年、この時期にこの 知見の宝庫 が共有されることで、キャッチアップがしやすくなる利点があります。

最強の教本ですね。

Issue

上述したように、DroidKaigiアプリは有志のエンジニアが募ってアプリを完成させていくため、好きなIssueを担当し、PRを出すことができます。

そのIssueは、運営?メンバーの方が立てたものはもちろん、自分がアプリを触っているうちに発見したバグや欲しい機能をIssueに追加することができます。

そのIssueの難易度にはかなり幅があり、

  • Layoutのmerginを調節する

という簡単なIssueから、

  • iOSアプリを対象にMPP(MultiPlatform Project)を進める

という力量が問われるIssueまで存在します。

冒頭でも行った、

Android開発初心者も思い切ってIssueを担当してみると圧倒的成長につながるよ

に繋がるのですが、

割と easy Labelの貼られたIssueが多く、誰でもContributorになれるチャンスがあるのです。

担当したIssue

実際に担当したIssueはこの2つです。

github.com

github.com

  1. スタッフ一覧を表示する画面に関して、ViewModelにUiModelを導入する
  2. 上記のViewModelTestの記述

これらのLabel、 easy なんですよ。

UiModelってなに?テストなんてかけないよ って思われる方もいるかもしれません。なんなら最初見たときは自分もそうでしたが、

既にUiModelを導入したViewModelや、テストを書いた画面が存在するため、基本的にはその実装を模倣すればできるのです。

これが easy である所以ですね。

ただまあ、コピペエンジニアは自分のポリシー的にもタブーなので、しっかりと理解して自分なりに実装してみつつ、あくまで他の画面は参考にするという方針でコードを書きました。

この記事は、同じAndroidエンジニアの友人へ向けた(自分なりの)コード解説を含んでいるので、

下記に UiModelとは、ViewModelTestに関して少し言及したいと思います。

※解釈に誤りがあれば教えてください🙏

これをみる前に、まずご自身でDoroidKaigiリポジトリのREADMEを一読してからのほうが理解が深まると思います。かなり丁寧に書いてくださっているので。

README見たらだいたいわかったという人は飛ばしてもらってOKです。

UiModel

StaffsViewModel

    class StaffsViewModel @Inject constructor(
        private val staffRepository: StaffRepository
    ) : ViewModel() {
    
        data class UiModel(
            val isLoading: Boolean,
            val error: AppError?,
            val staffContents: StaffContents
        ) {
            companion object {
                val EMPTY = UiModel(false, null, StaffContents.EMPTY)
            }
        }
    
        // 略
    }

自分が担当したIssueで追加したUiModelです。

UiModelというのはおそらく、UI(ActivityやFragment)側の状態を司るクラスだと思います。

実際にUiModeのコンストラクタでは、下記の3つの状態があります。

  • isLoading
    • ローディング中かどうか
  • error
    • AppErrorはseald classで ApiException 等々のExceptionを複数定義しているクラス
    • errorではない可能性があるためNullable
  • staffContents
    • 表示するスタッフのリストを保持したModel
    • repository等でデータ取得できればここで保持する

何も考えずにやるとすると、Loadingの状態をLiveDataとして普通にViewModelのメンバに持たせることになるかと思いますが、UiModelを挟むことで状態が散らばらずに一元管理できて便利なのでしょう。

StaffsViewModel

    private val staffContentsLoadState: LiveData<LoadState<StaffContents>> = liveData {
            emitSource(
                staffRepository.staffs()
                    .toLoadingState()
                    .asLiveData()
            )
            staffRepository.refresh()
        }

ViewModelのここでは、Repositoryを経由して StaffContents を取得し、それをLoadingStateに変換し、さらにLiveDataとして扱うよ〜〜というコードですね。

Repositoryを経由しているのは、Entityの取得をDatabaseから取得するかApiから取得するかを、ViewModel以上で知らなくて良くなるためです。

責務の分離ですね。

内部的には StaffRepository#staffs ではDatabaseから取得。 StaffRepository#refresh()APIを叩いてからDatabaseに最新のスタッフ情報を格納する挙動になっています。

これらの挙動をViewModel等が意識しなくて良くなるため、Databaseの種類が変わろうとViewModel以上には影響がなくて済むのです。 StaffsViewModel

    val uiModel: LiveData<UiModel> = combine(
            initialValue = UiModel.EMPTY,
            liveData1 = staffContentsLoadState
        ) { _, loadState ->
            val staffContents = when (loadState) {
                is LoadState.Loaded -> {
                    loadState.value
                }
                else -> {
                    StaffContents.EMPTY
                }
            }
            UiModel(
                isLoading = loadState.isLoading,
                error = loadState.getErrorIfExists().toAppError(),
                staffContents = staffContents
            )
        }

これも、やっていることは簡単です。

combine という、内部的には MediatorLiveData と同様のことをしている拡張関数で、引数を数個とることができます。

combine に渡したLiveDataの値に変更があれば、ラムダ式が実行されて、UiModelが更新されるという内容です。

今回はliveData1だけに止まっていますが、他にも hogeLoadState なるLiveDataがあった場合には、

  • staffContentsLoadState
  • hogeLoadState

のどちらかに変更があった場合にラムダが実行されます。

そのため、UiModelのisLoadingは

isLoading = staffContentLoadState.isLoading || hogeLoadState.isLoading

のように変えてあげる感じです。

詳しくはリポジトリSessionDetailViewModel で複数バージョンのものが実装されているので見てみてください。 StaffsFragment

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        //略
        staffsViewModel.uiModel.observe(viewLifecycleOwner) { uiModel ->
              progressTimeLatch.loading = uiModel.isLoading
              groupAdapter.update(uiModel.staffContents.staffs.map {
                  staffItemFactory.create(it)
              })
    
                uiModel.error?.let {
                    systemViewModel.onError(it)
              }
          }
    }

あとはFragmentのonViewCreatedでobserveして、UiModelの状態に応じてUIを切り替えてあげるだけです。

もしこれがUiModelとしてまとめておらず、ViewModelにLiveDataを乱立させていたら、observeが乱立し、UI更新処理が分散してしまい処理が追いづらくなるのです。

これですっきりまとめられましたね。

ちなみに、progressTimeLatchに関して、詳しくは内部実装を読んでいただきたいのですが、Fragmentでこのような実装がされていました。 StaffsFragment

    progressTimeLatch = ProgressTimeLatch { showProgress ->
                binding.progressBar.isVisible = showProgress
            }.apply {
                loading = true
            }

ざっくりいうと、 ProgressTimeLatch のメンバ変数である loading の値を変更すると、上記のラムダ式が実行されるって言う感じですね。

上記はラムダ式の定義と同時に loading をtrueにしているため、 binding.progressbar がVisibleになる感じですね。

おわりに

思ったより長くなってしまった...

結論何が言いたかったかと言うと、繰り返しになりますが。

Android開発初心者も思い切ってIssueを担当してみると圧倒的成長につながるよ

です。

最初は何書いてるか全くわからん状態かもしれません。しかし、DroidKaigiは優しいため、大抵他の画面で参考になるコードがあったりするので、 easy Labelであれば後先考えず

「🙋‍♂️」

とコメントしてしまうと良いかもしれません。

広く全体のコードを読もうとするから全くわからん状態になってしまうのかもしれません、 easy なIssueを引き受け、一旦は狭い範囲を重点的にみて、他のコードを参考にしつつ理解を深めて全体に視線を広げていく方針はお勧めです。

おまけ

ちなみに、

easy なIssueなんてすぐ埋まっちゃうよ!!

みたいな人は、GithubとSlackを連携させてIssueが立ったら自動的に通知してくれる仕組みを導入すると見逃しがなくなったり、素早く反応ができるためお勧めです。

slack.com

これを見ればすぐに設定ができるのでやり得です。