Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Now in Android アプリ解説 / Now in Android App

Now in Android アプリ解説 / Now in Android App

D2bcabeeb1ddff142fb8988b412cb4d3?s=128

Yuki Anzai

June 05, 2022
Tweet

More Decks by Yuki Anzai

Other Decks in Technology

Transcript

  1. :VLJ"O[BJ !ZBO[N  *0FYUFOEFE /PXJO"OESPJEΞϓϦղઆ

  2. :VLJ"O[BJ w (PPHMF%FWFMPQFS&YQFSUGPS"OESPJE w UXJUUFS!ZBO[N w CMPHZBO[NCMPHTQPUDPN w גࣜձࣾ΢ϑΟΧ

  3. /PXJO"OESPJEΞϓϦ w IUUQTHJUIVCDPNBOESPJEOPXJOBOESPJE w ."%ʢ.PEFSO"OESPJE%FWFMPQNFOUʣͳαϯϓϧΞϓϦ w ,PUMJO +FUQBDL$PNQPTF

  4. /PXJO"OESPJE͸<8PSLJOQSPHSFTT🚧>Ͱ͋Γɺ ͜͜Ͱ঺հͨ͠಺༰ʢ೥݄೔࣌఺ʣ͕ ࠓޙมߋ͞ΕΔՄೳੑ͕͋Γ·͢

  5. None
  6. BOESPJEBQQMJDBUJPO

  7. BOESPJEBQQMJDBUJPO NBDSPCFODINBSL

  8. BOESPJEBQQMJDBUJPO NBDSPCFODINBSL BOESPJEMJCSBSZ

  9. BOESPJEBQQMJDBUJPO NBDSPCFODINBSL BOESPJEMJCSBSZ ʁ

  10. ʁ pluginManagement { includeBuild("build-logic") repositories { google() mavenCentral() gradlePluginPortal() }

    } …
  11. pluginManagement { includeBuild("build-logic") repositories { google() mavenCentral() gradlePluginPortal() } }

    … QMVHJOCVJMEͷ SPPUQSPKFDUEJSFDUPSZ ಠࣗQMVHJOͷQSPKFDU
  12. NPEVMF

  13. None
  14. plugins { `kotlin-dsl` } group = "com.google.samples.apps.nowinandroid.buildlogic" java { sourceCompatibility

    = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } dependencies { implementation(libs.android.gradlePlugin) implementation(libs.kotlin.gradlePlugin) implementation(libs.spotless.gradlePlugin) } gradlePlugin { plugins { … } } ʁ implementation("com.android.tools.build:gradle:7.2.1") ͷΑ͏ͳจࣈྻࢦఆͰ͸ͳ͍ MJCTBESPJEHSBEMF1MVHJO͸Ͳ͜Λࢦ͍ͯ͠Δʁ
  15. ϥΠϒϥϦ؅ཧ w HSBEMFͷ7FSTJPO$BUBMPH w IUUQTEPDTHSBEMFPSHDVSSFOUVTFSHVJEFQMBUGPSNTIUNM w TFUUJOHTHSBEMF಺ʹ௚઀ఆٛPS50.-ϑΝΠϧʹఆٛ

  16. ϥΠϒϥϦ؅ཧ w HSBEMFͷ7FSTJPO$BUBMPH w IUUQTEPDTHSBEMFPSHDVSSFOUVTFSHVJEFQMBUGPSNTIUNM dependencyResolutionManagement { … versionCatalogs {

    create("libs") { from(files("../gradle/libs.versions.toml")) } } } …
  17. [versions] accompanist = "0.24.8-beta" androidDesugarJdkLibs = "1.1.5" androidGradlePlugin = "7.2.1"

    androidxActivity = "1.4.0" androidxAppCompat = "1.3.0" androidxCompose = "1.2.0-beta02" … [libraries] accompanist-flowlayout = { group = "com.google.accompanist", name = "accompanist-flowlayout", version.ref = "accompanist" } android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" } android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" } … [plugins] protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
  18. [versions] accompanist = "0.24.8-beta" androidDesugarJdkLibs = "1.1.5" androidGradlePlugin = "7.2.1"

    androidxActivity = "1.4.0" androidxAppCompat = "1.3.0" androidxCompose = "1.2.0-beta02" … [libraries] accompanist-flowlayout = { group = "com.google.accompanist", name = "accompanist-flowlayout", version.ref = "accompanist" } android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" } android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" } … [plugins] protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
  19. … dependencies { implementation(libs.android.gradlePlugin) … } dependencyResolutionManagement { … versionCatalogs

    { create("libs") { from(files("...")) } } } … … [libraries] … android-gradlePlugin = { … } …
  20. dependencies { … implementation(libs.androidx.activity.compose) implementation(libs.androidx.appcompat) … dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories

    { … } } WFSTJPO$BUBMPHT͕ࢦఆ͞Ε͍ͯͳ͍ɺ͜ͷMJCT͸Ͳ͔͜Βʁ
  21. ϥΠϒϥϦ؅ཧ w HSBEMFͷ7FSTJPO$BUBMPH w IUUQTEPDTHSBEMFPSHDVSSFOUVTFSHVJEFQMBUGPSNTIUNM w TFUUJOHTHSBEMF಺ʹ௚઀ఆٛPS50.-ϑΝΠϧʹఆٛ w HSBEMFͷԼʹMJCTWFSTJPOTUPNMͱ͍͏໊લͰϑΝΠϧΛ༻ҙ͢Δͱɺ MJCTͱ͍͏໊લͰ7FSTJPO$BUBMPH͕࡞ΒΕΔ

  22. CVJMEMPHJDͷDVTUPNHSBEMFQMVHJO͸ԿΛ͍ͯ͠Δͷ͔ʁ

  23. CVJMEMPHJDͷDVTUPNHSBEMFQMVHJO͸ԿΛ͍ͯ͠Δͷ͔ʁ plugins { id("nowinandroid.android.application") id("nowinandroid.android.application.compose") id("nowinandroid.android.application.jacoco") kotlin("kapt") id("jacoco") id("dagger.hilt.android.plugin") id("nowinandroid.spotless")

    } android { … } dependencies { … } ʁ OPXJOBOESPJE99ͷJEΛࢦఆ͍ͯͯ͠ id("com.android.application") Ͱ͸ͳ͍
  24. … gradlePlugin { plugins { register("androidApplicationCompose") { id = "nowinandroid.android.application.compose"

    implementationClass = "AndroidApplicationComposeConventionPlugin" } register("androidApplication") { id = "nowinandroid.android.application" implementationClass = "AndroidApplicationConventionPlugin" } … } }
  25. class AndroidApplicationConventionPlugin : Plugin<Project> { override fun apply(target: Project) {

    with(target) { with(pluginManager) { apply("com.android.application") apply("org.jetbrains.kotlin.android") } extensions.configure<BaseAppModuleExtension> { configureKotlinAndroid(this) defaultConfig.targetSdk = 32 } } } } ͜͜Ͱࢦఆ͞Ε͍ͯΔ BOESPJECMPDLͰͷઃఆͷڞ௨Խ
  26. internal fun Project.configureKotlinAndroid( commonExtension: CommonExtension<*, *, *, *>, ) {

    commonExtension.apply { compileSdk = 32 defaultConfig { minSdk = 21 } compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 isCoreLibraryDesugaringEnabled = true } kotlinOptions { … } } … }
  27. ΞϓϦͷߏ੒ w BOESPJEBQQMJDBUJPONPEVMF w BQQϝΠϯͷΞϓϦ w BQQOJBDBUBMPHΞϓϦ༻ͷ6*DPNQPOFOUTΛݟΕΔΧλϩάΞϓϦ

  28. BQQOJBDBUBMPH app-nia-catalog core-ui core-model

  29. BQQOJBDBUBMPH app-nia-catalog core-ui core-model data class Author( val id: String,

    val name: String, val imageUrl: String, val twitter: String, val mediumPage: String, val bio: String, )
  30. BQQOJBDBUBMPH app-nia-catalog core-ui core-model @Composable fun NiaFilledButton( onClick: () ->

    Unit, modifier: Modifier = Modifier, enabled: Boolean = true, small: Boolean = false, … content: @Composable RowScope.() -> Unit ) { Button( … ) }
  31. BQQOJBDBUBMPH app-nia-catalog core-model core-ui

  32. BQQ

  33. App core-ui core-navigation feature-author sync feature-interests feature-foryou feature-topic core-model core-common

    core-data core- datastore core- database core- network ˎUFTU༻ͷNPEVMF͸ আ͍͍ͯ·͢
  34. App core-ui core-navigation feature-author sync feature-interests feature-foryou feature-topic core-model core-common

    core-data core- datastore core- database core- network ˎUFTU༻ͷNPEVMF͸ আ͍͍ͯ·͢
  35. App core-ui core-navigation feature-author sync feature-interests feature-foryou feature-topic core-model core-common

    core-data core- datastore core- database core- network ˎUFTU༻ͷNPEVMF͸ আ͍͍ͯ·͢ w *0༻ͷ$PSPVUJOF%JTQBUDIFS w TFBMFEJOUFSGBDF3FTVMUPVU5
  36. App core-ui core-navigation feature-author sync feature-interests feature-foryou feature-topic core-model core-common

    core-data core- datastore core- database core- network ˎUFTU༻ͷNPEVMF͸ আ͍͍ͯ·͢ 1SPUPEBUBTUPSFͰGPMMPXͨ͠ 5PQJDͱ"VUIPSͷJEΛอଘ
  37. App core-ui core-navigation feature-author sync feature-interests feature-foryou feature-topic core-model core-common

    core-data core- datastore core- database core- network ˎUFTU༻ͷNPEVMF͸ আ͍͍ͯ·͢ 3FUPSpUΛ࢖ͬͯωοτϫʔΫ͔Β σʔλΛऔಘ
  38. App core-ui core-navigation feature-author sync feature-interests feature-foryou feature-topic core-model core-common

    core-data core- datastore core- database core- network ˎUFTU༻ͷNPEVMF͸ আ͍͍ͯ·͢ SPPNʹ5PQJD΍"VUIPSͳͲͷ σʔλΛอଘ
  39. App core-ui core-navigation feature-author sync feature-interests feature-foryou feature-topic core-model core-common

    core-data core- datastore core- database core- network ˎUFTU༻ͷNPEVMF͸ আ͍͍ͯ·͢ 5PQJD "VUIPS /FXT ͷ3FQPTJUPSZ࣮૷
  40. App core-ui core-navigation feature-author sync feature-interests feature-foryou feature-topic core-model core-common

    core-data core- datastore core- database core- network ˎUFTU༻ͷNPEVMF͸ আ͍͍ͯ·͢ 8PSL.BOBHFSΛ࢖ͬͯΞϓϦ ىಈ࣌ʹσʔλΛಉظ
  41. App core-ui core-navigation feature-author sync feature-interests feature-foryou feature-topic core-model core-common

    core-data core- datastore core- database core- network ˎUFTU༻ͷNPEVMF͸ আ͍͍ͯ·͢ /BWJHBUJPOͷભҠઌΛදݱ͢Δ JOUFSGBDF
  42. App core-ui core-navigation feature-author sync feature-interests feature-foryou feature-topic core-model core-common

    core-data core- datastore core- database core- network ˎUFTU༻ͷNPEVMF͸ আ͍͍ͯ·͢
  43. GFBUVSF99NPEVMF w ػೳ୯Ґͷ6*NPEVMF w 99/BWJHBUJPOLU/BW(SBQI#VJMEFSʹ DPNQPTBCMFΛ௥Ճ͢ΔͨΊͷ֦ுؔ਺ w 994DSFFOLUը໘ͷ$PNQPTBCMF w 997JFX.PEFMLU994DSFFOͰར༻͞Ε

    Δ7JFX.PEFM
  44. 5PQJD/BWJHBUJPOLU object TopicDestination : NiaNavigationDestination { override val route =

    "topic_route" override val destination = "topic_destination" const val topicIdArg = "topicId" } fun NavGraphBuilder.topicGraph( onBackClick: () -> Unit ) { composable( route = "${TopicDestination.route}/{${TopicDestination.topicIdArg}}", arguments = listOf( navArgument(TopicDestination.topicIdArg) { type = NavType.StringType } ) ) { TopicRoute(onBackClick = onBackClick) } } /BW(SBQIʹ͜ͷGFBUVSFͷը໘Λ ભҠઌͱͯ͠௥Ճ͢ΔͨΊͷ֦ுؔ਺
  45. @Composable fun NiaNavHost( … ) { NavHost( navController = navController,

    startDestination = startDestination, modifier = modifier, ) { … interestsGraph( …, nestedGraphs = { topicGraph(onBackClick = { navController.popBackStack() }) authorGraph(onBackClick = { navController.popBackStack() }) } ) } }
  46. $PNQPTFͷQSBDUJDF

  47. .BUFSJBMXJUI+FUQBDL$PNQPTF w EZOBNJDDPMPSTDIFNFʢ"OESPJEҎ߱ʣରԠ @Composable fun NiaTheme( darkTheme: Boolean = isSystemInDarkTheme(),

    dynamicColor: Boolean = false, androidTheme: Boolean = false, content: @Composable() () -> Unit ) { val colorScheme = when { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(co } androidTheme && darkTheme -> DarkAndroidColorScheme androidTheme -> LightAndroidColorScheme darkTheme -> DarkDefaultColorScheme else -> LightDefaultColorScheme }
  48. *DPOΛ·ͱΊͨPCKFDU object NiaIcons { val AccountCircle = Icons.Outlined.AccountCircle val Add

    = Icons.Rounded.Add val ArrowBack = Icons.Rounded.ArrowBack val ArrowDropDown = Icons.Rounded.ArrowDropDown val ArrowDropUp = Icons.Rounded.ArrowDropUp val Bookmark = R.drawable.ic_bookmark … } Icon( painter = painterResource(id = NiaIcons.Bookmark), contentDescription = null ) Icon(imageVector = NiaIcons.Add, contentDescription = null)
  49. 8JOEPX*OTFUTͷѻ͍ w 8JOEPX*OTFUTTBGF%SBXJOHΛओʹ࢖༻ NiaNavRail( …, modifier = Modifier.safeDrawingPadding() ) NavigationBar(

    modifier = Modifier.windowInsetsPadding( WindowInsets.safeDrawing.only( WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom ) ), … ) {
  50. 8JOEPX4J[FDMBTT w ը໘෯Ͱ#PUUPN#BSͱ/BW3BJMΛग़͠෼͚ Scaffold( … bottomBar = { if (windowSizeClass.widthSizeClass

    == WindowWidthSizeClass.Compact) { NiaBottomBar( … ) } } ) { padding -> Row(…) { if (windowSizeClass.widthSizeClass != WindowWidthSizeClass.Compact) { NiaNavRail( … ) } … } BOESPJEYDPNQPTFNBUFSJBMNBUFSJBMXJOEPXTJ[FDMBTTBMQIB
  51. 8JOEPX4J[FDMBTT w ը໘෯Ͱ#PUUPN#BSͱ/BW3BJMΛग़͠෼͚ w هࣄҰཡͷ$PMVNO਺ val numberOfColumns = when (windowSizeClass.widthSizeClass)

    { WindowWidthSizeClass.Compact, WindowWidthSizeClass.Medium -> 1 else -> floor(maxWidth / 300.dp).toInt().coerceAtLeast(1) } BOESPJEYDPNQPTFNBUFSJBMNBUFSJBMXJOEPXTJ[FDMBTTBMQIB
  52. /BWJHBUJPO 4BWFE4UBUF)BOEMF composable( route = "${TopicDestination.route}/{${TopicDestination.topicIdArg}}", arguments = listOf( navArgument(TopicDestination.topicIdArg)

    { type = NavType.StringType } ) ) { TopicRoute(onBackClick = onBackClick) } @Composable fun TopicRoute( onBackClick: () -> Unit, modifier: Modifier = Modifier, viewModel: TopicViewModel = hiltViewModel(), ) { … 5PQJD%FTUJOBUJPOUPQJD*E"SH ͱ͍͏໊લͰ 4USJOH ͷBSHVNFOU /BW#BDL4UBDL&OUSZͷBSHVNFOUT͕ "CTUSBDU4BWFE4UBUF7JFX.PEFM'BDUPSZ ͷEFGBVMU"SHTʹ౉͞ΕΔ
  53. /BWJHBUJPO 4BWFE4UBUF)BOEMF @HiltViewModel class TopicViewModel @Inject constructor( savedStateHandle: SavedStateHandle, …

    ) : ViewModel() { private val topicId: String = checkNotNull(savedStateHandle[TopicDestination.topicIdArg]) … } 5PQJD%FTUJOBUJPOUPQJD*E"SH ͱ͍͏໊લͰ 4USJOH ͷBSHVNFOU ͕4BWFE4UBUF)BOEMFʹೖ͍ͬͯΔ
  54. CZTBWFE4UBUF)BOEMFTBWFBCMF\^ TextField( value = viewModel.text, onValueChange = { viewModel.text =

    it } ) @HiltViewModel class SampleViewModel @Inject constructor( savedStateHandle: SavedStateHandle ) : ViewModel() { var text by mutableStateOf("") } 1SPDFTTLJMM࣌ʹ5FYU'JFMEͷ ೖྗจࣈΛࣦͬͯ͠·͏ @HiltViewModel class SampleViewModel @Inject constructor( savedStateHandle: SavedStateHandle ) : ViewModel() { var text by savedStateHandle.saveable { mutableStateOf("") } } 1SPDFTTLJMM࣌΋ೖྗจࣈ ͕อ࣋͞ΕΔ 😫 👍
  55. ·ͱΊ w /PXJO"OESPJEΞϓϦʹ͸࠷৽ͷ"OESPJE։ൃͷ஌ݟ͕੝Γࠐ·Ε͍ͯΔ w ίʔυΛಡΜͰΈΑ͏ w IUUQTHJUIVCDPNBOESPJEOPXJOBOESPJE