conference-app-2022 - DroidKaigi 2022 公式カンファレンスアプリ

(The Official Conference App for DroidKaigi 2022)

Created at: 2022-07-24 08:15:30
Language: Kotlin
License: Apache-2.0

DroidKaigi 2022 ロゴ

DroidKaigi 2022 公式アプリ

DroidKaigi 2022は、2022 年 10 月 5 日から 10 月 7 日に開催されます。そのアプリケーションを開発しています。一緒にアプリを開発して、エキサイティングなものにしましょう。

特徴

引き出し

デザイン

やってみて

未定

apk は GitHub Artifact からダウンロードできます。 https://github.com/DroidKaigi/conference-app-2022/actions/workflows/Build.yml?query=branch%3Amain

貢献する

私たちはいつでもあらゆる貢献を歓迎します!詳細については、 CONTRIBUTING.mdを参照してください。

日本語話者の方は、 CONTRIBUTING.ja.mdをご覧ください。

要件

最新の Android Studio Electric Eel以降。このページからダウンロードできます。 iOS 要件

技術スタック

今年のアプリは、今の Androidのアイデアをかなり取り入れて、多くのアイデアを追加しています。

画像

構成可能なビルド ロジック

ビルド ロジックの管理には、モジュール化で使用される feature-xxx や core-xx などの管理方法が導入されています。この方法により、ビルド ロジックが管理しやすくなります。

2 種類のプラグインによって管理されます。

  • プリミティブ プラグイン

以下に示すように、Android プラグインや Compose プラグインなど、いくつかの単純なプラグインが含まれています。単純なプラグインでもプラグイン間の依存関係が必要です。これは、パッケージ パスでそのような依存関係を示すことによって構成できます。たとえば、プラグイン

android.hilt
が必要です。
android

plugins {
    id("droidkaigi.primitive.android")
    id("droidkaigi.primitive.android.kotlin")
    id("droidkaigi.primitive.android.compose")
}
  • コンベンションプラグイン

このプロジェクトのコンベンション プラグインは、いくつかのプリミティブ プラグインを組み合わせて作成されています。

class AndroidFeaturePlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            with(pluginManager) {
                apply("droidkaigi.primitive.android")
                apply("droidkaigi.primitive.android.kotlin")
                apply("droidkaigi.primitive.android.compose")
                apply("droidkaigi.primitive.android.hilt")
                apply("droidkaigi.primitive.spotless")
                apply("droidkaigi.primitive.molecule")
            }
        }
    }
}

Compose を使用して UiModel を構築する

https://github.com/cashapp/moleculeを使用して UiModel を作成します。
Jetpack Compose を使用すると、Flow などのリアクティブ ストリームを Recompose で簡単に処理できます。これにより、初期状態を作成することもできます。

val uiModel = moleculeScope.moleculeComposeState(clock = ContextClock) {
    val scheduleResult by scheduleResultFlow.collectAsState(initial = Result.Loading)

    val scheduleState by remember {
        derivedStateOf {
            val scheduleState = ScheduleState.of(scheduleResult)
            scheduleState.filter(filters.value)
        }
    }
    SessionsUiModel(scheduleState = scheduleState, isFilterOn = filters.value.filterFavorite)
}

テスト戦略

ロボット テスト パターンを使用してテストをスケーラブルにする

このプロジェクトでは、テストは何をどのように行うかに分かれています。これにより、Compose メカニズム、レイアウトなどが変更されたときに多くのテストを書き直す必要がないため、テストがスケーラブルになります。

テストでは、何をテストするかを説明します。

@RunWith(AndroidJUnit4::class)
@HiltAndroidTest
class SessionsScreenTest {

    @get:Rule val robotTestRule = RobotTestRule(this)
    @Inject lateinit var sessionScreenRobot: SessionScreenRobot

    @Test
    fun canToggleFavorite() {
        sessionScreenRobot(robotTestRule) {
            clickFavoriteAt(0)
            checkFavoritedAt(index = 0, isFavorited = true)
            checkFavoriteIsSavedAt(0)
        }
    }
}

ロボットはそれをテストする方法を説明します。したがって、実装の詳細が含まれています。定期的にテストを追加する場合、このコードを見る必要はありません。

class SessionScreenRobot @Inject constructor() {
    ...

    context(RobotTestRule)
    fun clickFavoriteAt(index: Int) {
        composeTestRule
            .onFavorite(
                index
            )
            .performClick()
    }

    private fun AndroidComposeTestRule<*, *>.onFavorite(index: Int): SemanticsNodeInteraction {
        val title = DroidKaigiSchedule.fake().itemAt(index)
            .title
            .currentLangTitle

        return onNode(
            matcher = hasTestTag("favorite") and hasAnySibling(hasText(title)),
            useUnmergedTree = true
        )
    }
...

不安定にせずに忠実度の高いテストを作成する

このプロジェクトでは、JVM で Hilt を使用して統合テストを行い、デバイス固有の問題を回避します。
実際の本番アプリケーションと同じクラスを使用すればするほど、テストで実際の問題をより適切に検出できるようになると考えています。そのため、Hilt では可能な限り本番環境の依存関係を使用しています。このテストでは、基本的に実際の依存関係と、外部との接点である Fake the Repository を使用します。

画像

@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [SessionDataModule::class] // replace the production Module
)
@Module
class TestSessionDataModule {
    @Provides
    fun provideSessionsRepository(): SessionsRepository {
        return FakeSessionsRepository()
    }
}

アプリで使用されていないフィールドを Fake リポジトリでのみ公開して、いくつかのテストを許可します。お気に入りが保存されていることがわかります。

class FakeSessionsRepository : SessionsRepository {
    private val favorites = MutableStateFlow(persistentSetOf<TimetableItemId>())
    
    // ...
    
    // for test
    val savedFavorites get(): PersistentSet<TimetableItemId> = favorites.value
}
class SessionScreenRobot @Inject constructor() {
    @Inject lateinit var sessionsRepository: SessionsRepository
    private val fakeSessionsRepository: FakeSessionsRepository
        get() = sessionsRepository as FakeSessionsRepository

    fun checkFavoriteIsSavedAt(index: Int) {
        val expected = DroidKaigiSchedule.fake().itemAt(index).id
        fakeSessionsRepository.savedFavorites shouldContain expected
    }
}

Kotlin JS を使用したロジックの即時更新

実験的なアプローチとしてhttps://github.com/cashapp/ziplineを使用しようとしています。
これにより、通常の JVM Kotlin 実装をフォールバックとして使用しながら、Javascript で実装されたロジックをリリースし、Web 上での開発と同様に即座に更新することができます。
Kotlin のこれらの可能性に興奮しています。

次のインターフェースは、Kotlin JS と Android に実装されています。
ここにセッションのお知らせなどを追加できます。実験的な試みですので、今回はそのような実用的な役割はありません。

interface ScheduleModifier : ZiplineService {
    suspend fun modify(
        schedule: DroidKaigiSchedule
    ): DroidKaigiSchedule
}
class AndroidScheduleModifier : ScheduleModifier {
    override suspend fun modify(schedule: DroidKaigiSchedule): DroidKaigiSchedule {
        return schedule
    }
}
class JsScheduleModifier() : ScheduleModifier {
    override suspend fun modify(schedule: DroidKaigiSchedule): DroidKaigiSchedule {
...
    if (timetableItem is Session &&
        timetableItem.id == TimetableItemId("1")
    ) {
        timetableItem.copy(
            message = MultiLangText(
                enTitle = "This is a js message",
                jaTitle = "これはJSからのメッセージ",
            )
        )
    } else {
        timetableItem
    }
...
    }
}

マニフェスト ファイルをチェックして、その動作を確認できます。   https://droidkaigi.github.io/conference-app-2022/manifest.zipline.json

レイジーレイアウト

LazyColumn と LazyGrid の基本実装である LazyLayout を使用してタイムテーブルを描画しようとしています。これは、Google I/Oの Compose セッションの Lazy レイアウトで導入されました。

https://github.com/DroidKaigi/conference-app-2022/blob/91715b461b3162eb04ac58b79ba39ccdf21cf222/feature-sessions/src/main/java/io/github/droidkaigi/confsched2022/feature/sessions/Timetable.kt#L73

特別な感謝