Closed17

DroidKaigi/conference-app-2021へのContribution #18

高田 晴彦高田 晴彦

右上のForkボタンから自分のGitHubアカウントへフォークする。
そこからローカルにcloneする

git@github.com:tfandkusu/conference-app-2021.git

本家をupstreamリモートとしてして設定する。

cd conference-app-2021/
git remote add upstream git@github.com:DroidKaigi/conference-app-2021.git
高田 晴彦高田 晴彦

本家に追従するときは、このようにする。
今回はmasterブランチがメインではなくmainブランチがメインなので注意する。

git fetch upstream
git merge upstream/main
git push origin main
高田 晴彦高田 晴彦

CONTRIBUTING.ja.md をちゃんと読む

環境を構築する

Latest Android Studio Arctic Fox and higher.

が必要。こちらを読むとCananyビルドっぽいので、それをダウンロードしてインストールする。

プロジェクトを読み込む。
インデックスが構築できるまで待つ。
インデックスが構築できれば、Android Studioの実行ボタンをおして、ビルドとAPKの実機インストールを行う。
アプリの動作を確認。

高田 晴彦高田 晴彦

取り組むIssueを選ぶ

Issuesの番号の若い順から読んでいって、できそうなところで、🙋コメントを付ける。

若い番号にはStaff画面の作成がある。
#19#18が終わってからでないとやってはいけない。
#18をやってみる。

去年のconference-appを見て、イメージを膨らませる。
Staff画面はないが、Contributor画面と同じようなものと想定される。
APIはある (しかし今回触れる場所ではない)

表示

  • 連番
  • アイコン
  • 名前

操作

  • 上下にスクロールする
  • クリックしてGithubのURLを開く

処理

  • APIから一覧をとってくる。
高田 晴彦高田 晴彦

できるというある程度の自信を得る(寄り道中)

ホーム画面の構造を把握する

View層

MainActivity→DroidKaigiApp(Composable)→AppContent→FeedScreen→FeedList

ViewModel層

feedViewModel()→compositionLocalOf<FeedViewModel>

これが謎なので調べる。
Jetpack ComposeのCompositionLocalを使って下位階層にデータを渡す

CompositionLocal のキーを作成している。

private val LocalFeedViewModel = compositionLocalOf<FeedViewModel> {
    error("not LocalFeedViewModel provided")
}

CompositionLocalProviderでキーに対する実装を定義している。
content=blackは、さらにその子要素と思われる。

@Composable
fun ProvideFeedViewModel(viewModel: FeedViewModel, block: @Composable () -> Unit) {
    CompositionLocalProvider(LocalFeedViewModel provides viewModel, content = block)
}

@Previewを付けたComposableと、MainActivity#setUp で定義している本実装を切り替えている。

この知識を元に、MainActivityからたどれる。
ProvideViewModels→RealFeedViewModel→stateがUiのモデル相当
stateはallFeedContentsをcombineしている。
allFeedContentsはrepositoryから作っている。

Repository層

FeedRepositoryImpl
StaffRepository (FeedRepositoryのコピーだった)

高田 晴彦高田 晴彦

できるというある程度の自信を得る(まだ寄り道中)

View層

PreviewFeedScreen(@Previewがついているので、プレビューできる) →fakeFeedViewModel→FakeFeedViewModel

Interractiveモードが動くか確認する

スクロールできるし、ハートが付けられる

FakeStaffViewModelはすでにあった

これを使って、PreviewFeedScreenを参考に、Composableで画面を作成することができるが、それは#18のゴールではない

高田 晴彦高田 晴彦

できるというある程度の自信を得る

やってみるIssue
Implement FakeViewModel and FakeViewModelTest for "Staff" screen #18

要件

  • FakeStaffViewModelがあるが、エラーのケースがないので実装する。FakeFeedViewModelにはそれがある。
  • StaffViewModelTestがないので作成する

テストの作成方法

FeedItemViewModelTestと同じようなことをやればよい。
Parameterizedなテスト。
今回はdata()からFakeViewModelだけ提供すればよい。

自信を得たので🙋を付けた

高田 晴彦高田 晴彦

ブランチを切る

git checkout -b fake_staff_view_model

FakeStaffViewModelの実装

FakeFeedViewModelと同じノリで実装して、エラーは出ないようになった。
意味は後で調べる。

fun fakeStaffViewModel(errorFetchData: Boolean = false) = FakeStaffViewModel(errorFetchData)
class FakeStaffViewModel(errorFetchData: Boolean) : StaffViewModel {

    private val effectChannel = Channel<StaffViewModel.Effect>(Channel.UNLIMITED)
    override val effect: Flow<StaffViewModel.Effect> = effectChannel.receiveAsFlow()

    private val coroutineScope = CoroutineScope(
        object : CoroutineDispatcher() {
            // for preview
            override fun dispatch(context: CoroutineContext, block: Runnable) {
                block.run()
            }
        }
    )

    private val mutableStaffs = MutableStateFlow(
        fakeStaffs()
    )

    private val errorStaffs = flow<List<Staff>> {
        throw AppError.ApiException.ServerException(null)
    }.catch { error ->
        effectChannel.send(StaffViewModel.Effect.ErrorMessage(error as AppError))
    }.stateIn(coroutineScope, SharingStarted.Lazily, fakeStaffs())

    private val mStaffs: StateFlow<List<Staff>> = if (errorFetchData) {
        errorStaffs
    } else {
        mutableStaffs
    }

    override val state: StateFlow<StaffViewModel.State> = mStaffs.map {
        StaffViewModel.State(
            showProgress = false,
            staffContents = it
        )
    }.stateIn(coroutineScope, SharingStarted.Eagerly, StaffViewModel.State())

    override fun event(event: StaffViewModel.Event) {
    }
}

自分がフォークしたところにPUSHする

コミットする。

PUSHする。

git push origin fake_staff_view_model
高田 晴彦高田 晴彦

隣のIssue

Implement FakeViewModel and FakeViewModelTest for "Contributor" screen #21

作成したViewModelの意味を確認する

Unidirectional data flowを理解する

Unidirectional data flow

Undirectional = 一方向

ComposeはEventをViewModelに渡す、ViewModelはEventに反応してStateとEffectを変更して提供する。

インターフェース

interface UnidirectionalViewModel<EVENT, EFFECT, STATE> {
    val state: StateFlow<STATE>
    val effect: Flow<EFFECT>
    fun event(event: EVENT)
}

stateはStateFlowを使っている

StateFlowとは

StateFlowとSharedFlow

値を設定可能なFlow。MutableLiveDataと似ているが、バックグラウンドになっても最新の値を通知する。

エラーの送信方法

エラーはChannelを使って送っている。ChannelをFlowに変換して監視可能にしている。

effectFlow represents a one-time event such as a Snackbar display.

とあるようにSingleLiveEvent相当のもの。

Channelについては詳解 Kotlin Coroutines [2021]で学習する。

stateInとは

これも公式に記載

Flow を StateFlow に変換するには、stateIn 中間演算子を使用します。

こちらの記事も参考にする
https://at-sushi.work/blog/27

coroutineScopeを使っている。単体テストなのでスレッドを切り替えないCoroutineScopeを作ったと推測。

高田 晴彦高田 晴彦

いったんrebaseする

git rebase main

普段は git merge main だが、なぜか今回はそれで行いたくなった。pushするときは -f フラグが必要。

lintに怒られないようにする

怒られた内容

Execution failed for task ':spotlessKotlinCheck'.
> The following files had format violations:
      android/src/test/java/io/github/droidkaigi/feeder/StaffViewModelTest.kt
          @@ -29,7 +29,6 @@
           ········staffContents.size·shouldBeGreaterThan·1

Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
           ····}
Use '--warning-mode all' to show the individual deprecation warnings.

ローカルで確認

 ./gradlew stoplessCheck

修正方法が提示された。

  Run './gradlew :spotlessApply' to fix these violations.

実行した。

メソッドの間を2行開けたのがダメ

高田 晴彦高田 晴彦

フィールド名について、他のViewModelと命名規則を合わせる。

ViewModelのStateのフィールド名と同じ変数名にする。

このコミット

FakeStaffViewModel設置場所間違いの修正

FakeStaffViewModelの所属ディレクトリが隣のプルリクのFakeContributorViewModelと違うので、同じような場所に置いた。

ビルドエラー発生

CIではこちらを実行しているので、事前にローカル環境で実行する

/gradlew testDebugUnitTest

提出完了

レビューを待つ。
Implement FakeViewModel and FakeViewModelTest for "Staff" screen #214

通過してマージされた。

このスクラップは2021/03/03にクローズされました