ãã®è¨äºã¯ã³ãããAdvent Calendar 2024 07æ¥ç®ã®è¨äºã§ãã
ã³ãããã®ã¨ã³ã¸ãã¢ããã¶ã¤ãã¼ãPdMããéãããã¢ããã³ãã«ã¬ã³ãã¼ã§ãã ã³ãããã¯ã家æåãã¨ãããã¼ããåãã¾ãæ§ã ãªèª²é¡ã®è§£æ±ºãç®æãä¼ç¤¾ã§ã ããã®ä¸æ©ãæ¯ããã¢ããªããããªããªã©ãéå¶ãã¦ãã¾ãã
ããã«ã¡ã¯ãã³ãããAndroidã¨ã³ã¸ãã¢ã®ä¸å³¶(id:nacatl)ã§ãã æ©ããã®ã§ã³ãããã«ã¸ã§ã¤ã³ãã¦ãã1å¹´åè¿ããªãã¾ããã ä»åã¯ãããªAndroidã¢ããªã«ãããJetpack Composeã®Previewã«ã¤ãã¦ç´¹ä»ãããã¾ãã
- èæ¯
- ãããªAndroidã§ã®ç¾ç¶
- if ã«ã¼ã
- ãããã«
èæ¯
ãããªAndroidã¢ããªã§ã¯ãæ¨ä»ã®Androidéçºäºæ ã«æ¼ããJetpack Composeãåãå ¥ãå§ãã¦ãã¾ãã æ°ããéçºããç®æã¯ãç»é¢åä½ã§ãªãã¦ããä¾ãã°RecyclerViewã®ListItemåä½ãªã©ãããç½®ãæãã¦ããã¨ããã§ãã
ããã«ããæè¿ã§ã¯Navigation Composeã®æ¡ç¨ãå§ãã¦ãã¾ãã 詳ããã¯åAdvent Calendarã®04æ¥ç®ã®è¨äºãã覧ãã ããã
ãã¦ãComposeã¨ããã°ãéçºä¸ã«Previewãæ´»ç¨ããã¦ããæ¹ãå¤ãã¨æãã¾ãã
xmlã«ããPreviewã«æ¯ã¹ãColumnãªã©ã§ä¸¦ã¹ããã¨ã§Stateã®éãã«ããã表示ãæ¯è¼ãããããªã£ã¦ãã¾ãã ããã«ãã©ã¤ã/ãã¼ã¯è¨å®ãæåãµã¤ãºè¨å®ãã¯ããã¨ãã¦ãPreviewã¢ããã¼ã·ã§ã³ã®å¼æ°ã«ãã£ã¦ãã¾ãã¾ãªè¨å®ãæ±ããç¹ãããã¾ãã ç·åçã«åãåããåä¸ããæ´åãããã¨ã§UIã«ãããä¸ç¨®ã®ã¦ããããã¹ãã®ããã«æ±ããã¨æãã¦ãã¾ãã
ä¸æ¹ã§ãPreviewãKotlinã§æ¸ã以ä¸ãPreviewã³ã¼ãã®éã¨ç½®ãå ´ã«èª²é¡ãæãã¦ãã¾ãã æ軽ãã§è¨ãã°ãå®æ ã®ã³ã¼ãã¨åããã¡ã¤ã«ã«ç½®ããã¡ã§ãã ããããããããã¨Previewã®ãã«ãã®å½±é¿ããå¢ããããã¨Android Studioã®ããã©ã¼ãã³ã¹ã«å½±é¿ãåºããã¨ãããã¾ãã å ãã¦ä¸è¿°ã®ããã«ã¦ããããã¹ãã®ããã«æ±ããã¨ããã¨ããµã³ãã«ã«ããStateãå¢ãã¦ããã®ã§ããé¡èã«ãªãã¾ãã
ãã®è¨äºã§ã¯ããããã®èª²é¡ã«å¯¾ãã¦ã®ãããªAndroidã§ã®Composeã®ã¢ããã¼ããããã³ãã®å ã®æ´»ç¨ããç´¹ä»ãã¾ãã
ãããªAndroidã§ã®ç¾ç¶
åæã¨ãã¦ãå·çæç¹ã§ Android Studio Ladybug | 2024.2.1 Patch 2ãKotlin 2.0.21ãå©ç¨ãã¦ãã¾ãã
Previewã³ã¼ãã®ç®¡ç
1. ãã¡ã¤ã«ãåãã
ã¾ãã¯åºæ¬ã¨ããæãã§ããã¡ã¤ã«ãåå²ãã¾ãã
ãããªAndroidã§ã¯ã.preview
ã¨suffixã追å ãããã¨ã§ã©ã®Composeã®Previewãã示ãã¦ãã¾ãã
FeatureFooItem.kt
FeatureFooItem.preview.kt
æåã¯ããã ãã§ãååå¹æãçºæ®ãã¾ãã åé層ã«ç½®ãã°Composeã®visibilityãinternalãªã©ã«çµãããããªãã¾ãã ããããComposeç½®æãé²ã¿ãã¡ã¤ã«ãå¢ããã«ã¤ããããã¸ã§ã¯ãã®ãã¡ã¤ã«ããªã¼ãé·ããªãç ©ããããåºã¦ãã¾ãã
2. File Nestingãæ´»ç¨ãã
IntelliJ IDEAã«ã¯ãã«ã¼ã«ãæå®ãã¦ãã¡ã¤ã«ãã¾ã¨ãã¦ãã¹ãã£ã³ã°ã§ããæ©è½ãããã¾ãã
IntelliJ IDEAã«ããæ©è½ã¨ãããã¨ã¯ãã¤ã¾ãAndroid Studioã§ã使ããæ©è½ã§ãããã¾ãï¼ãã¡ããåºã«ãªã£ããã¼ã¸ã§ã³ã¯ç¢ºèªå¿ è¦ã§ããï¼ã
ãã®æ©è½ã¯ãçè
ãFlutterãéçºãã¦ããã¨ãã«ããå©ç¨ãã¦ãã¾ããã
å½æãDartã«ããã¦Kotlin data classç¸å½ã®æ©è½ãæ±ãããã«freezedãå©ç¨ãã¦ããã¾ããã
freezedã¯AnnotationProcessorã®ããã«ãdata classçãªæ©è½ãJsonãã¼ãµã¼ç¸å½ã®æ©è½ãªã©ã®é¨åãã³ã¼ãçæãã¦ãããããã±ã¼ã¸ã§ãã
ãã®çæãããã³ã¼ã㯠foo.dart
ã«å¯¾ã㦠foo.freezed.dart
foo.g.dart
ãªã©ã¨ãã£ãå¥ãã¡ã¤ã«ã§å½¢æãããèªåçæã§ããããéçºè
èªèº«ãç·¨éãããã¨ã¯ããã¾ããã
æ®æ®µã¯åç
§ããªããååã®ä¼¼ããã¡ã¤ã«ãããªã¼ã«å¸¸é§ãã¦ãã¾ãç
©ããããã©ããã¦ããã£ããããããããã¾ã¨ããããã«éå®ãã¦ãã¾ããã
ãããComposeã®Previewãã¡ã¤ã«ç®¡çã«å¿ç¨ãã¾ãã
ããã§Previewãã¡ã¤ã«ãæãããããããã«ãªããåé層ã«ä¸¦ã¹ã¦ä¸è¦§æ§ãç¶æãã¤ã¤ãããªã¼ã®å§è¿«ãç·©åããã¾ããã ã¤ã³ãã³ããä»ããã¨ã§è¦åããããããªãã¾ãã
Previewã³ã¼ãã®æ´»ç¨
管çãã²ã¨æ®µè½ããã¨ããã§ãæ´»ç¨ã®æ¹ã«ç§»ãã¾ãã
æåã«åæ説æã§ããããããªAndroidã¯æ¥ç¶ç°å¢ã®åå²ã Product Flavor ã® dev
prod
ã§ç®¡çãã¦ãã¾ãã
社å
ã«ãã¹ãé
å¸ãã dev
ã«ã¯ãvariantå°ç¨ã®ã³ã¼ãã¨ãã¦éçºç¨ç»é¢ã®Activityã追å ãã¦ãã¾ãã
çµè«ããè¨ãã¨ããã®éçºç¨Activityã¨ãã¦Previewã³ã¼ãããã®ã¾ã¾æµç¨ãããµã³ãã«ã³ã³ãã¼ãã³ãç»é¢ã®æ´åãé²ãã¦ãã¾ãã ç®çã¨ãã¦ã¯ä»¥ä¸ã®äºã¤ã§ãã
- ããããã¢ãã¯ãã¼ã¿ã§ã®UIå®è£
ãããªãªã¼ã¹ã³ã¼ãã®UI層ã¨åãé¢ãã¤ã¤ããµã¼ãã¼å´ã®éçºã¨ä¸¦è¡ãã¦è¡ãã
- 表示ã ããªãç´ ã®Previewã ãã§åäºåã§ãããå®æ©ä¸ã®ç¢ºèªããããå ´åã«æ¢åUIã¨éé¢ãã¦è¡¨ç¤ºã§ããã®ã¯ä¾¿å©ã§ã
- éã¨ã³ã¸ãã¢ã¡ã³ãã¼ãé
å¸ãããã¢ããªã§Previewã確èªã§ãã
- ãããããã®æ¡å¼µããããã¨æã£ããã£ããã§ããå®éã®æä½ã§ã¯ä½ãã«ãããã¿ã¼ã³ãªã©ã®è¡¨ç¤ºã確èªã§ãããããéçºä¸ã®é£æºãå¼·ãããã¾ã
PreviewComponentActivity ~ ComponentPreviewRoute
ãã¼ã¹ã¨ãªãActivity ~ Route Composeã§ã¯ããã¾ãç¹æ®ãªãã¨ã¯ãã¦ãã¾ããã 表示ãããPreviewãé¸æ表示ã§ããããã¼ã¯ã¢ã¼ããªã©ã®è¨å®ãå¤æ´ã§ããããã«ããããªã«Stateããã³UIãçµã¿ç«ã¦ãã ãã§ãã å¼·ãã¦è¨ãã°Now in Androidãåèã«ããªãªã¼ã¹ã³ã¼ãã«å è¡ãã¦EdgeToEdgeãã¢ãããã£ãã¬ã¤ã¢ã¦ã対å¿ã軽ãåãè¾¼ãã§ããç¨åº¦ã§ãã
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { // ãã¼ã¯è¨å®remember val defaultDark = isSystemInDarkTheme() var isDarkTheme by rememberSaveable { mutableStateOf(defaultDark) } DisposableEffect(isDarkTheme) { enableEdgeToEdge( ~~~~, ) onDispose {} } ComponentPreviewRoute( isDarkTheme = isDarkTheme, windowAdaptiveInfo = currentWindowAdaptiveInfo(), onDarkThemeChanged = { isDarkTheme = it }, onBackPressed = { onBackPressedDispatcher.onBackPressed() }, ) } }
åPreviewã®å·¥å¤«
åºæ¬çã«ã¯ãã®ã¾ã¾å¼ã³åºããã¨è¨ã£ã¦ããå¤å°æãå ããå¿ è¦ã¯ããã¾ããã
1. MaterialThemeã§æ¬ããªããã¦ãã¾ãã¨DarkThemeãåãæ¿ãããªã
æ®éã«Previewãä½ãå ´åãPreviewã¢ããã¼ã·ã§ã³ãä»ããComposableã«ãã®ã¾ã¾Themeããå ¨é¨æ¸ããã¨ãå¤ãã¨æãã¾ãã ã§ãããPreviewç¨ãã«ãã ãã§ãªãå¤ãããå¼ã³åºããããã«ããå ´åãThemeã§æ¬ã£ã¦ãã¾ã£ã¦ããã¨å¼ã³åºãå´ã®Themeè¨å®ãä¸æ¸ããã¦ãã¾ãã¾ãã ãããåé¿ããã«ã¯Previewç¨ã®æ¬ä½ã¨ãã¢ããã¼ã·ã§ã³ã®ä»ããThemeå¼ã³åºããåãåãã¾ãã
// FeatureFooItem.preview.kt internal val sampleFeatureFoo = FeatureFoo(id = 1, ~~) /** * Preview表示ç¨ãã«ãã¯ãããåç §ãã */ @Preview( heightDp = PreviewHeight, // LazyColumnã«ã§ããããã«ããé¢ä¿ãããããã®è¾ºãã¢ããªå ¨ä½ã§å°ãæ´åãã¦ãã¾ã ~~, ) @Composable private fun FeatureFooItemPreviewBase() { MaterialTheme( isDarkTheme = isSystemInDarkTheme(), ) { FeatureFooItemPreview( modifier = Modifier.height(PreviewHeight), baseFeatureFoo = sampleFeatureFoo, ) } } /** * ãã¬ãã¥ã¼æç»ã®å®æ ãéçºç¨Activityããã®å¼ã³åºãã§ã¯ãã¡ãã使ã */ @Composable internal fun FeatureFooItemPreview( modifier: Modifier = Modifier, baseFeatureFoo: FeatureFoo, ) { val sampleList = listOf( sampleFeatureFoo, sampleFeatureFoo.copy( /* 並ã¹ã¦è¦ããå¥ãã¿ã¼ã³ã«ãªããããã£ã¼ã«ããå¤æ´ */ ), ~~, ) LazyColumn( modifier = modifier, ) { items(sampleList.size) { index -> FeatureFooItem( ~~ ) } } }
2. åºæ¬ã®ãµã³ãã«ãã¼ã¿ãç·¨éã§ããããã«ãã
åã»ã¯ã·ã§ã³ã§æ¸ããããã«ããµã³ãã«ã¨ãªããã¼ã¿ã¯Previewã³ã¼ãã®ãã¡ã¤ã«ã«ç¨æãã¦ãã¾ãã
ãããã®ãã¼ã¿ãéçºç¨Activityã®UiStateã«æããã¾ãããããã«ViewModelã®æã¤StateFlowã®å½¢ã§ä¿æãã¦ãã¾ãã Flowã«ããçç±ã¨ãã¦ã¯ãå®æ©ä¸ã§ãµã³ãã«ãã¼ã¿ãå¤æ´å¯è½ã«ããç®çã§ãã ããã¯æã Androidã¨ã³ã¸ãã¢ã®éçºã¨ããããã¯ãç¹ã«PdMããã¶ã¤ãã¼ãéã¨ã³ã¸ãã¢ã®æ¹ã ã®æ¤è¨¼ç®çã«å½¹ç«ã¡ã¾ãã
// FeatureFooPreviewViewModel.kt val uiState: StateFlow<FeatureFooPreviewUiState> field = MutableStateFlow(FeatureFooPreviewUiState()) // 表示ããPreviewã®åãæ¿ã fun changeComponent(component: FeatureFooPreviewComponent) = uiState.update { it.changeComponent(component) } // ãã¼ã¹ã¨ãªãåºæ¬ãµã³ãã«ã®ç·¨é fun updateBaseFeatureFoo(featureFoo: FeatureFoo) = uiState.update { it.updateBaseFeatureFoo(featureFoo) } // ãªã»ãã fun initBaseFeatureFoo() = uiState.update { it.initBaseFeatureFoo() }
@Stable data class FeatureFooPreviewUiState( val currentComponent: FeatureFooPreviewComponent = FeatureFooPreviewComponent.ITEM_A, val baseFeatureFoo: FeatureFoo = defaultFeatureFoo, ) { fun changeComponent(component: FeatureFooPreviewComponent) = copy( currentComponent = component, ) fun updateBaseFeatureFoo(featureFoo: FeatureFoo) = copy( baseFeatureFoo = FeatureFoo, ) fun initBaseFeatureFoo() = updateBaseFeatureFoo(defaultFeatureFoo) } // Composeå´ã§Preview funã®å¼ã³åºããåãæ¿ããããã®enum enum class FeatureFooPreviewComponent : PreviewComponent { ITEM_A, ITEM_B, ITEM_C, ; }
å©ç¹
ãã®Preview表示ã®éçºç¨Activityãéç¨ãã¦è¯ãã£ãç¹ãç´¹ä»ãã¾ãã
- éçºä¸ã®UIãã¹ãçå©ç¨
- Previewã®é åãè¶ ãããããªæ°ã§ããLazyColumnãªã©ã§ã¹ã¯ãã¼ã«ããªãã確èªã§ãã
- ãã¿ã³ãªã©ã¦ã¼ã¶ã¼ã¢ã¯ã·ã§ã³ãåããã¦ãã¹ãã§ããï¼é©å½ã«Toast表示ãªã©ãããã ãã§ãé ç½®ãã¹ç¢ºèªã«ãªãã¾ãï¼
- ã¢ãããã£ãã¬ã¤ã¢ã¦ã対å¿ã®å®æ©ãã¹ãã«ã転ç¨ã§ãã
- ç»é¢ãåå¨ããããã«ãªããããMagicPodçã®UIãã¹ããµã¼ãã¹ã«ã転ç¨ã§ãã
- éã¨ã³ã¸ãã¢ã¨ã®ã³ãã¥ãã±ã¼ã·ã§ã³
- Firebase App Distributionã§ã®é å¸ãéãã¦ããµã¼ãã¼ã«ä¾åãããå®æ©ã§æ軽ã«PdM / ãã¶ã¤ãã¼ã¸ã®è¡¨ç¤ºå¥ç¢ºèªãªã©ãè¡ãã
- ãµã³ãã«ãã¼ã¿ã®ç·¨éã¾ã§å®è£ ã§ããã°ãã確èªãæã
åè ã«ã¤ãã¦ã¯ç¹ã«è¤éãªè¡¨ç¤ºåå²ä»æ§ããããã¼ãã«å¯¾ãã¦AndroidViewâComposeã«ç½®æããéã®å©ç¹ã大ããã£ãã¨æãã¾ãã ã¾ã å§ãã¦æ¥ãæµ ãäºãããå¾è ã«ã¤ãã¦ã¯ã¾ã å¨ç¥ãå§ãã¦ããã¨ããã§ãããã確èªããããã¦ä¾¿å©ãã¨ãã声ãé ãå§ãã¦ãã¾ããä»å¾ã浸éããã¦ããããã§ãã
æ¬ ç¹
ã¯ã£ããè¨ã£ã¦ãã¾ãã°ç¸å¿ã®ã³ã¼ãã£ã³ã°å·¥æ°ã¯ããã¾ãã
表示ã«é¢ããå·¥æ°
Previewèªä½ã¯ãããããã®æ´»ç¨ããªãã¦ãä½ãã§ãããããå¾ããã®è¿½å ã¯enumã¸ã®è¿½å ç¨åº¦ã§æ¸ã¿ã¾ãã ãªã®ã§ããã®æ®µéã§ã¯æåã®ç»é¢ä½æ / Previewã¸ã®å¤æ´å¨ãã主ãªå·¥æ°ã¨ãªãã¾ãã
ç·¨éã«é¢ããå·¥æ°
åãµã³ãã«ãã¼ã¿ããããã«ã¤ãã¦ç·¨éUIãä½ãå¿ è¦ãããã®ã§ããã¡ãã«é¢ãã¦ã¯ãããªãã«è¦è¾¼ãå¿ è¦ãããã¾ãã ãããªAndroidã§ããç¾ç¶ã§ã¯ééã縫ã£ã¦å®è£ ãé²ãã¦ãããããªç¶æ³ã§ãã
if ã«ã¼ã
ååãã¾ãã§ãç¾ç¶ã§ã¯æ¡ç¨ãã¦ãã¾ããããããããã®ãããã®ã§ã¯ï¼ãã¨èãã¦ããèæ¡ã軽ãç´¹ä»ãã¾ãã
previewã³ã¼ãã®ç®¡ç
File Nestingã®æ¬ ç¹
File Nestingã®æ´»ç¨ã«ãã£ã¦ãã¡ã¤ã«åå²ã§èµ·ããåé¡ã¯ã»ã¼è§£æ±ºãã¾ãã ã§ãããAndroid Studioã®ã¤ã³ã¿ã¼ãã§ã¼ã¹é¢ã§å°å³ã«å½±é¿ãããã¾ãã
ããã«ã¯ãªãã¯ã§è¦ªãã¡ã¤ã«ãéããªã
é常ã®ãã£ã¬ã¯ããªã¨åãããã«ãããã«ã¯ãªãã¯ã¯ããªã¼ã® collapse / expand ã«æ¶è²»ããã¾ãã
åé¿çã¯ã¯ã³ã¯ãªãã¯ã§ãã©ã¼ã«ã¹ãå½ã¦ãå¾ã«enter
ã§ãããããã¯ãã¡ã¤ã«æ¤ç´¢ãªã©çµç±ã§éãã°ãã¤ãéãã§ãã
ãã ãã¯ããååã«ããã«ã¯ãªãã¯ããçããã¾ã ã«æãã¦ãã¾ããã
ãã¹ãã®åãã¡ã¤ã«ã«å¯¾ããSelect Opened File
ãå¹ããªã
æ£ç´ãããä¸å ·åã§ã¯ï¼ï¼ã¨æã£ã¦ãã¾ãããå¹ããªããã®ã¯ããããããã¾ããã ããã«ã¤ãã¦ã¯ã親ãã¡ã¤ã«ã«å¯¾ãã¦å®è¡ãããããªããã¨æããã¾ãã
3. devãã«ãã®ãã£ã¬ã¯ããªã«ç½®ã
File Nestingã¯ç¢ºãã«ä¾¿å©ãªã®ã§ãããä¸è¿°ã®ããã«ã¡ãã£ã¨ã¯ã»ãããã¾ãã 代æ¿æ¡ã¨ãã¦ç¾ç¶ã¼ãããèãã¦ããã®ã¯ããéçºç¨Activityç¨ã«æ¸ããComposeã¨åæ§ã«devé ä¸ã«ç½®ãã¦ãã¾ãããã¨ã§ãã ãããããªãªã¼ã¹ãã«ãã«ã¯å½±é¿ããªããã¡ã¤ã«ã§ããã妥å½æ§ã¯æãã¾ãã
ãã ãã®ææ³ã§ã¯ãpackageä¸ã¯åä¸ã«ãªãã¾ããå®éã®ç½®ãå ´ã¯é¢ãã¦ãã¾ãã¾ãã®ã§ããªã¼ä¸ã§ã®ä¸è¦§æ§ã¯å£ãã¾ãã
ã©ãããã«ããããã¬ã¼ããªãã¯ä½ãããçºçããããªã¨ãæãã¦ãã¾ãã
ãããã«
ä»åã¯ããããªAndroidã«ãããCompose Previewã®ç®¡çæ¹æ³ã¨æ´»ç¨ã«ã¤ãã¦ç´¹ä»ããã¦ããã ãã¾ããã
ç´¹ä»ããæ¹æ³ãæ£è§£ãã©ããã¯ã¯ã£ããè¨ã£ã¦ããããã¾ãããã ä»å¾ãè²ã æãã¤ããããã¼ã å ã§ç¸è«ããæ¤è¨¼ãã¦ããããã¨æã£ã¦ãã¾ãã ãã®è¨äºãçæ§ã®Composeã©ã¤ããããè±ãã«ããä¸å©ã¨ãªãã°å¹¸ãã§ãã