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

Now in Android アプリ解説

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Now in Android アプリ解説

Avatar for Yuki Anzai

Yuki Anzai

June 05, 2022
Tweet

More Decks by Yuki Anzai

Other Decks in Technology

Transcript

  1. 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͸Ͳ͜Λࢦ͍ͯ͠Δʁ
  2. [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" }
  3. [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" }
  4. … dependencies { implementation(libs.android.gradlePlugin) … } dependencyResolutionManagement { … versionCatalogs

    { create("libs") { from(files("...")) } } } … … [libraries] … android-gradlePlugin = { … } …
  5. … gradlePlugin { plugins { register("androidApplicationCompose") { id = "nowinandroid.android.application.compose"

    implementationClass = "AndroidApplicationComposeConventionPlugin" } register("androidApplication") { id = "nowinandroid.android.application" implementationClass = "AndroidApplicationConventionPlugin" } … } }
  6. 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Ͱͷઃఆͷڞ௨Խ
  7. 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 { … } } … }
  8. 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, )
  9. 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( … ) }
  10. BQQ

  11. 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͸ আ͍͍ͯ·͢
  12. 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͸ আ͍͍ͯ·͢
  13. 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
  14. 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Λอଘ
  15. 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Λ࢖ͬͯωοτϫʔΫ͔Β σʔλΛऔಘ
  16. 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ͳͲͷ σʔλΛอଘ
  17. 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࣮૷
  18. 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Λ࢖ͬͯΞϓϦ ىಈ࣌ʹσʔλΛಉظ
  19. 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
  20. 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͸ আ͍͍ͯ·͢
  21. 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ͷը໘Λ ભҠઌͱͯ͠௥Ճ͢ΔͨΊͷ֦ுؔ਺
  22. @Composable fun NiaNavHost( … ) { NavHost( navController = navController,

    startDestination = startDestination, modifier = modifier, ) { … interestsGraph( …, nestedGraphs = { topicGraph(onBackClick = { navController.popBackStack() }) authorGraph(onBackClick = { navController.popBackStack() }) } ) } }
  23. .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 }
  24. *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)
  25. 8JOEPX*OTFUTͷѻ͍ w 8JOEPX*OTFUTTBGF%SBXJOHΛओʹ࢖༻ NiaNavRail( …, modifier = Modifier.safeDrawingPadding() ) NavigationBar(

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

    == WindowWidthSizeClass.Compact) { NiaBottomBar( … ) } } ) { padding -> Row(…) { if (windowSizeClass.widthSizeClass != WindowWidthSizeClass.Compact) { NiaNavRail( … ) } … } BOESPJEYDPNQPTFNBUFSJBMNBUFSJBMXJOEPXTJ[FDMBTTBMQIB
  27. 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
  28. /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ʹ౉͞ΕΔ
  29. /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ʹೖ͍ͬͯΔ
  30. 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࣌΋ೖྗจࣈ ͕อ࣋͞ΕΔ 😫 👍