ã¯ããã« ããã«ã¡ã¯ããã©ã³ããœãªã¥ãŒã·ã§ã³éçºæ¬éšFAANSéšã®ç°äžã§ããã·ã§ããã¹ã¿ããã®è²©å£²ãµããŒãããŒã«ãFAANSãã®Androidã¢ããªãæ
åœããŠããŸããæ¬èšäºã§ã¯ãFAANSã¢ããªã§FragmentããŒã¹ã®ããã²ãŒã·ã§ã³ããNavigation Composeãžã®ç§»è¡ããæ©èœéçºãšäžŠè¡ããŠã¢ãžã¥ãŒã«åäœã§é²ããåãçµã¿ã玹ä»ããŸãã çŽå幎ã§ã8ã€ã®æ©èœã¢ãžã¥ãŒã«ã®ãã¡5ã€ã®ç§»è¡ãå®äºããŸããããŽãŒã«ã¯ã ç»é¢é·ç§»ã®ç®¡çãActivityã«1ã€çœ®ããNavHostïŒNavigation Composeã§ç»é¢é·ç§»ã管çããComposableïŒãžéçŽãã ããšã§ãããã ãæ©èœéçºãšãªãªãŒã¹ãæ¢ããããã«ã¯ãããªããããFragmentãæ®ãããŸãŸ ã¢ãžã¥ãŒã«åäœã§ç§»è¡ãç©ã¿äžãã 圢ãåã£ãŠããŸãã以äžããã®é²ãæ¹ãšç§»è¡ã®äžã§èŠããŠãã課é¡ãä»åŸã®èŠéããæžããŸãã ç®æ¬¡ ã¯ããã« ç®æ¬¡ èæ¯ã»èª²é¡ ãã«ãã¢ãžã¥ãŒã«æ§æãšç»é¢é·ç§» UIã®Composeåãšäºé管ç ç§»è¡å¯Ÿè±¡ã®èŠæš¡ ç§»è¡æ¹é ãŽãŒã«ã𿮵é å
±åãããããã®éžæè¢ Navigation 3ã§ã¯ãªãNavigation Composeãéžãã çç± 1. ã¢ãžã¥ãŒã«åäœã®ç§»è¡ 1-1. æ¡çšããæ§é æªç§»è¡ã®Fragmentãžã®æ©æž¡ã 1-2. é²ãæ¹ã®æŽå 1-3. ç§»è¡åã®é·ç§»ã®èšé² 1-4. ç»é¢ã®ç§»è¡ ãã€ã¢ãã°ã®ç§»è¡ é·ç§»ãã€ãã³ãã§éç¥ããŠããViewModelã®ç§»è¡åå¯Ÿå¿ Fragmentã®ã©ã€ããµã€ã¯ã«ãšã®å¯Ÿå¿ 1-5. PRã®åãæ¹ 2. 芪åé¢ä¿ã«ããã¢ãžã¥ãŒã«ã®çµ±å 2-1. NavGraphBuilderæ¡åŒµãšããŠã®ã°ã©ãåé¢ 2-2. äžæçãªã¢ãžã¥ãŒã«éäŸåã®æ±ã 3. ActivityçŽäžã®åäžNavHostãžã®çµ±å 3-1. ã«ãŒãåŽã®çŸç¶ 3-2. çµ±åã®èŠéã ç§»è¡ã®äžã§èŠããŠããèª²é¡ èª²é¡1ïŒæ»ããã¿ã³ã®é£æã§ç»é¢ããã¯ã€ãã¢ãŠããã 課é¡2ïŒåçããã«ãŒããæ»ã£ãçŽåŸã®é·ç§»ãçºç«ããªã 課é¡3ïŒæªç§»è¡ã®ç»é¢ãæãã§ãããã¯ã¹ã¿ãã¯ã¯å€±ãããªãã£ã 課é¡4ïŒããã ã·ãŒããéãçŽããŠãåã®å
容ã衚瀺ããã 課é¡5ïŒç»é¢é·ç§»ãã¹ããæžããã«ã©ãæ
ä¿ããã ç§»è¡ã®é²æãšçŸæç¹ã®å¹æ æåŸã« èæ¯ã»èª²é¡ ãã«ãã¢ãžã¥ãŒã«æ§æãšç»é¢é·ç§» FAANSã¢ããªã¯ãã¢ãžã¥ãŒã«ããšã«ããã²ãŒã·ã§ã³ã°ã©ããæã€ãã«ãã¢ãžã¥ãŒã«æ§æã§ããåã¢ãžã¥ãŒã«ã®ã°ã©ããã«ãŒãã®ã°ã©ããã <include> ã§åã蟌ã¿ãé·ç§»å
ã¯ãã¹ãŠFragmentãšããŠå®çŸ©ããŠããŸããã¢ãžã¥ãŒã«ã¯æ©èœåäœã§åããããšãæå³ããŠããŸãããæŽå²çãªçµç·¯ã§ã¢ãžã¥ãŒã«ã®å¢çãšæ©èœã®å¢çãäžèŽããŠããªãç®æããããããèªäœãæè¡çè² åµã®1ã€ã«ãªã£ãŠããŸããããã§ããããã²ãŒã·ã§ã³ã°ã©ããšç»é¢é·ç§»ã®ãŸãšãŸããšããŠã¯ã¢ãžã¥ãŒã«ãåäœãšããŠæ©èœããŠãããããä»åã®ç§»è¡ã¯ ãã®ã¢ãžã¥ãŒã«ãåäœãšã㊠é²ããæ¹éãåããŸããã ããã²ãŒã·ã§ã³ã°ã©ãïŒXMLïŒã¯æ¬¡ã®ããã«å®çŸ©ããŠããŸããã«ãŒãã®ã°ã©ããåã¢ãžã¥ãŒã«ã®ã°ã©ãã <include> ã§åã蟌ã¿ãã¢ãžã¥ãŒã«ã®ã°ã©ãã«ã¯ç»é¢ã® <fragment> ãšé·ç§»ã® <action> ã䞊ã³ãŸãã <!-- ã«ãŒãã®ã°ã©ã: åã¢ãžã¥ãŒã«ã®ã°ã©ããåã蟌ã --> <navigation android : id = "@+id/nav_root" app : startDestination = "@id/SplashFragment" > <fragment android : id = "@+id/SplashFragment" android : name = "...SplashFragment" /> <include app : graph = "@navigation/module_a" /> <include app : graph = "@navigation/module_b" /> </navigation> <!-- ã¢ãžã¥ãŒã«Aã®ã°ã©ã: ç»é¢ããšã« fragment ãš action ãå®çŸ©ãã --> <navigation android : id = "@+id/nav_module_a" app : startDestination = "@id/SettingsFragment" > <fragment android : id = "@+id/SettingsFragment" android : name = "...SettingsFragment" > <action android : id = "@+id/action_Settings_to_Account" app : destination = "@id/AccountFragment" /> </fragment> <fragment android : id = "@+id/AccountFragment" android : name = "...AccountFragment" > <action android : id = "@+id/action_Account_to_ChangeEmail" app : destination = "@id/ChangeEmailFragment" /> </fragment> <fragment android : id = "@+id/ChangeEmailFragment" android : name = "...ChangeEmailFragment" /> </navigation> UIã®Composeåãšäºé管ç XMLã®ã¬ã€ã¢ãŠããJetpack Composeã«æžãæããäœæ¥ãç¶ããæ©èœã¢ãžã¥ãŒã«ã®ç»é¢ã¯ãã¹ãŠComposeåãå®äºããŸãããæ®ã£ãŠããã®ãç»é¢é·ç§»ã®å±€ã§ããç»é¢é·ç§»ãFragmentããŒã¹ã®ãŸãŸãªã®ã§ãComposeåããç»é¢ãé·ç§»å
ãšããŠç»é²ããå¿
èŠããããŸããããã®ããã«ã ComposeView ã setContent ããã ãã®Fragmentãã1ç»é¢ããšã«çšæããŠããŸãããããããŠãããã²ãŒã·ã§ã³ã°ã©ãã« <fragment> ãš <action> ãæžãç¶ããå¿
èŠããããŸããã // ç§»è¡å: äžèº«ã¯Composeãªã®ã«ãé·ç§»ã®ããã ãã«æ®ã£ãŠããFragment @AndroidEntryPoint class ExampleFragment : Fragment() { private val viewModel: ExampleViewModel by viewModels() private val args: ExampleFragmentArgs by navArgs() // åŒæ°ã¯XMLã® <argument> ããçæãããã¯ã©ã¹ã§åãåã override fun onCreateView(...): View = ComposeView(requireContext()).apply { setContent { FaansTheme { ExampleRoute( id = args.id, viewModel = viewModel, onNavigateBack = { findNavController().popBackStack() }, ) } } } } ç»é¢ã®å®è£
ã¯Composeã«ç§»ã£ãŠããã®ã«ãé·ç§»ãŸããã¯3ãæã«åããããŸãŸã§ããé·ç§»å
ã®å®çŸ©ã¯ããã²ãŒã·ã§ã³ã°ã©ãã«ãé·ç§»ã®å®è¡ã¯Fragmentã® findNavController() ã«æžããŸããç»é¢éã§æž¡ãåŒæ°ã¯ãXMLã® <argument> ããçæãããSafe Argsã®ã¯ã©ã¹ã§åãåããŸããComposeã®ç»é¢ã1ã€è¶³ããã³ã«FragmentãšXMLã®äž¡æ¹ãè§Šãã äºé管ç ããç©ã¿äžãã£ãŠããŸããã ç§»è¡å¯Ÿè±¡ã®èŠæš¡ é
ç® ç§»è¡å ããã²ãŒã·ã§ã³ã°ã©ãïŒXMLïŒ 12ãã¡ã€ã«ïŒæ©èœã¢ãžã¥ãŒã«å9ãã¢ããªå
±é3ïŒ é·ç§»å
ïŒ <fragment> ïŒ 90 é·ç§»å
ïŒ <dialog> ïŒ 50 ç»é¢ã®Composeåãããåããããããç»é¢é·ç§»ã®å±€ã«æãä»ããããç¶æ
ã«ãªããŸãããå
¬åŒã®ç§»è¡ã¬ã€ãããNavigation Composeãžåãæ¿ããåæãšããŠã é·ç§»å
ããã¹ãŠComposableã§ããããš ããæããŠããŸãã You can migrate to Navigation Compose once you're able to replace all of your Fragments with corresponding screen composables . Screen composables can contain a mix of Compose and View content, but all navigation destinations must be composables to enable Navigation Compose migration. Until then, you should continue using Fragment-based Navigation component in your interop View and Compose codebase. â Migrate Jetpack Navigation to Navigation Compose â Android Developers èŠçŽïŒãã¹ãŠã®é·ç§»å
ãComposableã«ãªããŸã§ã¯ãFragmentããŒã¹ã®ããã²ãŒã·ã§ã³ã䜿ãç¶ããå¿
èŠãããã äžæ¹ã§ãã¢ããªå
šäœãäžåºŠã«åãæ¿ããéžæè¢ã¯ãããŸããã§ããã æ©èœéçºãšäžŠè¡ããŠå°ããã€ããããåŸæ»ããªãé²ããæ¹æ³ ãå¿
èŠã§ããã ç§»è¡æ¹é ãŽãŒã«ã𿮵é Navigation Composeã§ã¯ã NavHost ãšãã1ã€ã®Composableãç»é¢ã®åãæ¿ããšããã¯ã¹ã¿ãã¯ã管çããŸããå
¬åŒã®åºæ¬æ§æã§ã¯ããããActivityã«1ã€çœ®ããŸãã ãŽãŒã«ã¯ãActivityçŽäžã«1ã€ã®Compose NavHostã眮ããXMLã®ããã²ãŒã·ã§ã³ã°ã©ããš NavHostFragment ããªããããšã§ãã ãããŸã§ãäžæ°ã«ã¯é²ããããªãã®ã§ã3段éã«åããŠããŸãã ã¢ãžã¥ãŒã«åäœã®ç§»è¡ ïŒèµ·ç¹ã®Fragmentãæ®ãããã®å
éšã«NavHostã眮ããŠãã¢ãžã¥ãŒã«å
ã®ç»é¢ãComposeãžç§»ãïŒã³ãŒãã¯1-1ïŒ èŠªåé¢ä¿ã«ããã¢ãžã¥ãŒã«ã®çµ±å ïŒç¹å®ã®1ã€ã®ã¢ãžã¥ãŒã«ããããé·ç§»ãããªãã¢ãžã¥ãŒã«ïŒãã®èšäºã§ã¯èŠªåé¢ä¿ãšåŒã³ãŸãïŒã®ç§»è¡ãå®äºãããããã®ã°ã©ãã芪åŽã®NavHostãžçµã¿èŸŒãïŒã³ãŒãã¯2-1ïŒ ActivityçŽäžã®åäžNavHostãžã®çµ±å ïŒãã¹ãŠã®ã¢ãžã¥ãŒã«ãæã£ã段éã§ãActivityã«1ã€ã ã眮ããNavHostãžäžæ¬ã§çµã¿èŸŒãïŒã³ãŒãã¯3-2ïŒ ä»¥äžããã®3段éãé ã«èª¬æããŸããã³ãŒãã¯èšå®ã¢ãžã¥ãŒã«ãéãã®äŸã«ããŠãåãã³ãŒãããã®3段éã§ã©ãå€ãããã远ãã圢ã«ããŠããŸãã3ã¯æ¬¡ã®ãã§ãŒãºãšããŠèšç»ããŠããæ®µéã§ãå®éã®ç§»è¡äœæ¥ã¯æ¬èšäºã®ã¹ã³ãŒãå€ã®ãããçŸç¶ãšèŠéãã ããæžããŸãã å
±åãããããã®éžæè¢ FragmentããŒã¹ã®ããã²ãŒã·ã§ã³ãšNavigation Composeãå
±åãããæ¹æ³ã¯ãå
¬åŒã®çžäºéçšAPIãå«ããŠããã€ããããŸãã æ¹æ³ æŠèŠ åããŠããå Žé¢ ããã²ãŒã·ã§ã³ã°ã©ãïŒXMLïŒã« composable ã眮ã Navigation 2.8.0 ã® navigation-fragment-compose ã ComposableNavHostFragment ã䜿ããšãã°ã©ãã« <composable> ãæžãã æ¢åã®ã°ã©ãæ§é ãä¿ã£ããŸãŸãç»é¢åäœã§Composeã«çœ®ãæããŠããããå Žå Composeã®NavHostã®äžã«Fragmentã眮ã Fragment 1.8.0 ã® fragment-compose ã AndroidFragment ã§Composeéå±€ã®äžã«Fragmentãé
眮ã§ããç¶æ
ã®ä¿åã»åŸ©å
ã«ã察å¿ããŠãã NavHostãå
ã«ComposeåŽãžç§»ããæ®ã£ãFragmentç»é¢ãåŸãã眮ãæãããå Žå èµ·ç¹ã®Fragmentãæ®ãããã®å
éšã«NavHostã眮ã å€åŽã¯FragmentããŒã¹ã®ããã²ãŒã·ã§ã³ã®ãŸãŸãã¢ãžã¥ãŒã«å
ã®ç»é¢ã ãã composable<T> ãžç§»ããŠãã ã¢ãžã¥ãŒã«åäœã§ç§»è¡ãå®çµããããå ŽåïŒä»åæ¡çšïŒ ç§ãã¡ã¯ã 1ã€ã®ã¢ãžã¥ãŒã«ãæåŸãŸã§ããåã£ãŠããæ¬¡ãžé²ã ãé²ãæ¹ã培åºããŠããããã3ã€ç®ãéžã³ãŸãããã¢ãžã¥ãŒã«ã®å
åŽã¯ãé·ç§»å
ããã¹ãŠComposableããšããå
¬åŒã®åæãæºãããå€åŽã¯æ¢åã®FragmentããŒã¹ã®ããã²ãŒã·ã§ã³ããã®ãŸãŸåããŸããç»é¢åäœã§å°ããã€æ··ãããå Žåã¯1ã€ç®ãNavHostãå
ã«ç§»ãããå Žåã¯2ã€ç®ãéžæè¢ã«ãªããŸãã Navigation 3ã§ã¯ãªãNavigation Composeãéžãã çç± å·çæç¹ã§ã¯ Navigation 3 ãå®å®çãšããŠå
¬éãããŠããŸãããããªãæåããNavigation 3ãžç§»è¡ããã°ããã®ã§ã¯ããšæããããããããŸãããããã Navigation 3ã¯Composeå°çšã§ãFragmentãé·ç§»å
ã«ã§ããŸãã ããã®ããFragmentãæ®ãæ®µéã§ã¯ãŸãNavigation Composeãžç§»è¡ããNavigation 3ãžã®ç§»è¡ã¯ãã®åŸã®æ®µéãšããŸãããåå®å
šã«ãŒããæ¡çšããŠããã®ã§ã å
¬åŒã®ç§»è¡ã¬ã€ã ãåæãšãã圢ã¯ãã®ãŸãŸæºãããŸãã 1. ã¢ãžã¥ãŒã«åäœã®ç§»è¡ 1-1. æ¡çšããæ§é 1ã€ã®ã¢ãžã¥ãŒã«ã«æ³šç®ãããšãç§»è¡åŸã®ã°ã©ãã¯Composeã§ããæžããŸããé·ç§»å
㯠@Serializable ãªã¯ã©ã¹ã§è¡šãã NavHost ã« composable<T> ãšããŠç»é²ããŸãã // é·ç§»å
ã®å®çŸ©: XMLã® <fragment> ã®ä»£ãã sealed interface SettingsDestination { @Serializable data object Settings : SettingsDestination @Serializable data object Account : SettingsDestination @Serializable data class ChangeEmail( val currentEmail: String ) : SettingsDestination } // ã¢ãžã¥ãŒã«ã®NavHost: XMLã® <action> ã®ä»£ããã«ã©ã ãã§é·ç§»ãã @Composable fun SettingsNavHost( navController: NavHostController, onClickBackButton: () -> Unit , ) { NavHost(navController, startDestination = SettingsDestination.Settings) { composable<SettingsDestination.Settings> { SettingsRoute( onClickBackButton = onClickBackButton, onClickAccount = { navController.navigate(SettingsDestination.Account) }, ) } composable<SettingsDestination.Account> { AccountRoute(onClickChangeEmail = { email -> navController.navigate(SettingsDestination.ChangeEmail(email)) }) } composable<SettingsDestination.ChangeEmail> { backStackEntry -> val args = backStackEntry.toRoute<SettingsDestination.ChangeEmail>() ChangeEmailRoute(currentEmail = args.currentEmail) } } } ãã®NavHostãã©ãã«çœ®ãããå
±åã®èŠç¹ã§ããã¢ãžã¥ãŒã«Aã®ã°ã©ãïŒXMLïŒã«å±ããŠããFragmentã®ãã¡ã èµ·ç¹ã®1ã€ã ããæ®ãããã®äžã«NavHostã眮ããŸã ãã¢ãžã¥ãŒã«å
ã®ç»é¢ã¯ãã¹ãŠNavHostã®äžã® composable<T> ã«ãªããŸãã å
¬åŒã¬ã€ãã®æé ã§ã¯NavHostãActivityã«çœ®ããŸãããå€åŽã®ã°ã©ãïŒXMLïŒãæ®ãå¿
èŠããã£ããããã¢ãžã¥ãŒã«ã®èµ·ç¹ãšãªãFragmentã«NavHostã眮ããŸããã 1ã¢ãžã¥ãŒã«=1NavHost ãšããåäœã§ãèæ¯ã§æãããé·ç§»å
ããã¹ãŠComposableããšããåæãã¢ãžã¥ãŒã«ã®å
åŽã§æºãããŸãã èµ·ç¹ã®Fragmentã®è²¬åã¯ãNavHostã眮ããããšãšãå€åŽãšã®æ¥ç¶ãã ãã«ãªããŸãã @AndroidEntryPoint class SettingsFragment : Fragment() { override fun onCreateView(...): View = ComposeView(requireContext()).apply { setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner) ) setContent { FaansTheme { val navController = rememberNavController() SettingsNavHost( navController = navController, onClickBackButton = { findNavController().popBackStack() }, ) } } } } NavHost ã¯ãã®ãŸãŸäœ¿ãããç»é¢é·ç§»ã¢ãã¡ãŒã·ã§ã³ã None ã«çµ±äžããèãã©ãããŒãå
±éã¢ãžã¥ãŒã«ã«çœ®ããŠãåã¢ãžã¥ãŒã«ã¯ããã䜿ããŸããFragmentæä»£ã®é·ç§»ãšèŠãç®ã®å·®ãåºããªãããã§ãã ãªããäžã® SettingsNavHost ã®äŸã§ã¯ç°¡ç¥åã®ãã NavHost ãçŽæ¥åŒãã§ããŸãããå®éã«ã¯ãã®ã©ãããŒïŒ FaansNavHost ïŒã䜿ã£ãŠããŸãã @Composable fun FaansNavHost( navController: NavHostController, startDestination: Any , modifier: Modifier = Modifier, builder: NavGraphBuilder.() -> Unit , ) { NavHost( navController = navController, startDestination = startDestination, modifier = modifier, enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None }, popEnterTransition = { EnterTransition.None }, popExitTransition = { ExitTransition.None }, builder = builder, ) } æªç§»è¡ã®Fragmentãžã®æ©æž¡ã å
±åæã«ã¯ãã¢ãžã¥ãŒã«å
ã§ã¯ç§»è¡æžã¿ã ããé·ç§»å
ããŸã Fragmentããšããçµã¿åãããå¿
ãåºãŸãã å
åŽã®NavHostãããã£ããæããå€åŽã® findNavController() ã§é·ç§»ãããŸã ãæ¬¡ã®ç§»è¡å¯Ÿè±¡ãTODOã§æç€ºããŠãããšãåŸããæ¢ãæéãçããŸãã onNavigateToLegacyScreen = { args -> // TODO LegacyFragment ãScreenåããã compose ã® navController ã§é·ç§»ãã findNavController().navigate(R.id.LegacyFragment, args) } 1-2. é²ãæ¹ã®æŽå æåã®ã¢ãžã¥ãŒã«ã«ã¯ãæ¥åã®äžå¿ã§ã¯ãªããã®ã®äžå®ã®å©çšããããèšå®ç»é¢ã®ã¢ãžã¥ãŒã«ãéžã³ãŸãããå©çšãå°ãªããããšåé¡ã衚ã«åºããå€ããããšåé¡ãåºããšãã®åœ±é¿ã倧ããããã§ããç»é¢æ°ãå°ãªããé·ç§»ãäžçŽç·ã§ããããšãçç±ã®1ã€ã§ãã ãã®ã¢ãžã¥ãŒã«ã¯ç§ã1人ã§ç§»è¡ãã倿ã«è¿·ããšããã¯ããŒã ã«çžè«ããªããé²ããŸãããç§»è¡åŸã¯ã ç§»è¡æžã¿ãšæªç§»è¡ã®ã¢ãžã¥ãŒã«ãæ··åšããç¶æ
ã®ãŸãŸéåžžã®ãªãªãŒã¹ã«èŒã ãåé¡ãåºãªãããšã確ãããŸããã ãã®ããã§æé ãšæ³šæç¹ãClaude Codeã®ã¹ãã«ïŒæé æžãç»é²ããåŒã³åºããŠå®è¡ãããä»çµã¿ïŒã«èœãšã蟌ã¿ã2ã¢ãžã¥ãŒã«ç®ä»¥éã¯ããŒã ã§åæ
ããŠããŸããã¹ãã«ã¯ç§»è¡æé ã®å®è¡ã ãã§ãªãããã®ååŸã®æ®µåããèªååããŠããŠã䜿ããªããæçŽããç¶ããŠããŸãã åæ
ããŠé²ããããã«ã次ã®ãã®ãæŽããŸããã 鲿ãäžèЧã§ãã管çããŒãž ïŒã©ã®ã¢ãžã¥ãŒã«ãã©ããŸã§é²ãã§ãããã1ããŒãžã§èŠããåãäœæ¥ãã±ããïŒèª²é¡ç®¡çã¯JiraïŒã«ã¢ãžã¥ãŒã«åã®ã©ãã«ãä»ãããã®ããŒãžããã¢ãžã¥ãŒã«ããšã«çµã蟌ãã ã¢ãžã¥ãŒã«ããšã®éçŽãã©ã³ã ïŒåç»é¢ã®PRãéçŽãã©ã³ãã«ç©ã¿ãã¢ãžã¥ãŒã«å
šäœã®åäœç¢ºèªãçµãã£ãŠããéçºã®ã¡ã€ã³ãã©ã³ããžåã蟌ããæ©èœéçºåŽã®ãªãªãŒã¹ãšè¡çªãã«ãããªã 骚çµã¿ã®PRãå
ã«å
¥ãã ïŒèµ·ç¹ã®Fragmentã«NavHostãå°å
¥ããPRãæåã«å
¥ããŠãããšãåç»é¢ã®ç§»è¡ã¯ããããç¬ç«ããPRã«ãªããè€æ°äººã§äžŠè¡ããŠé²ãããã 鲿ã®ç®¡çããŒãžã¯ãã®ãããªåœ¢ã§ãã 1-3. ç§»è¡åã®é·ç§»ã®èšé² ç»é¢é·ç§»ã®ãã¹ãã¯æžããªã倿ãããŸãã ãNavigation Composeã«ã¯ãã¹ãçšã®APIãçšæãããŠããŸãããããã§ãæžããªããšæ±ºãããŸã§ã®çµç·¯ã¯èª²é¡5ã§æžããŸãããšã¯ãããç§»è¡åãšåãé·ç§»ãã§ããããšã®ç¢ºèªã¯å¿
èŠã§ããããã§éäžãã Maestro ã§ç»é¢é·ç§»ã®ã·ããªãªãäœããç§»è¡ã®ååŸã§åãã·ããªãªãæµããšãã軜ã確èªã詊ããŠããŸãã Maestroã¯ãUIæäœã®ã·ããªãªïŒMaestroã§ã¯ãããŒãšåŒã³ãŸãïŒãYAMLã§æžããšã宿©ã§åãæäœãåçããããŒã«ã§ãããã¹ãã³ãŒããæžãããæè»œã§ãç§»è¡ã®ãã³ã«ç¹°ãè¿ããç¹ãä»åã®çšéã«åããŠããŸããã ã·ããªãªã®YAMLã¯ã mobile-mcp çµç±ã§Claude Codeã«å®æ©ã®ç»é¢ãæäœãããªããçæããŠããŸãã AIã«ä»»ããã®ã¯äœæãŸã§ã§ãã§ãããã£ãã·ããªãªã®å®è¡ã¯Maestro CLIã ãã§è¡ããŸã ãå®è¡ã®ãã³ã«AIã®å€æãå
¥ããªãã®ã§ãåãæäœãåãçµæã§ç¹°ãè¿ããŸãã ã·ããªãªã§ç¢ºèªããŠããã®ã¯ãç»é¢ãéããŠè¡šç€ºã確èªããèµ·ç¹ã«æ»ããšãã åçŽãªç»é¢é·ç§»ã ã ã§ããåé€ãæçš¿ã®ããã«ããŒã¿ã倿Žããæäœããè€æ°ã®å
¥åã䌎ããããŒã¯å«ããŠããŸãããããã¯åŸæ¥ã©ããQAãšæå確èªã§æ
ä¿ããŠããŸãã ã·ããªãªã¯ç»é¢ããšã«ãèµ·ç¹ç»é¢ã§å§ãŸããèµ·ç¹ç»é¢ã«åž°çãããããã«æžããããã runFlow ã§æãã1æ¬ãçšæããŸããåäœã§ãæããŠãå®è¡ã§ããã¹ããããäºéã«æã¡ãŸããã å®è¡ãããšãå·Šã®ãã°ã1ã¹ããããã€é²ã¿ãå³ã®ç«¯æ«ã§åãæäœãåçãããŸãã Maestroã¯ç»é¢äžã®èŠçŽ ãã衚瀺ããã¹ããIDã§æå®ããŠæäœããŸããViewã®ç»é¢ãªã android:id ããã®ãŸãŸIDã«ãªããŸãããComposeã®èŠçŽ ã«ã¯IDã«åœãããã®ããããŸãããããã§æäœãããèŠçŽ ã« Modifier.testTag("...") ãä»ããŸããããã«ãã¢ããªå
šäœãå
ãããŒãã®ComposableïŒãã¹ãŠã®ç»é¢ãå¿
ãéãããã¡ã°ãå€åŽã®ComposableïŒã§ testTagsAsResourceId = true ãèšå®ããŸããããã§ testTag ã®æååãAndroidã® resource-id ãšããŠå€ããèŠããããã«ãªããMaestroã® id: ã§æå®ã§ããŸãã // ã¢ããªå
šäœãå
ãããŒãã®Composableã§äžåºŠã ãèšå®ãã(é
äžã®ãã¹ãŠã®ç»é¢ã«å¹ã) Modifier.semantics { testTagsAsResourceId = true } // æäœãããèŠçŽ ã« testTag ãä»ãã Button(modifier = Modifier.testTag( "settings_change_password_button" ), ...) # Maestro ã®ã·ããªãªãã㯠id ã§æå®ã§ãã - tapOn : id : "settings_change_password_button" Android Compose: Use Modifier.semantics { testTagsAsResourceId = true } to ensure your test tags are discoverable as IDs. â Core Selectors â Maestro Documentation èŠçŽïŒComposeã§ã¯ testTagsAsResourceId ãæå¹ã«ããããšã§ã testTag ãIDã»ã¬ã¯ã¿ãšããŠäœ¿ããã maestro record ã§ååŸããåç»ãPRã«æ·»ä»ããã¬ãã¥ã¢ãŒãé·ç§»ã®ç¶æã確èªã§ããããã«ããŠããŸããCIã«ã¯çµã¿èŸŒãŸããç§»è¡PRããšã«ããŒã«ã«ã§å®è¡ããŠããŸããæžããã·ããªãªã¯ç§»è¡åŸãæ®ãã®ã§ãç»é¢ã®è¿œå ãå
±éã³ã³ããŒãã³ãã®å·®ãæ¿ããã©ã€ãã©ãªæŽæ°ã®ãšãã«ãåãã·ããªãªã§å垰確èªãã§ããŸãã 1-4. ç»é¢ã®ç§»è¡ æ€èšŒçšã®ã·ããªãªãæã£ãããããããç§»è¡ã«åãããããŸããé²ããé çªã¯æ¬¡ã®ãšããã§ãã èµ·ç¹ã®Fragmentã«ç©ºã®NavHostãå°å
¥ããèµ·ç¹ç»é¢ã ãã composable<T> ã§é
ç·ãã骚çµã¿ã®PRãå
¥ãã åç»é¢ã1ã€ãã€ç§»è¡ããã Fragmentãåé€ããç»é¢ã®Composableã composable<T> ãšããŠNavHostã«ç»é²ãã äžèŠã«ãªã£ãå®çŸ©ãããã²ãŒã·ã§ã³ã°ã©ãããåé€ããããã®ç»é¢ã® <fragment> ãšããããžåãã <action> ãæ¶ãã <!-- ç§»è¡å: ããã²ãŒã·ã§ã³ã°ã©ãã«æžããŠããå®çŸ©ãç§»è¡åŸã¯ãã®2ã€ãåé€ãã --> <fragment android : id = "@+id/ChangeEmailFragment" android : name = "...ChangeEmailFragment" /> <action android : id = "@+id/action_Account_to_ChangeEmail" app : destination = "@id/ChangeEmailFragment" /> // ç§»è¡åŸ: NavHoståŽã« composable<T> ãç»é²ããé·ç§»ã¯ã©ã ãã§æž¡ã composable<SettingsDestination.ChangeEmail> { backStackEntry -> val args = backStackEntry.toRoute<SettingsDestination.ChangeEmail>() ChangeEmailRoute( currentEmail = args.currentEmail, onNavigateBack = dropUnlessResumed { navController.navigateUp() }, ) } FAANSã§ã¯ç»é¢ã®ComposableããViewModelãåãåã£ãŠç¶æ
ã賌èªãã XxxRoute ãšãç¶æ
ãšã³ãŒã«ããã¯ã ããåãåãçŽç²ãªUIã® XxxScreen ã«åããŠããŸãã Fragmentãæ
ã£ãŠããViewModelã®çæã»åŒæ°ã®åãåãã»é·ç§»ã³ãŒã«ããã¯ã®æäŸã¯ãNavHoståŽã® composable<T> ãããã¯ãš Route ãžç§»ããŸãã // ç»é¢åŽ: ViewModelãåãåã£ãŠç¶æ
ã賌èªããScreenã«æž¡ã @Composable fun ChangeEmailRoute( currentEmail: String , onNavigateBack: () -> Unit , viewModel: ChangeEmailViewModel = hiltViewModel(), ) { val state by viewModel.state.collectAsStateWithLifecycle() ChangeEmailScreen( state = state, onAction = viewModel :: dispatchAction, onNavigateBack = onNavigateBack, ) } ViewModel ïŒ by viewModels() ã®ä»£ããã« hiltViewModel() ã䜿ããé·ç§»å
ïŒ NavBackStackEntry ïŒã«ã¹ã³ãŒããããã®ã§ãç»é¢ãæããã°ç Žæ£ããã åŒæ° ïŒ Bundle ãSafe Argsã®ä»£ããã« backStackEntry.toRoute<T>() ã§åä»ãã®åŒæ°ãåãåã é·ç§» ïŒ findNavController() ã®ä»£ããã«ãNavHoståŽã§ navController ã䜿ãã©ã ããæž¡ã ãã€ã¢ãã°ã®ç§»è¡ ããã²ãŒã·ã§ã³ã°ã©ãã«ã¯é·ç§»å
ã® <dialog> ã50ãããŸãããç§»è¡åŸã¯çš®é¡ããšã«æ±ããå€ãããŸãã 確èªãã€ã¢ãã°ïŒ AlertDialog ïŒ ïŒå
±éã®Composeãã€ã¢ãã°ã«çœ®ãæããã芪ç»é¢ã®ç¶æ
ïŒ showDeleteConfirmDialog ã®ãããªãã©ã°ïŒã§è¡šç€ºãåãæ¿ããé·ç§»å
ãšããŠã¯ç»é²ããªã BottomSheetDialogFragment ïŒ ModalBottomSheet ã«çœ®ãæããã確èªãã€ã¢ãã°ãšåããã芪ç»é¢ã®ç¶æ
ã§è¡šç€ºãåãæ¿ãã å
šç»é¢ã®DialogFragment ïŒéåžžã®ç»é¢ãšåãã composable<T> ãšããŠç»é²ãã åŒæ°ãåãåããç¬ç«ããé·ç§»å
ãšããŠæ±ãå¿
èŠããããã® ïŒ dialog<T> ã§NavHostã«ç»é²ãã é·ç§»å
ãšããŠç»é²ããªããã€ã¢ãã°ã¯ãããã²ãŒã·ã§ã³ã°ã©ãã® <dialog> ãšãããžåãã <action> ãæ¶ããŸãã // 芪ç»é¢ã®ç¶æ
ã§ãã€ã¢ãã°ãåºãåãããé·ç§»å
ãšããŠã¯ç»é²ããªã if (state.showDeleteConfirmDialog) { FaansAlertDialog( message = stringResource(R.string.delete_account_confirm), onConfirm = { onAction(AccountAction.OnClickDeleteConfirm) }, onDismiss = { onAction(AccountAction.OnDismissDeleteConfirmDialog) }, ) } é·ç§»ãã€ãã³ãã§éç¥ããŠããViewModelã®ç§»è¡åå¯Ÿå¿ FAANSã®æ°ããç»é¢ã¯ãUIç¶æ
ã StateFlow ã«éçŽãã圢ã§ãããããå€ãç»é¢ã«ã¯ãç»é¢é·ç§»ããã€ã¢ãã°è¡šç€ºã LiveData<Event> ã®ãããª1åéãã®ã€ãã³ãã§éç¥ããŠããViewModelãæ®ã£ãŠããŸããã å
¬åŒã®ã¢ãŒããã¯ãã£ã¬ã€ã ã¯ãViewModelã§çºçããã€ãã³ãã UIç¶æ
ã®æŽæ°ãšããŠè¡šçŸãã ããšãæšå¥šããŠããŸããããããç»é¢ã¯ãé·ç§»ã®èŠåŠãç¶æ
ïŒ shouldNavigateBack ã®ãããªãã©ã°ïŒãšããŠæã€åœ¢ã«å€æããŠããç§»è¡ããŸããç»é¢åŽã§ã¯ç¶æ
ãããŒã«ãã LaunchedEffect ã§é·ç§»ãåŒã³ãåŒãã ããšãViewModelã«æ»ããŠç¶æ
ããªã»ããããŸãã LaunchedEffect(state.shouldNavigateBack) { if (state.shouldNavigateBack) { onNavigateBack() onAction(DetailAction.ConsumeNavigateBack) } } ãã®å¯Ÿå¿ã¯æåã®å€æŽãå«ãã®ã§ãç§»è¡PRãšã¯åããŠãç§»è¡å察å¿ããšããŠã¬ãã¥ãŒããŠããŸãã Fragmentã®ã©ã€ããµã€ã¯ã«ãšã®å¯Ÿå¿ Fragmentãåé€ãããšã onViewCreated ã onResume ã§è¡ã£ãŠããåæåããã°éä¿¡ã®çœ®ãå ŽæããªããªããŸããåŠçã®çš®é¡ããšã«ãFragmentã§ã®æžãæ¹ãšComposeã§ã®æžãæ¹ã䞊ã¹ããšæ¬¡ã®ããã«ãªããŸãã ç»é¢ã衚瀺ããããšãã®åæå // Fragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super .onViewCreated(view, savedInstanceState) viewModel.dispatchAction(ChangeEmailAction.Initialize) } // Compose: Compositionã«å
¥ã£ããšãã«1åå®è¡ããã LaunchedEffect( Unit ) { onAction(ChangeEmailAction.Initialize) } åé¢ã«æ»ããã³ã®ããŒã¿åååŸãšãç»é¢è¡šç€ºã®ãã°éä¿¡ // Fragment override fun onResume() { super .onResume() viewModel.dispatchAction(ChangeEmailAction.Refresh) analytics.logScreenView(SCREEN_NAME) } // Compose: ON_RESUME ããšã«å®è¡ããããåŸå§æ«ãèŠããã®ã¯ LifecycleResumeEffectã // ãã°ã®ããã«æããã ãã®ãã®ã¯ LifecycleEventEffect LifecycleResumeEffect( Unit ) { onAction(ChangeEmailAction.Refresh) onPauseOrDispose { } } LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { analytics.logScreenView(SCREEN_NAME) } LifecycleResumeEffect ãš LifecycleEventEffect 㯠å¥ã®API ã§ããã©ã¡ãã ON_RESUME ã§ãããã¯ãåããŸãããåè
㯠onPauseOrDispose ãšãã¢ã§ãåéã§å§ããŠäžæã§æ¢ãããåŠçãæ±ããåŸè
ã¯ã€ãã³ãã®ãã³ã«åŠçãå®è¡ããã ãã§ãã Fragmentã®ã©ã€ããµã€ã¯ã«ãšã®å¯Ÿå¿è¡šã¯å
¬åŒããã¥ã¡ã³ãã«ã¯èŒã£ãŠããªãããã Composeã®ã©ã€ããµã€ã¯ã«å¯Ÿå¿APIã®èª¬æ ãããšã«èªåãã¡ã§æŽçããç§»è¡åã«æ±ºããŠãããŸããã Fragmentã§æžããŠããããš Composeã§ã®çœ®ãæã è£è¶³ onViewCreated ã§ã®åæåã»è³Œèª LaunchedEffect(Unit) Compositionã«å
¥ããã³ã«å®è¡ãNavHostå
ã§æ»ã£ãŠãããšããåå®è¡ããã onResume ã§ã®ããŒã¿åååŸ LifecycleResumeEffect ON_RESUME ã«çµã³ä»ãã onPauseOrDispose ã§åŸå§æ« onResume ã§ã®ç»é¢è¡šç€ºãã°éä¿¡ LifecycleEventEffect(ON_RESUME) å
¬åŒããã¥ã¡ã³ãããã°ãåæãªã©ã®ã¯ã³ã·ã§ããã€ãã³ãåããšããŠããAPI äžåºŠã ãè¡ãåæå ViewModelã® init Composableã®ã©ã€ããµã€ã¯ã«ã«äŸåãããªã 1-5. PRã®åãæ¹ PRã¯åœ¹å²ã§åããŠããŸãã Fragmentã®åé€ã composable<T> ãžã®ç»é²ãäžèŠã«ãªã£ãããã²ãŒã·ã§ã³ã°ã©ãã®å®çŸ©ã®åé€ã ãããç§»è¡PRãã«å
¥ããŸã ãViewModelãUIç¶æ
ã®åœ¢ã«å¯ããäœæ¥ã¯ãç§»è¡å察å¿ããæ®ã£ãã¬ã€ã¢ãŠãXMLãäžèŠimportã®æŽçã¯ãç§»è¡åŸå¯Ÿå¿ããšããŠå¥PRã«åãåºããŸããã¬ãã¥ã¢ãŒããããã¯ç§»è¡ã®å·®åããæå倿Žãããè¿·ããªãããã§ãã æåã®ã¢ãžã¥ãŒã«ã¯æé ã確ç«ããããã«éžã³ãŸããããæé ãå®ãŸã£ãŠããã¯ã æ©èœéçºã®æ¡ä»¶ã§æãå
¥ããã¢ãžã¥ãŒã«ãåªå
ããŠç§»è¡ ããŠããŸããç§»è¡ã ããç®çã«å·¥æ°ã確ä¿ããã®ã¯é£ãããæ¡ä»¶ã§è§Šãã¢ãžã¥ãŒã«ã§ããã°åäœç¢ºèªã®æ©äŒãèªç¶ã«åŸãããããã§ãããã ãPRãšãã±ããã¯æ¡ä»¶ãšã¯å¿
ãåããŸãã 2. 芪åé¢ä¿ã«ããã¢ãžã¥ãŒã«ã®çµ±å ã¢ãžã¥ãŒã«ã®ç§»è¡ãå®äºãããããã®ã¢ãžã¥ãŒã«ã®NavHostãä»ã®NavHostãžçµã¿èŸŒããŸããçµ±åã«ã¯2çš®é¡ãããå€æåºæºãç°ãªããŸãã çµ±åã®çš®é¡ ã¿ã€ãã³ã° æ ¹æ 芪åé¢ä¿ã«ããã¢ãžã¥ãŒã«ã®çµ±åïŒåã¢ãžã¥ãŒã«ã¯èŠªã¢ãžã¥ãŒã«ããããé·ç§»ãããªãïŒ åã¢ãžã¥ãŒã«ã®å
šç»é¢ç§»è¡ãå®äºãããããã å®éã®ç»é¢éå±€ã«æ²¿ããXMLåŽã®ãšã³ããªãæ©ãæžããããPRãå°ãã ActivityçŽäžã®åäžNavHostãžã®çµ±åïŒãããã¬ãã«ã®ã¢ãžã¥ãŒã«çŸ€ïŒ è€æ°ã¢ãžã¥ãŒã«ããŸãšãŸã£ãŠããäžæ¬ 1ã€ã ãå
ã«çµ±åãããšé察称ãªäžéç¶æ
ã«ãªããActivityåŽã®NavHosté
ç·ãXMLã®å€§èŠæš¡åé€ã¯ãŸãšããŠè¡ãæ¹ããªã¹ã¯ãäœã ãã®ç« ã§ã¯åè
ãæ±ããŸããåŸè
ã¯æ¬¡ç« ã§ãã 2-1. NavGraphBuilderæ¡åŒµãšããŠã®ã°ã©ãåé¢ çµ±åã«åããŠãã¢ãžã¥ãŒã«ã®ã°ã©ã㯠NavGraphBuilder ã®æ¡åŒµé¢æ°ãšããŠåãåºããé·ç§»ã®å
¥ãå£ã NavController ã®æ¡åŒµé¢æ°ãšããŠå
¬éããŸããããã¯å
¬åŒã® Encapsulate your navigation code ã瀺ããŠãã圢ã§ããGoogleã®å
¬åŒãµã³ãã«ã¢ããªã§ããNow in Androidããåfeatureã¢ãžã¥ãŒã«ã§åãæ§æãåã£ãŠããŸãã // SettingsGraph.kt â ã°ã©ãã»Destinationã»navigateToXxx() ã1ãã¡ã€ã«ã« fun NavGraphBuilder.settingsGraph(navController: NavHostController) { // ãã¹ãã°ã©ãã®å
¥ãå£ã1-1 ã® SettingsDestination ã« data object Graph ã远å ãã navigation<SettingsDestination.Graph>( startDestination = SettingsDestination.Settings, ) { composable<SettingsDestination.Settings> { ... } composable<SettingsDestination.Account> { ... } composable<SettingsDestination.ChangeEmail> { ... } } } fun NavController.navigateToSettings() = navigate(SettingsDestination.Graph) // çµ±åå
(MypageGraph.kt): 芪ã¢ãžã¥ãŒã«ã® NavHost ã«åã®ã°ã©ããçµã¿èŸŒã fun NavGraphBuilder.mypageGraph(navController: NavHostController) { composable<MypageDestination.Home> { MypageRoute(onClickSettings = { navController.navigateToSettings() }) } settingsGraph(navController) } 2-2. äžæçãªã¢ãžã¥ãŒã«éäŸåã®æ±ã å
¬åŒã® ã¢ãžã¥ãŒã«åã¬ã€ã ã§ã¯ãæ©èœã¢ãžã¥ãŒã«ã¯ããŒã¿å±€ã®ã¢ãžã¥ãŒã«ã«äŸåããæ©èœå士ã®ããåãã¯ä»²ä»ããã¢ãžã¥ãŒã«ïŒéåžžã¯appã¢ãžã¥ãŒã«ïŒãéã圢ã瀺ãããŠããŸããFAANSã§ããæ©èœã¢ãžã¥ãŒã«å士ã¯çŽæ¥äŸåããªãããã«ãŒã«ã«ããŠããŸãããã®çµ±åã§ã¯èŠªã¢ãžã¥ãŒã«ãåã¢ãžã¥ãŒã«ãžäŸåãã圢ã«ãªãããã®ã«ãŒã«ãšäžæçã«ççŸããŸããåäžNavHostãžã®çµ±åãŸã§ã® æéä»ãã®äŸå€ ãšäœçœ®ã¥ããæ¬¡ã®æ¡ä»¶ãèšããŠããŸãã äŸåã¯äžæ¹åã®ã¿ãšãã è§£æ¶ã¿ã€ãã³ã°ãTODOã³ã¡ã³ãã§å¿
ãæç€ºãã çµ±åå
ã®èµ·ç¹Fragmentã¯ãä»ã¢ãžã¥ãŒã«ããã® <action> ãæ¶ãããŸã§åé€ããªã 3. ActivityçŽäžã®åäžNavHostãžã®çµ±å ããã¯æ¬¡ã®ãã§ãŒãºãªã®ã§ãçŸç¶ãšèŠéãã ããæžããŸãã 3-1. ã«ãŒãåŽã®çŸç¶ Activity㯠NavHostFragment ã1ã€æã¡ãã«ãŒãã®ã°ã©ãïŒXMLïŒããåã¢ãžã¥ãŒã«ã®ã°ã©ãã <include> ã§åã蟌ãã§ãããã¹ãã©ãã·ã¥ãèªèšŒãªã©ã¢ããªå
±éã®ç»é¢ã¯ãŸã Fragment ãã£ãŒããªã³ã¯ã¯Activityãåãåãã findNavController(...) ã§XMLã® <deepLink> ã«è§£æ±ºããŠãã Activityã¹ã³ãŒãã§å
±æããViewModelãæ°çš®é¡ãããå¥Activityã§åããããŒãXMLã® <activity> ã§ç¹ãã£ãŠãã 3-2. çµ±åã®èŠéã çµ±åã®æ¬äœã¯ãActivityã®äžã«1ã€ã®NavHostã眮ããåã¢ãžã¥ãŒã«ã®ã°ã©ãã䞊ã¹ãããšããé
ç·ã®çœ®ãæãã§ãç»é¢ã®ã³ãŒãã«ã¯æãå
¥ããŸããã以äžã¯çŸæç¹ã®èšç»ã§ãå®è£
ãšæ€èšŒã¯ããããã§ãã æºå ïŒåã¢ãžã¥ãŒã«ã®ã°ã©ãã NavGraphBuilder æ¡åŒµã® xxxGraph() ã«æããã2ç« ã®çµ±åã§äœ¿ã£ãŠãã圢ãšåãã§ãåäžNavHostãžã®çµ±åã§ã¯ããã䞊ã¹ãã ãã«ãªããæ®ãã®ã¢ãžã¥ãŒã«ãšã¢ããªå
±éã®ç»é¢ã®ç§»è¡ãããã«å«ãŸãã åãæ¿ã ïŒActivityã® setContent ã«ã«ãŒãã®NavHostã眮ãã NavHostFragment ã»ã«ãŒãã®ã°ã©ãïŒXMLïŒã»åã¢ãžã¥ãŒã«ã®èµ·ç¹Fragmentãåé€ãããã¢ãžã¥ãŒã«æšªæã®å€æŽãªã®ã§ãæã£ã段éã§äžåºŠã«è¡ã ä»éãã眮ãæã ïŒãã£ãŒããªã³ã¯ã¯ navDeepLink<T> ãš handleDeepLink(intent) ã§çœ®ãæãããå
±æViewModelã¯ãã¹ãã°ã©ããžã®ã¹ã³ãŒãïŒ hiltViewModel(parentEntry) ïŒãå¥Activityã®èµ·å㯠activity<T> ã䜿ããããããNavigation Composeã«åå®å
šãªAPIããã // ã«ãŒãã® NavHost(èšç») setContent { FaansTheme { val navController = rememberNavController() FaansNavHost(navController, startDestination = RootDestination.Splash) { composable<RootDestination.Splash> { ... } homeGraph(navController) // åã¢ãžã¥ãŒã«ã® xxxGraph() ã䞊ã¹ã mypageGraph(navController) // 2ç« ã§ settingsGraph() ãçµã¿èŸŒã¿æžã¿ } } } åãæ¿ãã®ååŸã§ããç§»è¡åã«ã·ããªãªãæããŠçµ±ååŸã«åãã·ããªãªãæµãæé ããã®ãŸãŸäœ¿ããŸãã ã¢ãžã¥ãŒã«åäœã§ç©ã¿äžããŠãããã®ããæåŸã«äžåºŠã ãé
ç·ãçŽã㊠ãã®ç§»è¡ãçµããèšç»ã§ãã ç§»è¡ã®äžã§èŠããŠããèª²é¡ å
±åæ§é ãã®ãã®ã¯å
¬åŒã¬ã€ãã®å»¶é·ã§ãããããå®éã«éçšãããšããå€åŽãšå
åŽã«NavControllerã2ã€ãããããšããFragmentãšComposeã§ã©ã€ããµã€ã¯ã«ãã¹ã³ãŒãã®åäœãå€ããããšã«èµ·å ãã課é¡ãããã€ãåºãŠããŸããã 課é¡1ïŒæ»ããã¿ã³ã®é£æã§ç»é¢ããã¯ã€ãã¢ãŠããã æåã®ã¢ãžã¥ãŒã«ãç§»è¡ããçŽåŸã®ãã¶ã€ã³ã¬ãã¥ãŒã§ããæ»ããã¿ã³ãé£ç¶ã§ã¿ãããããšç»é¢ãçã£çœã«ãªãããšããææãåããŸãããç¹å®ã®ç«¯æ«ã§ã ãåçŸããçŸè±¡ã§ããã åå ã¯ã 1åç®ã®æ»ãã§ç»é¢ãåãæ¿ãã£ãŠããæäžã«ã2åç®ã®æ»ããå®è¡ãããŠãã ããšã§ããã¢ãžã¥ãŒã«å
ã®NavHostã¯ç»é¢ãå°ãªãããã2åç®ã® popBackStack() ã§èµ·ç¹ç»é¢ãŸã§æ¶ããNavHostã«è¡šç€ºãããã®ããªããªã£ãŠçœãç»é¢ã«ãªããŸãã 端æ«ã®æ»ãã¯NavHostãåŠçããããã¯ã¹ã¿ãã¯1ä»¶ã§ã¯ç¡å¹ã«ãªããããèµ·ç¹ç»é¢ãŸã§ã¯æ¶ããŸãããåé¡ã¯ããŒã«ããŒã®æ»ããã¿ã³ã§ãããã§ popBackStack() ãåŒãã§ããŸããã察å¿ã¯æ¬¡ã®ãšããã§ãã ComposeåŽã®æ»ãåŠçã dropUnlessResumed ã§å
ã¿ãç»é¢ã RESUMED ã§ãªããšãã®ã¿ãããç¡èŠãã ããŒã«ããŒã®æ»ãã popBackStack() ãã navigateUp() ã«æ¿ããã navigateUp() 㯠ããã¯ã¹ã¿ãã¯ã1ä»¶ã ãã®ãšãã¯popããªã ã®ã§ãèµ·ç¹ç»é¢ãæ¶ããªã å€åŽã®NavControllerãåŒã¶çµè·¯ã§ãåãããšãèµ·ããããã RESUMED æªæºãªãäœãããªã safePopBackStack æ¡åŒµãçšæãã // 察å¿1: dropUnlessResumed ã§å
ã / 察å¿2: navigateUp() ã䜿ã onNavigateBack = dropUnlessResumed { navController.navigateUp() } // 察å¿3: å€åŽã® NavController çšã®æ¡åŒµ fun NavController.safePopBackStack(): Boolean { val currentEntry = currentBackStackEntry ?: return false return if (currentEntry.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { popBackStack() } else { false } } Lifecycle 2.8.0 ã§è¿œå ããã dropUnlessResumed ã¯ãç»é¢ïŒ NavBackStackEntry ïŒã RESUMED ã§ãªããã°ãããã¯ãæšãŠãŸãã For Navigation users, it's recommended to safeguard navigate methods when using them while a composable is in transition as a result of navigation. â dropUnlessResumed â androidx.lifecycle.compose (KDoc) èŠçŽïŒé·ç§»ã¢ãã¡ãŒã·ã§ã³äžã®navigateåŒã³åºãã¯ããã®é¢æ°ã§ã¬ãŒãããããšãæšå¥šãããŠããã 察å¿2ã§ popBackStack() ã navigateUp() ã«æ¿ããã®ã¯ã端æ«ã®æ»ããšåãæåã«å¯ããããã§ããã©ã¡ããããã¯ã¹ã¿ãã¯1ä»¶ã§popããªãããšã¯ãandroidxã® navigateUpã®å®è£
ãš æ»ãã³ãŒã«ããã¯ã®æå¹æ¡ä»¶ ã§ç¢ºèªã§ããŸãã é·ç§»äžã®ã¿ããã¯ç¡èŠãããããããŸãã«ããäžåºŠæŒãå¿
èŠãããå Žé¢ã¯ããåŸãŸãããçœãç»é¢ã«ãªãããã¯èš±å®¹ã§ãããšå€æããŸããã 課é¡2ïŒåçããã«ãŒããæ»ã£ãçŽåŸã®é·ç§»ãçºç«ããªã 課é¡1ã§å
¥ãã RESUMED ã®ã¬ãŒãããOSã®åçããã«ãŒããæ»ã£ãçŽåŸã®é·ç§»åŠçã«ãä»ãããšããã é·ç§»ãçºç«ããªããªããŸãã ãåå ã¯Activityã®ä»æ§ã§ãã An activity can never receive a result in the resumed state. You can count on onResume being called after this method, though not necessarily immediately after. â Activity.onActivityResult â Android API reference èŠçŽïŒActivity㯠RESUMED ç¶æ
ã§çµæãåãåãããšã¯ãªãã onResume ã¯ãã®åŸã«åŒã°ããã çµæãé
ä¿¡ãããæç¹ã§Activity㯠RESUMED ã§ã¯ãªãã NavBackStackEntry ã®ã©ã€ããµã€ã¯ã«ããã¹ãã®ç¶æ
ãè¶
ããããŸããã dropUnlessResumed ã RESUMED ã¬ãŒãã¯ç»é¢å
ã®ã¿ããèµ·ç¹ã«ã ãä»ããActivityResultãéåæå®äºã³ãŒã«ããã¯èµ·ç¹ã«ã¯ä»ããªãããšãã䜿ãåãã«èœã¡çããŸããã 課é¡3ïŒæªç§»è¡ã®ç»é¢ãæãã§ãããã¯ã¹ã¿ãã¯ã¯å€±ãããªãã£ã ã¢ãžã¥ãŒã«å
ã§èšå®ç»é¢ããã¢ã«ãŠã³ãèšå®ãžé²ã¿ãããããæªç§»è¡ã®Fragmentãžé·ç§»ããŠãæ»ããæŒãããšããŸããæåŸ
ããã®ã¯ã¢ã«ãŠã³ãèšå®ã«æ»ãããšã§ãããšãããæªç§»è¡ã®Fragmentããæ»ããšèµ·ç¹ã®Fragmentã®ãã¥ãŒãåçæãããããããã®äžã®NavHostãäœãçŽãã㊠èšå®ç»é¢ãŸã§å·»ãæ»ãã®ã§ã¯ãªãã ããšå¿é
ããŠããŸããã çµæã¯ãã¢ã«ãŠã³ãèšå®ã«æ»ããŸã ã rememberNavController 㯠rememberSaveable ã§ç¶æ
ãä¿æããŠããŸãããããŠFragmentã¯ãããã¯ã¹ã¿ãã¯äžã§ãã¥ãŒãç Žæ£ããéã« ãã¥ãŒããªãŒã® SavedStateRegistry ã®ç¶æ
ãä¿åãããã¥ãŒã®åçææã«åŸ©å
ããŸããNavigation ComposeåŽã®ããã¯ã¹ã¿ãã¯ã¯ãã®çµè·¯ã§åŒãç¶ãããã®ã§ãåé¡ãããŸããã§ããã 課é¡4ïŒããã ã·ãŒããéãçŽããŠãåã®å
容ã衚瀺ããã äžèЧã®é
ç®ãã¿ãããããšéãããã ã·ãŒãã§ã2ã€ç®ã®é
ç®ãã¿ããããŠã1ã€ç®ã®å
容ã衚瀺ãããçŸè±¡ãQAã§èŠã€ããŸãããããã ã·ãŒãã®äžã§ hiltViewModel() ãããŒãªãã§ååŸããŠããã®ãåå ã§ãã hiltViewModel() 㯠åŒã³åºãå
ã®é·ç§»å
ïŒ NavBackStackEntry ïŒã«ã¹ã³ãŒããããŸã ããã®ããåãç»é¢ã®äžã§æ¡ä»¶ä»ãã«è¡šç€ºãããããã ã·ãŒãã¯ãã©ã®é
ç®ããéããŠãåäžã®ViewModelã€ã³ã¹ã¿ã³ã¹ãåãåããŸããFragmentæä»£ã¯ DialogFragment ããšã«ViewModelãäœãããŠããã®ã§èµ·ããªãã£ãæåã§ããã // ä¿®æ£å: åãç»é¢å
ã§ã¯åžžã«åãã€ã³ã¹ã¿ã³ã¹ãè¿ã viewModel: ItemDetailViewModel = hiltViewModel() // ä¿®æ£åŸ: 察象IDãããŒã«ããŠãé
ç®ããšã«å¥ã®ã€ã³ã¹ã¿ã³ã¹ã«ãã viewModel: ItemDetailViewModel = hiltViewModel(key = itemId) 察å¿ã¯å¯Ÿè±¡IDãããŒã«ããããšã§ãããã ãåã圢ã®åé¡ã¯ãã€ã¢ãã°ã LaunchedEffect ã§ãèµ·ãããããã³ãŒãã£ã³ã°èŠçŽãšã¬ãã¥ãŒèгç¹ã2ã€è¿œå ããŸããã1ã€ã¯ãæ¡ä»¶ä»ãã§è¡šç€ºããããã ã·ãŒãã»ãã€ã¢ãã°ã¯å¯Ÿè±¡IDãViewModelã®ããŒã«ãããã§ãããã1ã€ã¯ããã©ã¡ãŒã¿ä»ãã³ã³ããŒãã³ãã®åæå㯠LaunchedEffect(Unit) ã§ã¯ãªãèå¥ãã©ã¡ãŒã¿ãããŒã«ãããã§ãã 課é¡5ïŒç»é¢é·ç§»ãã¹ããæžããã«ã©ãæ
ä¿ããã 1-3ã§æžãããšããã NavHostã®ç»é¢é·ç§»ãã¹ãã¯æžããŸããã§ãã ãNavigation Composeã«ã¯ TestNavHostController ãªã©ãã¹ãçšã®APIãçšæãããŠããŸãããããªã®ã«ãªãæžããªãã£ãã®ããæ€èšããé ã«æžããŸãã TestNavHostController ãš createComposeRule ã䜿ãã°ãJVMäžã®Robolectricã§é·ç§»ãã¹ãèªäœã¯æžããŸããViewModelãæããªãç»é¢ã§ã¯å®éã«åããŸãããã€ãŸãããã®ã¯ hiltViewModel() ã§ããç»é¢ã®Composableã®äžã§åŒãã§ãããšããã¹ãçšã® ComponentActivity ãHiltã®ãšã³ããªãã€ã³ãã§ã¯ãªããã倱æããŸãã解決çã¯4ã€æ€èšããŸããã 解決ç ã¡ãªãã ãã¡ãªãã 1. NavHostã®åŒæ°ã§ViewModelãåãåã ã¢ãã¯ãæž¡ãã ãã§æžã èµ·ç¹Fragment衚瀺æã«å
šViewModelãçæããã 2. ãã¹ãçšã«NavHostã®ã³ããŒãäœã æ¬çªã®åŒæ°ãå¢ãããªã NavHostæŽæ°æã«ãã¹ãã忿޿°ïŒæŒããªã¹ã¯ïŒ 3. FactoryåŒæ°ãæãããç»é¢ã®ComposableåŒã³åºãæã«çæ 1ã®åé¡ãåé¿ãã€ã€ã¢ãã¯å¯èœ NavHostã®åŒæ°ãå¢ãã 4. Hiltãã¹ãç°å¢ãæ§ç¯ãã æ¬çªã³ãŒããå€ããªã ããŒã¿å±€ã®ãã¹ãããã«çŸ€ãåæ æ¡4㯠Now in Android ãæ¡ãæ¹åŒã§ã @HiltAndroidTest ã§æ¬ç©ã®Activityãèµ·åãã @TestInstallIn ã§ããŒã¿å±€ããã¹ãçšã®å®è£
ã«å·®ãæ¿ããŸãããã ã ããŒã¿å±€ãæåããå·®ãæ¿ããããèšèšã«ãªã£ãŠããããšãåæ ã§ããFAANSã§ã¯é·ç§»ãã¹ãã®åã«ããŒã¿å±€ãäœãæ¿ããããšã«ãªããç§»è¡ãã®ãã®ãã倧ããªäœæ¥ã§ããæ®ãæ¡3ãæããã©ã³ã¹ã®è¯ã圢ã§ããããå®è£
ãžé²ãåã«ãé·ç§»ãã¹ãã§äœã確ããããã®ããæŽçãçŽããŸããã 確ããããããšãåè§£ãããšããã¿ã³ããã©ã ããåŒã°ããããšã¯Screenã®ãŠããããã¹ãã§ã navigate(X) ã§Xã衚瀺ãããããšã¯Navigationã©ã€ãã©ãªèªèº«ã®ãã¹ãã§æ
ä¿ãããŠããŸããæ®ãã®ã¯ãã©ã ãã«æ£ãã navigate ãæž¡ããŠãããããšããNavHostã®é
ç·ã ãã§ããå
¬åŒã®ãã¹ãã¬ã€ããããã¹ã察象ãããã«éå®ããŠããŸãã The Navigation component handles all the work of managing navigation between destinations, passing arguments, and working with the FragmentManager . These capabilities are already rigorously tested, so there is no need to test them again in your app. What is important to test, however, are the interactions between the app-specific code in your fragments and their NavController . â Test Navigation â Android Developers èŠçŽïŒã©ã€ãã©ãªã®æ©èœã¯åãã¹ãäžèŠããã¹ããã¹ããªã®ã¯ãã¢ããªåºæã®ã³ãŒããšNavControllerã®ã€ã³ã¿ã©ã¯ã·ã§ã³ã ãã®é
ç·ããåå®å
šã«ãŒãã«ããããšã§å€§åã®ãã¹ã¯ã³ã³ãã€ã«æã«æ€åºãããæ®ãã®ã¯ã©ã ã1è¡ã®å®£èšã ãã§ãã ãã®ããã«Hiltãå«ããã¹ãç°å¢ãæŽããã³ã¹ãã¯èŠåããªã ãšå€æããScreenã®ãŠããããã¹ãã»ã³ãŒãã¬ãã¥ãŒã»QAã§æ
ä¿ããããšã«ããŸããã1-3ã®Maestroã«ãã確èªã¯ããã®åŸã«å ãã£ããã®ã§ããé·ç§»ãè€éã§ãã¹ããå¿
èŠãªç»é¢ã«ã€ããŠã¯ããããŒããŒã¿ãçšæããçµåãã¹ãã§æžãæ¹éã§ãã ç§»è¡ã®é²æãšçŸæç¹ã®å¹æ çŽå幎ã®ããã ãæ©èœéçºãšäžŠè¡ããŠç§»è¡ãé²ããææ¬¡ã®ãªãªãŒã¹ãç¶ç¶ã§ããŸããã8ã€ã®æ©èœã¢ãžã¥ãŒã«ã®ãã¡5ã€ã®ç§»è¡ãå®äºãããã®ãã¡1ã€ã¯èŠªã¢ãžã¥ãŒã«ã®NavHostãžçµ±åãããXMLåŽã®ãšã³ããªãåé€ã§ããŠããŸããç§»è¡ãå®äºããã¢ãžã¥ãŒã«ã§ã¯ããèæ¯ã»èª²é¡ãã§æããäºé管çãæ¬¡ã®ããã«è§£æ¶ãããŠããŸãã ç§»è¡å ç§»è¡åŸïŒå®äºããã¢ãžã¥ãŒã«ïŒ ç»é¢ã1ã€è¿œå ãããšãã®äœæ¥ Fragmentã¯ã©ã¹ãäœã / XMLã« <fragment> ãš <action> ãæžã / Safe Argsãçæãã / findNavController() ã§é·ç§»ãæžã Destinationã远å ãã composable<T> ãç»é²ãããé·ç§»ã®ã©ã ããšåŒæ°ã®åãåããåãå Žæã«æžã é·ç§»ã®å®çŸ©ã»å®è¡ã»åŒæ°ã®çœ®ãå Žæ XML / Fragment / Safe Args NavHostã®1ãæã«éãŸããåŒæ°ã¯ @Serializable ãªã¯ã©ã¹ã§ã³ã³ãã€ã«æã«æ€æ»ããã å¯ç£ç©ãšããŠãã¢ãžã¥ãŒã«ããšã®ç»é¢é·ç§»ã·ããªãªãæããMaestroã§å垰確èªã§ããããã«ãªããŸãããç§»è¡ã®æé ãã¹ãã«ãšããŠæ®ã£ãŠããã®ã§ãæ®ãã®ã¢ãžã¥ãŒã«ã¯æ
åœè
ãå€ãã£ãŠãåãé²ãæ¹ãã§ããŸãã æåŸã« æ®ã3ã€ã¯ç»é¢æ°ã®å€ã倧åã¢ãžã¥ãŒã«ã§ãããåããã¿ãŒã³ãšã·ããªãªã§é²ããããèŠéããç«ã£ãŠããŸãããã®å
ã«ããActivityçŽäžã®åäžNavHostãžã®çµ±åã¯3ç« ã®èŠéãã«æ²¿ã£ãŠé²ããåå®å
šã«ãŒãã§æããŠããã®ã§Navigation 3ãžã®ç§»è¡ãåãå»¶é·ç·äžã«ãããŸãã Maestroã®ã·ããªãªã¯ä»ã®ãšããããŒã«ã«ã§å®è¡ããŠããã ãã§ããã·ããªãªãæã£ãŠãããCIã§ç¶ç¶çã«åããç»é¢é·ç§»ãå£ããŠããªãããšãèªåã§ç¢ºãããããç¶æ
ã«ã§ããã°ãšèããŠããŸããå®çŸã§ãããã¯ãŸã èŠããŠããŸããããæã£ãã·ããªãªã®æ¬¡ã®äœ¿ãéãšããŠè©Šãããããšã®1ã€ã§ãã Fragmentè³ç£ãæ±ãããŸãŸComposeãé²ããŠããæ¹ã«ãšã£ãŠã æ¢åã®ç»é¢é·ç§»ãæ¢ããã«ç§»è¡ãé²ãã1ã€ã®ããæ¹ ãšããŠåèã«ãªãã°å¹žãã§ãã ZOZOã§ã¯ãäžç·ã«ãµãŒãã¹ãäœãäžããŠãããæ¹ãåéäžã§ãããèå³ã®ããæ¹ã¯ã以äžã®ãªã³ã¯ãããã²ãå¿åãã ããã corp.zozo.com