DroidKaigi/conference-app-2021へのContribution #18
DroidKaigi/conference-app-2021へContributionする。去年やったがPRの送り方などを忘れているので、思い出しながら行う。
右上の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の実機インストールを行う。
アプリの動作を確認。
できるというある程度の自信を得る(寄り道中)
ホーム画面の構造を把握する
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
プルリクを作る
お手本にしているリンク先が行方不明と勘違い
FakeFeedViewModelTestが無くなったと焦ったが、FeedItemViewModelTestがそれだった。
CIが動く
GitHub Actionっぽい。
プルリクの本文を修正
作成したプルリク(WIP)
[WIP]Implement FakeViewModel and FakeViewModelTest for "Staff" screen #214
隣のIssue
Implement FakeViewModel and FakeViewModelTest for "Contributor" screen #21
作成したViewModelの意味を確認する
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とは
値を設定可能な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を作ったと推測。
StaffViewModelTestを作成
FeedItemViewModelTestと同じように作成する。
意味はあとで調べる。
先行しているContributorViewModelTestと比較しても同じような書き方になったので問題ないと思われる。
いったん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.
実行した。
寄り道 Java11のインストール
Java 11 (OpenJDK: AdoptOpenJDK) を Homebrew で macOS にインストールする
brew tap AdoptOpenJDK/openjdk
brew install adoptopenjdk11
フィールド名について、他のViewModelと命名規則を合わせる。
ViewModelのStateのフィールド名と同じ変数名にする。
FakeStaffViewModel設置場所間違いの修正
FakeStaffViewModelの所属ディレクトリが隣のプルリクのFakeContributorViewModelと違うので、同じような場所に置いた。
ビルドエラー発生
CIではこちらを実行しているので、事前にローカル環境で実行する
/gradlew testDebugUnitTest
提出完了
レビューを待つ。
Implement FakeViewModel and FakeViewModelTest for "Staff" screen #214
通過してマージされた。
Parametarized Testの理解
Android で Parameterized テスト(JUnit4, Robolectric)を行う
@Parameterized.Parameters(name = "{0}")
を設定すると、テスト失敗時の表示が分かりやすくなる。
spotlessについて
プロジェクト直下のbuild.gradleで定義している
general-purpose formatting plugin
内部でktlintが使われている。
ktlintの設定ファイルが .editorconfig