Slide 1

Slide 1 text

/BWJHBUJPOΛʹҠߦ͢Δʢ༧ఆʣ ͨΊʹ΍ͬͨ͜ͱ 

Slide 2

Slide 2 text

 4NBSU#BOL *OD .PCJMF"QQ&OHJOFFS ZPLPNJJʢΑ͜Έʔʣ !JJNPLPZ !ZPLPNJJ

Slide 3

Slide 3 text

X

Slide 4

Slide 4 text

͍͔ͭ͘గਖ਼🙇

Slide 5

Slide 5 text

/BWJHBUJPOΛʹҠߦ͢Δʢ༧ఆʣ ͨΊʹ΍ͬͨ͜ͱ 

Slide 6

Slide 6 text

గਖ਼ /BWJHBUJPO$PNQPTFΛ /BWJHBUJPOʹҠߦ͢Δʢ༧ఆʣͨΊʹ΍ͬͨ͜ͱ 

Slide 7

Slide 7 text

/BWJHBUJPO$PNQPTFΛ/BWJHBUJPOʹ Ҡߦ͢Δʢ༧ఆʣ ͨΊʹ΍ͬͨ͜ͱ 

Slide 8

Slide 8 text

 /BWJHBUJPOͷ҆ఆ൛·ͩͰ͢ʂʂʂʂʂ

Slide 9

Slide 9 text

/BWJHBUJPO$PNQPTFΛ/BWJHBUJPOʹҠߦ͢Δ ʢ༧ఆʣ ͨΊʹ΍ͬͨ͜ͱ 

Slide 10

Slide 10 text

 ൃද্ͷϥΠϒϥϦόʔδϣϯ implementation("androidx.navigation3:navigation3-runtime:1.0.0-alpha08") implementation("androidx.navigation3:navigation3-ui:1.0.0-alpha08") implementation("androidx.lifecycle:lifecycle-viewmodel-navigation3:2.10.0-alpha03") implementation("androidx.compose.material3.adaptive:adaptive-navigation3:1.0.0-alpha01")

Slide 11

Slide 11 text

/BWJHBUJPO

Slide 12

Slide 12 text

 /BWJHBUJPO IUUQTXXXZPVUVCFDPNXBUDI W(KWHUX40$BP

Slide 13

Slide 13 text

 /BWJHBUJPO $PNQPTFઐ༻ʹߏங͞Εͨɺφ ϏήʔγϣϯϥΠϒϥϦ https://android-developers.googleblog.com/2025/05/announcing-jetpack-navigation-3-for-compose.html

Slide 14

Slide 14 text

 /BWJHBUJPO ʮ6*ͷঢ়ଶʯͱʮભҠͷঢ়ଶʯ͕ ผ؅ཧ ը໘ભҠ͸Πϕϯτۦಈ ௚઀ঢ়ଶΛมߋͤͣɺΠϕϯ τʹΑͬͯߋ৽ /BWJHBUJPO$PNQPTF PME

Slide 15

Slide 15 text

 /BWJHBUJPO ΧʔυλϒΛબ୒࣌ ᶃ6*ͷঢ়ଶΛߋ৽ ᶄભҠΠϕϯτͷൃՐ ભҠΠϕϯτ͕ॲཧ͞ΕΔ·Ͱͷ ؒɺঢ়ଶͷෆҰக͕ൃੜ /BWJHBUJPO$PNQPTF PME

Slide 16

Slide 16 text

 /BWJHBUJPO ભҠΠϕϯτ͕ॲཧ͞ΕͯɺΑ͏ ΍͘ঢ়ଶ͕Ұக /BWJHBUJPO$PNQPTF PME

Slide 17

Slide 17 text

 /BWJHBUJPO ʮ6*ͷঢ়ଶʯͱɺʮભҠͷঢ়ଶʯ ͕ಉҰ৘ใʢ4405ʣ /BWJHBUJPO OFX

Slide 18

Slide 18 text

 /BWJHBUJPO ભҠΠϕϯτͳ͠Ͱը໘ભҠ ঢ়ଶ͕Ұ࣌తʹෆҰகʹͳΔ ϦεΫ͕ͳ͍ ঢ়ଶ؅ཧͷෳࡶ͞Λܰݮ /BWJHBUJPO OFX

Slide 19

Slide 19 text

/BW #BDLTUBDL

Slide 20

Slide 20 text

 /BW #BDLTUBDL όοΫελοΫ͸zঢ়ଶzΛ಺แ͢ ΔϦετ val backStack : List = remember { mutableListOf(FoodList) } data object FoodList data class FoodDetail(val id: String)

Slide 21

Slide 21 text

 /BW #BDLTUBDL όοΫελοΫ͕؅ཧ͢Δঢ়ଶΛ ΩʔͱݺͿ "OZܕ val backStack : List = remember { mutableListOf(FoodList) } data object FoodList data class FoodDetail(val id: String)

Slide 22

Slide 22 text

 /BW #BDLTUBDL ΩʔʹҾ਺Λ࣋ͨͤΔ͜ͱ͕Ͱ͖ Δ val backStack : List = remember { mutableListOf(FoodList) } data object FoodList data class FoodDetail(val id: String)

Slide 23

Slide 23 text

 /BW #BDLTUBDL όοΫελοΫʹ͸ඞͣϧʔτ ΩʔΛઃఆ͢Δ ۭʹͳΔ͜ͱ͸૝ఆ֎ val backStack : List = remember { mutableListOf(FoodList) } data object FoodList data class FoodDetail(val id: String)

Slide 24

Slide 24 text

 /BW #BDLTUBDL 4OBQTIPU4UBUF-JTUΛ༻͍Δ͜ͱ Ͱɺಈతͳมߋ௨஌͕Մೳ val backStack : SnapshotStateList = remember { mutableStateListOf(FoodList) }

Slide 25

Slide 25 text

 /BW #BDLTUBDL ελοΫʹΩʔΛBEE͢Δ͜ͱͰ ը໘ભҠ // [ FoodList, FoodDetail ] backStack.add(FoodDetail(id = "lice"))

Slide 26

Slide 26 text

 /BW #BDLTUBDL SFNPWFͰલͷը໘ʹ໭Δ // [ FoodList ] backStack.removeLastOrNull()

Slide 27

Slide 27 text

 /BW #BDLTUBDL ໨తڍಈ /BWJHBUJPO$PNQPTF /BW ը໘ਐΉʢ௨ৗͷOBWJHBUFʣ OBW$POUSPMMFSOBWJHBUF lGPPE@MJTUJE GPPE@EFUBJMl CBDL4UBDLBEE 'PPE%FUBJM lJEz ը໘໭Δ OBW$POUSPMMFSQPQ#BDL4UBDL CBDL4UBDLSFNPWF-BTU0S/VMM ೚ҙ஍఺·Ͱ໭ΔʢJODMVTJWFGBMTFʣ OBW$POUSPMMFSQPQ#BDL4UBDL GPPE@MJTU  JODMVTJWFGBMTF CBDL4UBDLESPQ-BTU8IJMF\JU'PPE-JTU^ ೚ҙ஍఺·Ͱ໭ΔʢJODMVTJWFUSVFʣ OBW$POUSPMMFSQPQ#BDL4UBDL lGPPE@MJTU  JODMVTJWFUSVF CBDL4UBDLESPQ-BTU8IJMF\JU'PPE-JTU^ CBDL4UBDLESPQ-BTU  MBVODI4JOHMF5PQʢτοϓʹಉ͡ϧʔτ͕͋Δͳ Βੵ·ͳ͍ʣ OBW$POUSPMMFSOBWJHBUF lGPPE@MJTUz \MBVODI4JOHMF5PQUSVF^ JG CBDL4UBDLMBTU0S/VMM 'PPE-JTU  CBDL4UBDLBEE 'PPE-JTU

Slide 28

Slide 28 text

 /BW #BDLTUBDL SFNFNCFS/BW#BDL4UBDL ߏ੒ͷมߋ΍ϓϩηεΩϧͰ΋ଘ ଓ͢ΔόοΫελοΫͷ࡞੒ Ωʔ͸/BW,FZΠϯλʔϑΣʔε Λ࣮૷͠ɺ!4FSJBMJ[BCMFͱ͢Δ @Serializable data object FoodList : NavKey val backStack : SnapshotStateList = rememberNavBackStack(FoodList)

Slide 29

Slide 29 text

/BW%JTQMBZ

Slide 30

Slide 30 text

 /BW%JTQMBZ /BW%JTQMBZ όοΫελοΫͷঢ়ଶΛݩʹɺը ໘Λඳը͢Δίϯϙʔωϯτ DG/BW)PTUʢ/BWJHBUJPO $PNQPTFʣ NavDisplay( backStack = backStack, onBack = { backStack.removeLastOrNull() }, entryProvider = { key -> when (key) { is FoodList -> NavEntry(key) { FoodListScreen() } is FoodDetail -> NavEntry(key) { FoodDetailScreen(id = key.id) } else -> error("Unknown route: $key") } } )

Slide 31

Slide 31 text

 /BW%JTQMBZ /BW%JTQMBZCBDL4UBDL όοΫελοΫΛઃఆ NavDisplay( backStack = backStack, onBack = { backStack.removeLastOrNull() }, entryProvider = { key -> when (key) { is FoodList -> NavEntry(key) { FoodListScreen() } is FoodDetail -> NavEntry(key) { FoodDetailScreen(id = key.id) } else -> error("Unknown route: $key") } } )

Slide 32

Slide 32 text

 /BW%JTQMBZ /BW%JTQMBZFOUSZ1SPWJEFS ΩʔʹରԠ͢Δ/BW&OUSZΛฦ͢ ίʔϧόοΫ  DG/BW(SBQI#VJMEFSDPNQPTBCMF ʢ/BWJHBUJPO$PNQPTFʣ NavDisplay( backStack = backStack, onBack = { backStack.removeLastOrNull() }, entryProvider = { key -> when (key) { is FoodList -> NavEntry(key) { FoodListScreen() } is FoodDetail -> NavEntry(key) { FoodDetailScreen(id = key.id) } else -> error("Unknown route: $key") } } )

Slide 33

Slide 33 text

 /BW%JTQMBZ /BW%JTQMBZFOUSZ1SPWJEFS FOUSZ1SPWJEFS%4-Ͱهड़ͷলུ NavDisplay( … entryProvider = entryProvider { entry { FoodListScreen() } entry{ key -> FoodDetailScreen(id = key.id) } } )

Slide 34

Slide 34 text

 /BW&OUSZ /BW&OUSZ Ωʔͱඳը͢ΔίϯςϯπΛରԠ ͚ͮͨ΋ͷ  DG/BW#BDL4UBDL&OUSZ ʢ/BWJHBUJPO$PNQPTFʣ public open class NavEntry( private val key: T, public val contentKey: Any = defaultContentKey(key), public open val metadata: Map = emptyMap(), private val content: @Composable (T) -> Unit, ) { ... } BOESPJEYOBWJHBUJPOSVOUJNF/BW&OUSZLU

Slide 35

Slide 35 text

 /BW&OUSZ /BW&OUSZDPOUFOU,FZ ΤϯτϦʔͷࣝผࢠ σϑΥϧτ͸LFZUP4USJOH public open class NavEntry( private val key: T, public val contentKey: Any = defaultContentKey(key), public open val metadata: Map = emptyMap(), private val content: @Composable (T) -> Unit, ) { ... } internal fun defaultContentKey(key: Any): Any = key.toString() BOESPJEYOBWJHBUJPOSVOUJNF/BW&OUSZLU

Slide 36

Slide 36 text

 /BW&OUSZ /BW&OUSZNFUBEBUB ΤϯτϦʔʹ௥Ճ৘ใΛ෇༩͢Δ ͨΊͷ,FZWBMVFϚοϓ public open class NavEntry( private val key: T, public val contentKey: Any = defaultContentKey(key), public open val metadata: Map = emptyMap(), private val content: @Composable (T) -> Unit, ) { ... } BOESPJEYOBWJHBUJPOSVOUJNF/BW&OUSZLU

Slide 37

Slide 37 text

 /BW%JTQMBZ /BW%JTQMBZPO#BDL γεςϜͷ໭Δૢ࡞Ͱݺ͹ΕΔ ίʔϧόοΫ Ҿ਺͸໭Δ΂͖݅਺ Ұ౓ʹෳ਺ͷΤϯτϦʔΛ όοΫ͢Δέʔε͕͋ΓಘΔ NavDisplay( ... onBack = { count -> repeat(count) { backStack.removeLastOrNull() } }, )

Slide 38

Slide 38 text

4DFOF4DFOF4USBUFHZ

Slide 39

Slide 39 text

 4DFOF4DFOF4USBUFHZ /BWJHBUJPOͷॏཁͳίϯηϓτ ͸ɺΞμϓςΟϒϨΠΞ΢τʢ͞ ·͟·ͳσόΠεαΠζʹదͨ͠ ϨΠΞ΢τʣʹ͓͚Δը໘ભҠػ ೳΛఏڙ͢Δ͜ͱ ͦΕΛ࣮ݱ͢Δ͘͠Έ͕4DFOF 4DFOF4USBUFHZ IUUQTXXXZPVUVCFDPNXBUDI W(KWHUX40$BP

Slide 40

Slide 40 text

 4DFOF4DFOF4USBUFHZ 4DFOF ը໘্ʹΤϯτϦʔΛͲͷΑ͏ ʹඳը͢Δ͔Λܾఆ͠ɺඳըॲཧ Λఏڙ public interface Scene { // γʔϯͷҰҙੑΛࣔ͢Ωʔ val key: Any // γʔϯ͕ඳը͢ΔΤϯτϦʔ܈ val entries: List> // γʔϯͷഎ໘ʹඳը͞ΕΔΤϯτϦʔ܈ val previousEntries: List> // ඳըॲཧ val content: @Composable () -> Unit } BOESPJEYOBWJHBUJPOVJ4DFOFLU

Slide 41

Slide 41 text

 4DFOF4DFOF4USBUFHZ 4DFOF γʔϯʹΑͬͯ͸ը໘্ʹෳ਺ ͷΤϯτϦʔΛඳը͢Δ͜ͱ͕͋ Δ /BWJHBUJPOʹ͓͍ͯ͸ɺΤϯ τϦʔʺը໘ public interface Scene { // ෳ਺ඳը͢ΔՄೳੑ͕͋ΔͷͰϦετ val entries: List> ... } BOESPJEYOBWJHBUJPOVJ4DFOFLU

Slide 42

Slide 42 text

 4DFOF4DFOF4USBUFHZ 4DFOF4USBUFHZ /BW%JTQMBZ্ͷΤϯτϦʔ܈Λ Ͳͷ4DFOFͰඳը͢Δ͔Λܭࢉ ͢Δ public fun interface SceneStrategy { @Composable public fun calculateScene( entries: List>, onBack: (count: Int) -> Unit ): Scene? } BOESPJEYOBWJHBUJPOVJ4DFOF4USBUFHZLU

Slide 43

Slide 43 text

 4DFOF4DFOF4USBUFHZ DBMDVMBUF4DFOF PO#BDL /BW%JTQMBZʹઃఆͨ͠ίʔϧόο ΫͷͦΕ ෳ਺ΤϯτϦʔΛඳը࣌ɺҰ౓ʹ ෳ਺ΤϯτϦʔΛόοΫ͢Δ͜ͱ ͕͋Δ ˠγʔϯ಺ͰίʔϧόοΫΛ࣮ ߦ public fun interface SceneStrategy { @Composable public fun calculateScene( entries: List>, onBack: (count: Int) -> Unit ): Scene? } BOESPJEYOBWJHBUJPOVJ4DFOF4USBUFHZLU

Slide 44

Slide 44 text

 4DFOF4DFOF4USBUFHZ 4JOHMF1BOF4DFOF4USBUFHZ /BW%JTQMBZͷσϑΥϧτετϥ ςδʔ @Composable public fun NavDisplay( ɹɹɹɹ... sceneStrategy: SceneStrategy = SinglePaneSceneStrategy() ... ) BOESPJEYOBWJHBUJPOVJ/BW%JTQMBZLU

Slide 45

Slide 45 text

 4DFOF4DFOF4USBUFHZ 4JOHMF1BOF4DFOF4USBUFHZ 4JOHMF1BOFM4DFOFΛੜ੒ public class SinglePaneSceneStrategy : SceneStrategy { @Composable override fun calculateScene( ... ): Scene = SinglePaneScene( key = entries.last().contentKey, entry = entries.last(), previousEntries = entries.dropLast(1), ) } BOESPJEYOBWJHBUJPOVJ/BW%JTQMBZLU

Slide 46

Slide 46 text

 4DFOF4DFOF4USBUFHZ 4JOHMF1BOF4DFOF /BW%JTQMBZͷσϑΥϧτͷγʔ ϯ ը໘্ʹΤϯτϦʔΛඳը͢Δ ͚ͩ internal data class SinglePaneScene( override val key: Any, val entry: NavEntry, override val previousEntries: List>, ) : Scene { override val entries: List> = listOf(entry) // ༩͑ΒΕͨ1ΤϯτϦʔΛͦͷ··ඳը͍ͯ͠Δ override val content: @Composable () -> Unit = { entry.Content() } } BOESPJEYOBWJHBUJPOVJ4JOHMF1BOFM4DFOFLU

Slide 47

Slide 47 text

 4DFOF4DFOF4USBUFHZ BEBQUJWFOBWJHBUJPOϥΠϒϥ ϦͰ͸ɺʮϦετৄࡉϨΠΞ΢ τʯΛ࣮૷͢ΔͨΊͷ4DFOF 4DFOF4USBUFHZΛఏڙ IUUQTNNBUFSJBMJPGPVOEBUJPOTMBZPVUDBOPOJDBMMBZPVUTMJTUEFUBJM

Slide 48

Slide 48 text

 4DFOF4DFOF4USBUFHZ ϦετৄࡉϨΠΞ΢τ࣮૷ྫ /BW%JTQMBZͷTDFOF4USBUFHZҾ ਺ʹ-JTU%FUBJM4DFOF4USBUFHZ Λઃఆ import androidx.compose.material3.adaptive.navigat ion3.rememberListDetailSceneStrategy NavDisplay( sceneStrategy = rememberListDetailSceneStrategy(), ... )

Slide 49

Slide 49 text

 4DFOF4DFOF4USBUFHZ ϦετৄࡉϨΠΞ΢τ࣮૷ྫ -JTU%FUBJM4DFOF4USBUFHZͰ͸ɺ όοΫελοΫͷ࠷ޙͷΤϯτ Ϧʔʢݱࡏඳը͢΂͖ը໘ʣ͕ɺ γʔϯͷର৅Ͱ͋Δ͔Λ֬ೝ͢Δ @Composable override fun calculateScene( entries: List>, onBack: (count: Int) -> Unit, ): Scene? { val lastPaneMetadata = getPaneMetadata(entries.last()) ?: return null ... } private fun getPaneMetadata(entry: NavEntry): PaneMetadata? = entry.metadata[ListDetailRoleKey] as? PaneMetadata BOESPJEYDPNQPTFNBUFSJBMBEBQUJWFOBWJHBUJPO-JTU%FUBJM4DFOF4USBUFHZLU

Slide 50

Slide 50 text

 4DFOF4DFOF4USBUFHZ ϦετৄࡉϨΠΞ΢τ࣮૷ྫ ର৅ΤϯτϦʔͰ͋Δ͔ͷ֬ೝख ஈͱͯ͠ɺΤϯτϦʔͷϝλσʔ λΛνΣοΫ͢Δ @Composable override fun calculateScene( entries: List>, onBack: (count: Int) -> Unit, ): Scene? { val lastPaneMetadata = getPaneMetadata(entries.last()) ?: return null ... } private fun getPaneMetadata(entry: NavEntry): PaneMetadata? = entry.metadata[ListDetailRoleKey] as? PaneMetadata BOESPJEYDPNQPTFNBUFSJBMBEBQUJWFOBWJHBUJPO-JTU%FUBJM4DFOF4USBUFHZLU

Slide 51

Slide 51 text

 4DFOF4DFOF4USBUFHZ ϦετৄࡉϨΠΞ΢τ࣮૷ྫ ϝλσʔλ͸ΤϯτϦʔͷҾ਺ʹ ઃఆ NavDisplay( ... entryProvider = entryProvider { entry( metadata = ListDetailSceneStrategy.listPane() ) { ... } entry( metadata = ListDetailSceneStrategy.detailPane() ) { ... } } )

Slide 52

Slide 52 text

 4DFOF4DFOF4USBUFHZ ϦετৄࡉϨΠΞ΢τ࣮૷ྫ ΤϯτϦʔ͕γʔϯͷର৅ͳΒɺ ޙଓͷର৅ΤϯτϦʔΛଋͶΔ @Composable override fun calculateScene( ... ): Scene? { ... val scaffoldEntries = mutableListOf>() var idx = entries.lastIndex while (idx >= 0) { val paneMetadata = getPaneMetadata(entry) ?: break ... scaffoldEntries.add(0, entries[i]) i-- } ... } BOESPJEYDPNQPTFNBUFSJBMBEBQUJWFOBWJHBUJPO-JTU%FUBJM4DFOF4USBUFHZLU

Slide 53

Slide 53 text

 4DFOF4DFOF4USBUFHZ ϦετৄࡉϨΠΞ΢τ࣮૷ྫ ଋͶͨΤϯτϦʔΛҾ਺ʹɺ 5ISFF1BOF4DB ff PME4DFOFΛੜ ੒ PO#BDLίʔϧόοΫΛγʔ ϯʹ౉͢ @Composable override fun calculateScene( ... ): Scene? { ... val scaffoldEntries = mutableListOf>() … val scene = ThreePaneScaffoldScene( onBack = onBack, scaffoldEntries = scaffoldEntries, ... ) ... } BOESPJEYDPNQPTFNBUFSJBMBEBQUJWFOBWJHBUJPO-JTU%FUBJM4DFOF4USBUFHZLU

Slide 54

Slide 54 text

 4DFOF4DFOF4USBUFHZ ϦετৄࡉϨΠΞ΢τ࣮૷ྫ 5ISFF1BOF4DB ff PME4DFOFͰɺը ໘αΠζʹԠͨ͡1BOF਺Λܭࢉ খը໘ˠ1BOF େը໘ˠ1BOFʢ-JTU %FUBJMʣ 1BOFͷͱ͖͸ 4JOHMF1BOF4DFOFʹඳըΛҕৡ

Slide 55

Slide 55 text

 4DFOF4DFOF4USBUFHZ ϦετৄࡉϨΠΞ΢τ࣮૷ྫ 5ISFF1BOF4DB ff PME4DFOFͰɺ໭ Δૢ࡞ͰQPQ͢ΔΤϯτϦʔ਺Λ ܭࢉ ܭࢉ݁ՌΛҾ਺ʹࢦఆͯ͠ PO#BDLίʔϧόοΫΛ࣮ߦ

Slide 56

Slide 56 text

 4DFOF4DFOF4USBUFHZ

Slide 57

Slide 57 text

0WFSMBZ4DFOF

Slide 58

Slide 58 text

 0WFSMBZ4DFOF 0WFSMBZ4DFOF ଞͷγʔϯʹ෴͍ඃ͞ΔΑ͏ʹඳ ը͢Δγʔϯ public interface OverlayScene : Scene { // ͜ͷγʔϯ͕෴͍ඃ͞ΔΤϯτϦʔ܈ public val overlaidEntries: List> } BOESPJEYOBWJHBUJPOVJ0WFSMBZ4DFOFLU

Slide 59

Slide 59 text

 0WFSMBZ4DFOF /BW%JTQMBZͰ͸௨ৗͷγʔϯΛ "OJNBUFE$POUFOU಺Ͱඳըޙɺ 0WFSMBZ4DFOFΛඳը͢Δ @Composable public fun NavDisplay(...) { ... transition.AnimatedContent(...) { ... targetScene.content() ... } ... overlayScenes.fastForEachReversed { overlayScene -> ... overlayScene.content.invoke() ... } ... } BOESPJEYOBWJHBUJPOVJ/BW%JTQMBZLU

Slide 60

Slide 60 text

%JBMPH4DFOF%JBMPH4DFOF4USBUFHZ

Slide 61

Slide 61 text

 %JBMPH4DFOF%JBMPH4DFOF4USBUFHZ %JBMPH4DFOF ΤϯτϦʔΛμΠΞϩάͱͯ͠ඳ ը͢Δγʔϯ /BW%JTQMBZͷҾ਺ʹ %JBMPH4DFOF4USBUFHZΛઃఆ͢ Δ͜ͱͰ࢖༻Մೳ val dialogStrategy = remember { DialogSceneStrategy() } NavDisplay( sceneStrategy = dialogStrategy, ... )

Slide 62

Slide 62 text

 %JBMPH4DFOF%JBMPH4DFOF4USBUFHZ ετϥςδʔ͸ෳ਺ઃఆ͕Մೳ ઃఆॱংͰΤϯτϦʔͷର৅ γʔϯΛ୳͢ ࠷ॳʹΈ͔ͭͬͨγʔϯʹ ΑͬͯΤϯτϦʔΛඳը ʢର৅γʔϯ͕ݟ͔ͭΒͳ͚Ε ͹4JOHMF1BOF4DFOFͰඳըʣ NavDisplay( sceneStrategy = dialogStrategy .then(otherStrategy) ... )

Slide 63

Slide 63 text

 %JBMPH4DFOF%JBMPH4DFOF4USBUFHZ μΠΞϩάͱͯ͠ඳը͢ΔΤϯτ Ϧʔʹ %JBMPH4DFOF4USBUFHZEJBMPHϝ λσʔλΛઃఆ %JBMPH1SPQFSUJFT͸ɺγʔϯͰ ඳը͢Δ%JBMPHίϯϙʔωϯτ ʹద༻͞ΕΔ NavDisplay( entryProvider = entryProvider { ... entry( metadata = DialogSceneStrategy.dialog( dialogProperties = DialogProperties( dismissOnClickOutside = false ) ) ) { ... } } ... )

Slide 64

Slide 64 text

 %JBMPH4DFOF%JBMPH4DFOF4USBUFHZ %JBMPH4DFOF4USBUFHZͰɺඳըର ৅ͷΤϯτϦʔʹϝλσʔλ͕ઃ ఆ͞Ε͍ͯΔ͔Λ֬ೝ ϝλσʔλ͕ઃఆ͞Ε͍ͯͨΒ %JBMPH1SPQFSUJFTΛऔಘͯ͠γʔ ϯΛ࡞੒ public class DialogSceneStrategy() : SceneStrategy { @Composable public override fun calculateScene(...): Scene? { val lastEntry = entries.lastOrNull() val dialogProperties = lastEntry?.metadata?.get(DIALOG_KEY) as? DialogProperties return dialogProperties?.let { properties -> DialogScene( ... ) } } } BOESPJEYOBWJHBUJPOVJ%JBMPH4DFOFLU

Slide 65

Slide 65 text

 %JBMPH4DFOF%JBMPH4DFOF4USBUFHZ %JBMPH4DFOFͰɺΤϯτϦʔͷί ϯςϯπΛ%JBMPHίϯϙʔωϯ τ্Ͱඳը import androidx.compose.ui.window.Dialog internal class DialogScene( private val entry: NavEntry, private val dialogProperties: DialogProperties, private val onBack: (count: Int) -> Unit, ... ) : OverlayScene { ... override val content: @Composable (() -> Unit) = { Dialog( onDismissRequest = { onBack(1) }, properties = dialogProperties ) { entry.Content() } } } BOESPJEYOBWJHBUJPOVJ%JBMPH4DFOFLU

Slide 66

Slide 66 text

 %JBMPH4DFOF%JBMPH4DFOF4USBUFHZ ϝλσʔλ͔Βऔಘͨ͠ %JBMPH1SPQFSUJFTͱɺPO#BDL ίʔϧόοΫΛ%JBMPHʹઃఆ internal class DialogScene( private val entry: NavEntry, private val dialogProperties: DialogProperties, private val onBack: (count: Int) -> Unit, ... ) : OverlayScene { ... override val content: @Composable (() -> Unit) = { Dialog( onDismissRequest = { onBack(1) }, properties = dialogProperties ) { entry.Content() } } } BOESPJEYOBWJHBUJPOVJ%JBMPH4DFOFLU

Slide 67

Slide 67 text

 %JBMPH4DFOF%JBMPH4DFOF4USBUFHZ

Slide 68

Slide 68 text

 %JBMPH4DFOF%JBMPH4DFOF4USBUFHZ μΠΞϩάΛʮಠཱͨ͠ΤϯτϦʔʯͰඳը͢ΔϝϦοτ ᶃόοΫελοΫͰදࣔ؅ཧ͕Ͱ͖Δ ᶄΤϯτϦʔͷϥΠϑαΠΫϧʹ࿈ಈ͢Δঢ়ଶϗϧμʢ4BWFE4UBUFɺ7JFX.PEFMͳͲʣ Λ࣋ͨͤΔ͜ͱ͕Մೳ

Slide 69

Slide 69 text

/BW&OUSZ%FDPSBUPS

Slide 70

Slide 70 text

 /BW&OUSZ%FDPSBUPS /BW&OUSZ%FDPSBUPS ΤϯτϦʔʹঢ়ଶ΍੹຿Λ֦ு͢ Δػೳ public class NavEntryDecorator internal constructor( internal val onPop: (key: Any) -> Unit, internal val navEntryDecorator: @Composable (entry: NavEntry) -> Unit, ) BOESPJEYOBWJHBUJPOSVOUJNF/BW&OUSZ%FDPSBUPSLU

Slide 71

Slide 71 text

 /BW&OUSZ%FDPSBUPS /BW&OUSZ%FDPSBUPSOBW&OUSZ%FD PSBUPS ΤϯτϦʔͷίϯςϯπΛඳը͢ Δͱͱ΋ʹɺ֦ுॲཧΛ࣮૷͢Δ public class NavEntryDecorator internal constructor( internal val onPop: (key: Any) -> Unit, internal val navEntryDecorator: @Composable (entry: NavEntry) -> Unit, ) BOESPJEYOBWJHBUJPOSVOUJNF/BW&OUSZ%FDPSBUPSLU

Slide 72

Slide 72 text

 /BW&OUSZ%FDPSBUPS /BW&OUSZ%FDPSBUPSPO1PQ ΤϯτϦʔ͕όοΫελοΫ͔Β QPQ͞ΕΔͱ͖ʹݺ͹Εɺޙॲཧ Λ࣮૷͢Δ public class NavEntryDecorator internal constructor( internal val onPop: (key: Any) -> Unit, internal val navEntryDecorator: @Composable (entry: NavEntry) -> Unit, ) BOESPJEYOBWJHBUJPOSVOUJNF/BW&OUSZ%FDPSBUPSLU

Slide 73

Slide 73 text

 /BW&OUSZ%FDPSBUPS /BW&OUSZ%FDPSBUPS͸ /BW%JTQMBZͷҾ਺ʹෳ਺ઃఆͰ ͖Δ ઃఆͨ͠શσίϨʔλʔॲཧ͕ఆ ٛॱংͰΤϯτϦʔʹద༻͞ΕΔ @Composable public fun NavDisplay( ... entryDecorators: List> = listOf( rememberSceneSetupNavEntryDecorator(), rememberSavedStateNavEntryDecorator(), ), ... ){ ... } BOESPJEYOBWJHBUJPOVJ/BW%JTQMBZLU

Slide 74

Slide 74 text

 /BW&OUSZ%FDPSBUPS /BW%JTQMBZ͸σϑΥϧτͰҎԼ ͷσίϨʔλʔΛઃఆ ɹ4DFOF4FUVQ/BW&OUSZ%FDPSBUPS ɹ4BWFE4UBUF/BW&OUSZ%FDPSBUPS @Composable public fun NavDisplay( ... entryDecorators: List> = listOf( rememberSceneSetupNavEntryDecorator(), rememberSavedStateNavEntryDecorator(), ), ... ){ ... } BOESPJEYOBWJHBUJPOVJ/BW%JTQMBZLU

Slide 75

Slide 75 text

 /BW&OUSZ%FDPSBUPS 4DFOF4FUVQ/BW&OUSZ%FDPSBUPS ΤϯτϦʔͷঢ়ଶ͕γʔϯؒͷҠ ಈͰࣦΘͳ͍Α͏ʹ੍ޚΛ͢Δ public fun SceneSetupNavEntryDecorator(): NavEntryDecorator { val movableContentMap: MutableMap Unit) -> Unit> = mutableStateMapOf() return navEntryDecorator( onPop = { contentKey -> movableContentMap.remove(contentKey) } ) { entry -> ... val movableContent = remember { movableContentMap.getOrPut(contentKey) { movableContentOf { content -> content() } } } movableContent { entry.Content() } ... } } BOESPJEYOBWJHBUJPOVJ4DFOF4FUVQ/BW&OUSZ%FDPSBUPSLU

Slide 76

Slide 76 text

 /BW&OUSZ%FDPSBUPS 4DFOF4FUVQ/BW&OUSZ%FDPSBUPS NPWBCMF$POUFOU0GͰΤϯτ ϦʔͷίϯςϯπΛϥοϓ͠ɺ SFNFNCFSঢ়ଶΛอ࣋ return navEntryDecorator( ... ) { entry -> ... val movableContent = remember { movableContentMap.getOrPut(contentKey) { movableContentOf { content -> content() } } } movableContent { entry.Content() } ... } } BOESPJEYOBWJHBUJPOVJ4DFOF4FUVQ/BW&OUSZ%FDPSBUPSLU

Slide 77

Slide 77 text

 /BW&OUSZ%FDPSBUPS 4DFOF4FUVQ/BW&OUSZ%FDPSBUPS PO1PQͰQPQ͞ΕͨΤϯτϦʔ ͷίϯςϯπΛϝϞϦղ์ return navEntryDecorator( onPop = { contentKey -> ɹɹɹɹɹmovableContentMap.remove(contentKey) } ) {ɹ...ɹ} BOESPJEYOBWJHBUJPOVJ4DFOF4FUVQ/BW&OUSZ%FDPSBUPSLU

Slide 78

Slide 78 text

 /BW&OUSZ%FDPSBUPS 4BWFE4UBUF/BW&OUSZ%FDPSBUPS ΤϯτϦʔݻ༗ͷ 4BWFE4UBUF3FHJTUSZ 0XOFS Λ ఏڙ SFNFNCFS4BWFBCMFͷੜଘظ ͕ؒΤϯτϦʔʹඥͮ͘Α͏ʹͳ Δ public fun SavedStateNavEntryDecorator( saveableStateHolder: SaveableStateHolder ): NavEntryDecorator { val onPop: (Any) -> Unit ={ contentKey -> saveableStateHolder.removeState(contentKey) } return navEntryDecorator(onPop = onPop) { entry -> saveableStateHolder.SaveableStateProvider( entry.contentKey ) { entry.Content() } } } BOESPJEYOBWJHBUJPOSVOUJNF4BWFE4UBUF/BW&OUSZ%FDPSBUPSLU

Slide 79

Slide 79 text

 /BW&OUSZ%FDPSBUPS 4BWFE4UBUF/BW&OUSZ%FDPSBUPS 4BWFBCMF4UBUF1SPWJEFSͷLFZ ʹΤϯτϦʔͷࣝผࢠ ʢDPOUFOU,FZʣΛઃఆ͢Δ͜ͱ ͰɺΤϯτϦʔݻ༗ͷ 4BWFE4UBUF3FHJTUSZΛੜ੒ public fun SavedStateNavEntryDecorator( saveableStateHolder: SaveableStateHolder ): NavEntryDecorator { val onPop: (Any) -> Unit ={ contentKey -> saveableStateHolder.removeState(contentKey) } return navEntryDecorator(onPop = onPop) { entry -> saveableStateHolder.SaveableStateProvider( entry.contentKey ) { entry.Content() } } } BOESPJEYOBWJHBUJPOSVOUJNF4BWFE4UBUF/BW&OUSZ%FDPSBUPSLU

Slide 80

Slide 80 text

 /BW&OUSZ%FDPSBUPS 4BWFE4UBUF/BW&OUSZ%FDPSBUPS PO1PQͰঢ়ଶഁغ public fun SavedStateNavEntryDecorator( saveableStateHolder: SaveableStateHolder ): NavEntryDecorator { val onPop: (Any) -> Unit ={ contentKey -> saveableStateHolder.removeState(contentKey) } return navEntryDecorator(onPop = onPop) { entry -> saveableStateHolder.SaveableStateProvider( entry.contentKey ) { entry.Content() } } } BOESPJEYOBWJHBUJPOSVOUJNF4BWFE4UBUF/BW&OUSZ%FDPSBUPSLU

Slide 81

Slide 81 text

 /BW&OUSZ%FDPSBUPS 7JFX.PEFM4UPSF/BW&OUSZ%FDPSBUPS ΤϯτϦʔݻ༗ͷ 7JFX.PEFM4UPSF0XOFSΛఏڙ BOESPJEYMJGFDZDMFMJGFDZDMF WJFXNPEFMOBWJHBUJPO public fun ViewModelStoreNavEntryDecorator( viewModelStore: ViewModelStore, shouldRemoveStoreOwner: () -> Boolean, ): NavEntryDecorator { val storeOwnerProvider: EntryViewModel = viewModelStore.getEntryViewModel() val onPop: (Any) -> Unit = { key -> if (shouldRemoveStoreOwner()) { storeOwnerProvider .clearViewModelStoreOwnerForKey(key) } } ... return navEntryDecorator(onPop) { entry -> val childViewModelStoreOwner = remember { ... } CompositionLocalProvider( LocalViewModelStoreOwner provides childViewModelStoreOwner ) { entry.Content() } } } BOESPJEYMJGFDZDMFWJFXNPEFMOBWJHBUJPO7JFX.PEFM4UPSF/BW&OUSZ%FDPSBUPSLU

Slide 82

Slide 82 text

 /BW&OUSZ%FDPSBUPS 7JFX.PEFM4UPSF/BW&OUSZ%FDPSBUPS $PNQPTJUJPO-PDBM1SPWJEFSͰ -PDBM7JFX.PEFM4UPSF0XOFSΛ ઃఆ public fun ViewModelStoreNavEntryDecorator( ... ): NavEntryDecorator { ... return navEntryDecorator(onPop) { entry -> ... CompositionLocalProvider( LocalViewModelStoreOwner provides childViewModelStoreOwner ) { entry.Content() } } } BOESPJEYMJGFDZDMFWJFXNPEFMOBWJHBUJPO7JFX.PEFM4UPSF/BW&OUSZ%FDPSBUPSLU

Slide 83

Slide 83 text

 7JFX.PEFM4UPSF/BW&OUSZ%FDPSBUPS 7JFX.PEFM4UPSF/BW&OUSZ%FDPSBUPS 7JFX.PEFMʹ 4BWFE4UBUF)BOEMFSΛఏڙ͢Δ ͨΊͷઃఆ 4BWFE4UBUF/BW&OUSZ%FDPSBUPS ʹΑͬͯఏڙ͞ΕΔ 4BWFE4UBUF3FHJTUSZ0XOFSΛ༻͍ ΔલఏͰઃܭ͞Ε͍ͯΔ public fun ViewModelStoreNavEntryDecorator( ... ) { ... val savedStateRegistryOwner = LocalSavedStateRegistryOwner.current val childViewModelStoreOwner = remember { object : ViewModelStoreOwner, SavedStateRegistryOwner by savedStateRegistryOwner, ... { ... init { require( this.lifecycle.currentState == Lifecycle.State.INITIALIZED ) { … } enableSavedStateHandles() } } } } BOESPJEYMJGFDZDMFWJFXNPEFMOBWJHBUJPO7JFX.PEFM4UPSF/BW&OUSZ%FDPSBUPSLU

Slide 84

Slide 84 text

 7JFX.PEFM4UPSF/BW&OUSZ%FDPSBUPS 7JFX.PEFM4UPSF/BW&OUSZ%FDPSBUPS ͸ඞͣ 4BWFE4UBUF/BW&OUSZ%FDPSBUPSͷ ޙʹఆٛ͢Δ NavDisplay( ... entryDecorators = listOf( rememberSceneSetupNavEntryDecorator(), rememberSavedStateNavEntryDecorator(), // ←ઌʹ rememberViewModelStoreNavEntryDecorator(), // ←ޙʹ ), )

Slide 85

Slide 85 text

ϫϯόϯΫΞϓϦʹ͓͚Δ Ҡߦ՝୊ͱܭը

Slide 86

Slide 86 text

ϢχʔΫΩʔͷରԠ

Slide 87

Slide 87 text

 ϢχʔΫΩʔͷରԠ /BWJHBUJPOʢ/BWJHBUJPO$PNQPTFʣͱ͸ɺόοΫελοΫ্ʹಉҰͷϧʔτ ΦϒδΣΫτʢΩʔʣ͕ଘࡏ͢Δͱ͖ͷΤϯτϦʔͷੜ੒ํ๏ʹࠩҟ͕͋Δ /BWJHBUJPOɿ ಉ͡ϧʔτͰભҠͯ͠΋ຖճ৽ن/BW#BDL4UBDL&OUSZ͕ੜ੒͞ΕΔ /BWJHBUJPOɿ Ωʔ͕ಉҰͳΒಉ͡ΤϯτϦʔͱͯ͠ѻΘΕΔ

Slide 88

Slide 88 text

 ϢχʔΫΩʔͷରԠ

Slide 89

Slide 89 text

 ϢχʔΫΩʔͷରԠ σΟʔϓϦϯΫ΍௨஌ͳͲɺ༧ଌ Ͱ͖ͳ͍ભҠ͕ݟࠐ·ΕΔ৔߹ɺ ΩʔॏෳʹΑΔҙਤ͠ͳ͍ঢ়ଶ෮ ݩ͕ൃੜ͠ͳ͍͔Λ֬ೝ͢Δ ঢ়ଶ෮ݩΛ๷͙৔߹͸ɺର৅ͷ Ωʔ͕౎౓ϢχʔΫͱͳΔΑ͏ʹ ͢Δ // ຖճ৽͍͠ΩʔΛੜ੒ // ΩʔͷҰҙੑ͸ NavEntry.contentKey ʹΑܾͬͯ·Δ // contentKey ͷσϑΥϧτ͸ key.toString() data class Form( val id: String = UUID.randomUUID().toString() )

Slide 90

Slide 90 text

 ϢχʔΫΩʔͷରԠ /BWJHBUJPO͸ͦ΋ͦ΋όοΫε λοΫ্ʹಉҰΩʔ͕ੵ·ΕΔ͜ ͱΛ૝ఆ͍ͯ͠ͳ͍ʁ ࿈ଓͯ͠ಉҰΤϯτϦʔʹભ Ҡ͠Α͏ͱ͢ΔͱɺભҠͷΞχ ϝʔγϣϯ͕ແࢹ͞ΕΔ ಉҰΩʔʹʢঢ়ଶΛอ࣋ͭͭ͠ʣ ભҠ͍ͨ͠৔߹͸ɺΩʔ͕ॏෳ͠ ͳ͍Α͏ʹ੍ޚ͢Δ🙆 // ϢχʔΫΩʔΛอͪͭͭભҠ fun MutableList.navigateTo(key: T) { remove(key) add(key) }

Slide 91

Slide 91 text

όοΫελοΫΩʔͷ੍໿

Slide 92

Slide 92 text

 όοΫελοΫΩʔͷ੍໿ όοΫελοΫΛϓϩηεΩϧΛ ލ͍Ͱੜଘͤ͞ΔͨΊʹ͸ɺ SFNFNCFS/BW#BDL4UBDLΛ༻͍ Δ ˠ!4FSJBMJ[BCMFͷ੍໿͕ੜ ͡Δ @Serializable data object FoodList : NavKey val backStack : SnapshotStateList = rememberNavBackStack(FoodList)

Slide 93

Slide 93 text

 όοΫελοΫΩʔͷ੍໿ ϫϯόϯΫͰ͸ 5ZQF4BGF/BWJHBUJPO ʢ/BWJHBUJPOʣʹ͓͚Δ !4FSJBMJ[BCMFͷ੍໿ʹΑͬͯɺ ͍͔ͭ͘ݒ೦͕ੜ͍ͯ͡Δ @Serializable data class UserRoute(val userId: String)

Slide 94

Slide 94 text

 όοΫελοΫΩʔͷ੍໿ !4FSJBMJ[BCMF੍໿ͷݒ೦ࣄ߲ Ξϊςʔτͷҙਤ͕఻ΘΓͮΒ ͍ʢ͏͔ͬΓফͯ͠͠·͏ڪΕʣ Ξϊςʔτ͕6*ϨΠϠʔΛ௒͑ ͯɺσʔλϨΠϠʹ೾ٴ // ͳΜͷͨΊͷΞϊςʔτʁ @Serializable data class UserRoute(val user: User) // σʔλϨΠϠ͕ Navigation ੍໿Λ஌͍ͬͯΔ @Serializable data class User(val id: String)

Slide 95

Slide 95 text

 όοΫελοΫΩʔͷ੍໿ !4FSJBMJ[BCMF ੍໿ʹࡍͯ͠ͷίʔυن໿ /BWJHBUJPO໨తͰ͋Δ͜ͱΛࣔ ͢UZQFBMJBTΛ࢖༻ // φϏήʔγϣϯ໨తͰ͋Δ͜ͱͷ໌ࣔ༻ typealias NavSerializable = kotlinx.serialization.Serializable @NavSerializable data class UserNavKey(val userId: String) : NavKeyɹ

Slide 96

Slide 96 text

 όοΫελοΫΩʔͷ੍໿ !4FSJBMJ[BCMF ੍໿ʹࡍͯ͠ͷίʔυن໿ 6*ϨΠϠʔ֎ͰͷφϏήʔγϣ ϯ໨తͷΞϊςʔτͷېࢭ // UI ϨΠϠʔ֎ͰͷφϏήʔγϣϯ໨తͷΞϊςʔτΛېࢭ // @NavSerializable data class User(val id: String) // ϞσϧσʔλΛɺΩʔσʔλʹม׵ fun UserModel.mapNavKey() = UserNavKey(this.id)ɹ

Slide 97

Slide 97 text

ωετάϥϑͷҠߦ

Slide 98

Slide 98 text

 ωετάϥϑͷҠߦ ϫϯόϯΫΞϓϦͰ͸ɺભҠϑ ϩʔͷΧϓηϧԽͷ໨తͰɺωε τ͞ΕͨφϏήʔγϣϯάϥϑΛ ࠾༻ ΧʔυͷೖۚϑϩʔͳͲ NavHost(navController, startDestination = Home) { composable { HomeScreen() } /** ↓ ωετ͞ΕͨφϏήʔγϣϯάϥϑ **/ navigation( startDestination = DepositStart ) { composable { DepositStartScreen( ɹɹɹɹɹɹɹ onComplete = { ɹɹɹɹɹɹɹ navController.navigate(DepositComplete) ɹɹɹɹɹɹɹ } ɹɹɹɹɹɹɹ) } composable { DepositComplete() } } }

Slide 99

Slide 99 text

 ωετάϥϑͷҠߦ ʮϧʔτΤϯτϦʔ͕ 4UPSF0XOFSͷ7JFX.PEFMʯΛ ڞ༻͢Δ͜ͱͰɺάϥϑ಺ͷը໘ ؒͰঢ়ଶڞ༻ navigation(startDestination = DepositStart) { composable { val parentEntry = remember(it) { navController.getBackStackEntry(Deposit) } val parentViewModel = viewModel( viewModelStoreOwner = parentEntry ) DepositStartScreen( onComplete = { depositData -> /* ωετάϥϑͷϥΠϑαΠΫϧʹσʔλΛอଘ */ parentViewModel.saveDepositData(depositData) navController.navigate(DepositComplete) } ) } composable { val parentEntry = ... val parentViewModel = ... DepositComplete(data = parentViewModel.depositData) } }

Slide 100

Slide 100 text

 ωετάϥϑͷҠߦ

Slide 101

Slide 101 text

 ωετάϥϑͷҠߦ /BWJHBUJPOͰωετάϥϑΛ࠶ ݱ͢Δʹ͸ɺάϥϑͱ͍ͨ͠Τϯ τϦʔ಺Ͱ/BW%JTQMBZΛωετ ͢Δ͚ͩ NavDisplay( backStack = routeBackStack, entryProvider = entryProvider { entry(Home) { HomeScreen() } entry(Deposit) { /* ωετ͞Εͨ NavDisplay */ NavDisplay( ... ) } } )

Slide 102

Slide 102 text

 ωετάϥϑͷҠߦ ⚠OBWJHBUJPOVJBMQIBͰ͸ɺωετ/BW%JTQMBZͷόοΫελοΫ͕࢒Γ ݅ͷͱ͖ʹɺ໭Δૢ࡞Λ͢ΔͱΫϥογϡ ਌/BW%JTQMBZͷQSFEJDUJWFCBDLHFTUVSFঢ়ଶΛޡೝ͠ɺࣗ਎΋QSFEJDUJWFCBDLத ʹ͋Δͱ൑அ͠ɺଘࡏ͠ͳ͍എ໘γʔϯΛඳը͠Α͏ͱͯ͠Ϋϥογϡ εφοϓγϣοτ൛Ͱ͸मਖ਼͞Ε͍ͯΔͷͰɺҰ࣌తͳෆ۩߹ͷ͸ͣ😮

Slide 103

Slide 103 text

 ωετάϥϑͷҠߦ entry(Deposit) { val parentViewModel = viewModel() NavDisplay( backStack = depositBackStack, entryProvider = entryProvider { entry(DepositStart) { DepositStartScreen( onComplete = { depositData -> parentViewModel.saveDepositData(depositData) depositBackStack.add(DepositComplete) } ) } entry(DepositComplete) { DepositCompleteScreen( data = parentViewModel.depositData ) } } ) ) ωετ/BW%JTQMBZ಺Ͱ 7JFX.PEFMΛڞ༻͍ͨ͠৔߹ ͸ɺωετ/BW%JTQMBZͷඳըΤ ϯτϦʔΛ4UPSF0XOFSͱ͢Δ 7JFX.PEFMΛ༻͍Δ

Slide 104

Slide 104 text

 ωετάϥϑͷҠߦ ⚠/BW%JTQMBZωετߏ଄ͷͱ͖ɺ਌/BW%JTQMBZʹࢦఆͨ͠4DFOF4USBUFHZ /BW&OUSZ%FDPSBUPS͸ɺωετ/BW%JTQMBZͷΤϯτϦʔʹ͸ద༻͞Εͳ͍

Slide 105

Slide 105 text

Ҡߦͷॱং

Slide 106

Slide 106 text

 Ҡߦͷॱং ϫϯόϯΫͷݱঢ়ͷφϏήʔγϣ ϯߏ੒

Slide 107

Slide 107 text

 Ҡߦͷॱং Ҡߦ͸ωετάϥϑ͔ΒਐΊΔ ᶃωετάϥϑΛղମ͠ϑϥοτ ͳϧʔτఆٛ ʢ/BW(SBQI#VJMEFSDPNQPTBCMFʣ ʹஔ͖׵͑Δ ᶄ಺෦Ͱ/BW%JTQMBZΛ༻͍Δ

Slide 108

Slide 108 text

 ωετάϥϑͷҠߦ navigation( startDestination = DepositStart ) { composable { DepositStartScreen( ... ) } composable { DepositCompleteScreen( ... ) } } composable { val depositBackStack = remember { mutableStateListOf(DepositStart) } NavDisplay( backStack = depositBackStack, entryProvider = entryProvider { entry { DepositStartScreen( ... ) } entry { DepositCompleteScreen( ... ) } } ) }

Slide 109

Slide 109 text

 ܭըᶃωετ͞ΕͨάϥϑͷҠߦ composable { ... ɹɹ val parentViewModel =ɹ viewModel() NavDisplay( backStack = depositBackStack, entryProvider = entryProvider { entry { ... } entry { DepositCompleteScreen( data = parentViewModel.depositData ) } } ) } ΤϯτϦʔؒͰڞ༻͢Δ 7JFX.PEFM͸ /BW#BDL4UBDL&OUSZ௚ԼͰੜ੒ ͠ɺ֤ΤϯτϦʔͰ௚઀ࢀর͢Δ

Slide 110

Slide 110 text

 Ҡߦͷॱং ωετάϥϑͷҠߦ͕શͯ׬ྃ͠ ͨΒɺ/BW)PTUΛ/BW%JTQMBZʹ ஔ͖׵͑Δ

Slide 111

Slide 111 text

)JMU 7JFX.PEFM ͱͷ౷߹

Slide 112

Slide 112 text

 )JMU 7JFX.PEFM ͱͷ౷߹ /BWJHBUJPOͰ)JMU7JFX.PEFM Λ࢖༻͢Δ৔߹͸ɺ௨ৗͷ 7JFX.PEFM࢖༻࣌ͱಉ༷ʹɺ 7JFX.PEFM4UPSF/BW&OUSZ%FDP SBUPSΛઃఆ͢Δ NavDisplay( entryDecorators = listOf( … rememberViewModelStoreNavEntryDecorator() ), entryProvider = entryProvider { entry(DetailKey) { val viewModel = hiltViewModel() } } ... ) @HiltViewModel class DetailViewModel @AssistedInject constructor( val detailRepository: DetailRepository ) { ... }

Slide 113

Slide 113 text

 )JMU 7JFX.PEFM ͱͷ౷߹ /BWJHBUJPO$PNQPTFͷ/BW"SHT͸7JFX.PEFMͷ4BWFE4UBUF)BOEMFʹࣗಈͰ อଘ͞ΕΔ͕ɺ/BWJHBUJPOͷΩʔ͸อଘ͞Εͳ͍ ʢΩʔ͸"OZܕͳͷͰɺ4BWFE4UBUFʹอଘͰ͖Δܗࣜͱ͸ݶΒͳ͍ʣ

Slide 114

Slide 114 text

 )JMU 7JFX.PEFM ͱͷ౷߹ !"TTJTUFE'BDUPSZܦ༝ͰΩʔΛ %*͢Δྫ NavDisplay( entryProvider = entryProvider { entry(DetailKey) { val viewModel = ɹɹɹɹɹ hiltViewModel( creationCallback = { factory -> factory.create(key) } ) } } ... ) @HiltViewModel(assistedFactory = DetailViewModel.Factory::class) class DetailViewModel @AssistedInject constructor( @Assisted val key : DetailKey ) : ViewModel() { @AssistedFactory interface Factory { fun create(navKey: DetailKey): DetailViewModel } }

Slide 115

Slide 115 text

σΟʔϓϦϯΫφϏήʔγϣϯ

Slide 116

Slide 116 text

 σΟʔϓϦϯΫφϏήʔγϣϯ /BWJHBUJPOͰ͸/BWJHBUJPOͱಉ౳ͷσΟʔϓϦϯΫ63-ͷϋϯυϦϯάػೳ ͕ݱঢ়αϙʔτ͞Εͯͳ͍

Slide 117

Slide 117 text

 σΟʔϓϦϯΫφϏήʔγϣϯ Ծʹ 63-Λࣗಈղੳ὎Ωʔʹม׵὎ όοΫελοΫʹੵΉ ͱ͍ͬͨػೳ͕ϥΠϒϥϦͰఏڙ ͞Εͨ৔߹ɺόοΫελοΫΛࣗ લͰ؅ཧͰ͖Δར఺͔Βҳ୤͢ Δɾɾ ˠ͢ͳΘͪࣗલ࣮૷͕ٻΊΒ ΕΔ͔΋ʁ

Slide 118

Slide 118 text

 σΟʔϓϦϯΫφϏήʔγϣϯ ࣗલͰϋϯυϦϯάॲཧΛ࣮૷͢ Δྫᶃ ΩʔʹରԠ͢Δ63-จࣈྻΛฦ ͢ΠϯλʔϑΣʔε 63-จࣈྻ͔ΒΩʔʹม׵͢Δ ΠϯλʔϑΣʔε Λఆٛ // ΩʔͰ࣮૷ fun interface NavDeepLinkKey: NavKey { fun toUrl(): String? } // Ωʔ΋͘͠͸Ωʔ಺ͷ companion object Ͱ࣮૷ fun interface NavDeeplinkParser { fun parse(url: String): NavDeepLinkKey? }

Slide 119

Slide 119 text

 σΟʔϓϦϯΫφϏήʔγϣϯ ࣗલͰϋϯυϦϯάॲཧΛ࣮૷͢ Δྫᶄ %BUBPCKFDUͰ͸ɺ྆ํͷΠϯ λʔϑΣʔεΛ࣮૷ data object Home : NavDeepLinkKey, NavDeeplinkParser { override fun toUrl(): String = "myapp://home" override fun parse(url: String) = if (url == toUrl()) Home else null }

Slide 120

Slide 120 text

 σΟʔϓϦϯΫφϏήʔγϣϯ ࣗલͰϋϯυϦϯάॲཧΛ࣮૷͢ Δྫᶅ %BUBDMBTTͰ͸ɺΫϥεͱ $PNQBOJPOPCKFDUʹ෼͚࣮ͯ૷ data class Detail(val id: String) : NavDeepLinkKey { override fun toUrl(): String = “$URL${id}" // ύʔεॲཧ͸ Companion object Ͱ࣮૷ companion object DeepLinkParser : NavDeeplinkParser { private const val URL = "myapp://detail/" override fun parse(url: String) = if (url.startsWith(URL)) { Detail(id = url.removePrefix(URL)) ɹɹɹɹɹɹɹ} else { ɹɹɹɹɹɹɹ null } } }

Slide 121

Slide 121 text

 σΟʔϓϦϯΫφϏήʔγϣϯ ࣗલͰϋϯυϦϯάॲཧΛ࣮૷͢ Δྫᶆ ύʔεॲཧΛॱʹ࣮ߦ͠ɺ࠷ॳʹ ݟ͔ͭͬͨΩʔΛόοΫελοΫ ʹ௥Ճ fun MutableList.navigateTo( url: String ) { val key = parse(url) ?: return add(key) } fun parse(url: String): NavDeepLinkKey? = listOf( Home, Detail.DeepLinkParser, /* ύʔεର৅ͷΩʔ͕૿͑ͨΒ͜͜ʹ௥Ճ */ ).firstNotNullOfOrNull { it.parse(url) }

Slide 122

Slide 122 text

#PUUPN4IFFUͷҠߦ

Slide 123

Slide 123 text

 #PUUPN4IFFUͷҠߦ #PUUPN4IFFU/BWJHBUPS ʢBOESPJEYDPNQPTFNBUFSJBMNB UFSJBMOBWJHBUJPOʣΛҠߦ͢Δ

Slide 124

Slide 124 text

 #PUUPN4IFFUͷҠߦ /BWJHBUJPOʹ͸#PUUPN4IFFU/BWJHBUPSͱಉ౳ͷɺ#PUUPN4IFFUΛΤϯτ Ϧʔͱͯ͠ѻ͏࢓૊Έ͕zݱঢ়zαϙʔτ͞Ε͍ͯͳ͍ ͭ·Γɺzݱঢ়zࣗલͰ࣮૷͢Δඞཁ͕͋Δ

Slide 125

Slide 125 text

 #PUUPN4IFFUͷҠߦ #PUUPN4IFFUͷඳըཁ݅ ௨ৗͷભҠΞχϝʔγϣϯΛ ࠶ੜ͠ͳ͍ ଞͷγʔϯʹ෴͍ඃ͞ΔܗͰ ඳը͢Δ ্هཁ݅Λຬͨͨ͢Ίʹ 0WFSMBZ4DFOFΛ༻͍Δ

Slide 126

Slide 126 text

 #PUUPN4IFFUͷҠߦ 4DFOF4USBUFHZͱϝλσʔλͷઃ ఆ class BottomSheetSceneStrategy : SceneStrategy { companion object { internal const val KEY_BOTTOM_SHEET = "bottomSheet" fun bottomSheet(): Map = ɹɹɹɹɹɹɹɹɹmapOf(KEY_BOTTOM_SHEET to Unit) } } NavDisplay( sceneStrategy = remember { BottomSheetSceneStrategy() } entryProvider = entryProvider { entry { ... } entry( metadata = BottomSheetSceneStrategy.bottomSheet() ) { ... } } )

Slide 127

Slide 127 text

 #PUUPN4IFFUͷҠߦ ϝλσʔλͷ֬ೝ class BottomSheetSceneStrategy : SceneStrategy { @Composable override fun calculateScene( entries: List>, onBack: (count: Int) -> Unit ): Scene? { val last = entries.lastOrNull() val metadata = last?.metadata[KEY_BOTTOM_SHEET] if(metadata == null) return null ... } }

Slide 128

Slide 128 text

 #PUUPN4IFFUͷҠߦ γʔϯͷ࡞੒ @Composable override fun calculateScene( entries: List>, onBack: (count: Int) -> Unit ): Scene? { ... return BottomSheetScene( key = last.contentKey, previousEntries = entries.dropLast(1), overlaidEntries = entries.dropLast(1), entry = last, onBack = onBack, ) }

Slide 129

Slide 129 text

 ՝୊ᶃ#PUUPN4IFFUͷҠߦ 4DFOFͷ࣮૷ .PEBM#PUUPN4IFFU্ʹΤϯτ ϦʔͷίϯςϯπΛඳը import androidx.compose.material3.ModalBottomSheet internal class BottomSheetScene( override val key: Any, override val previousEntries: List>, override val overlaidEntries: List>, private val entry: NavEntry, private val onBack: (count: Int) -> Unit, ) : OverlayScene { override val entries: List> = listOf(entry) @OptIn(ExperimentalMaterial3Api::class) override val content: @Composable () -> Unit = { ModalBottomSheet( onDismissRequest = { onBack(1) }, ) { entry.Content() } } }

Slide 130

Slide 130 text

 ՝୊ᶃ#PUUPN4IFFUͷҠߦ

Slide 131

Slide 131 text

/BWJHBUJPO#BSͷҠߦ

Slide 132

Slide 132 text

 /BWJHBUJPO#BSͷҠߦ ϫϯόϯΫͷ/BWJHBUJPO#BS

Slide 133

Slide 133 text

 /BWJHBUJPO#BSͷҠߦ ϫϯόϯΫ/BWJHBUJPO#BSͷ όοΫελοΫ؅ཧ όοΫελοΫͷϧʔτ͸ʮϗʔ Ϝʯ ʮϗʔϜʯҎ֎͸ඇબ୒ঢ়ଶͱ ͳͬͨΒQPQ͢Δ ֤λϒͷঢ়ଶ͸QPQ࣌ʹอଘ͢Δ

Slide 134

Slide 134 text

 /BWJHBUJPO#BSͷҠߦ ϫϯόϯΫ/BWJHBUJPO#BSͷ όοΫελοΫ؅ཧ όοΫελοΫͷϧʔτ͸ʮϗʔ Ϝʯ ʮϗʔϜʯҎ֎͸ඇબ୒ঢ়ଶͱ ͳͬͨΒQPQ͢Δ ֤λϒͷঢ়ଶ͸QPQ࣌ʹอଘ͢Δ

Slide 135

Slide 135 text

 /BWJHBUJPO#BSͷҠߦ /BWJHBUJPOͰ͸όοΫελοΫ ʹੵ·Ε͍ͯΔΤϯτϦʔͷঢ়ଶ ͷΈ͕อ࣋͞ΕΔ

Slide 136

Slide 136 text

 /BWJHBUJPO#BSͷҠߦ λϒͷঢ়ଶΛอ࣋ɾ෮ݩ͢Δʹ ͸ɺόοΫελοΫ͔ΒΩʔΛ࡟ আͤͣɺॱ൪ΛೖΕସ͑Δʂ

Slide 137

Slide 137 text

 /BWJHBUJPO#BSͷҠߦ όοΫελοΫͷॳظԽ όοΫελοΫʹશͯͷλϒ ΩʔΛੵΉ ࠷ޙͷΩʔʢ࠷ॳʹඳը͢Δը ໘ʣ͕ʮϗʔϜʯͱͳΔΑ͏ʹฒ ͼସ͑Δ ˠॳظը໘͕ʮϗʔϜʯͷঢ়ଶ sealed interface TabKey { data object Home : TabKey data object Card : TabKey data object Pocket : TabKey companion object { val all = listOf(Home, Card, Pocket) } } val backStack = remember { mutableStateListOf().apply { addAll(TabKey.all) remove(TabKey.Home) add(TabKey.Home) } }

Slide 138

Slide 138 text

 /BWJHBUJPO#BSͷҠߦ λϒબ୒ॲཧ ʮϗʔϜʯλϒΛબ୒࣌ɺϗʔϜ ΩʔΛελοΫͷ࠷ޙʹੵΉ ʮϗʔϜʯҎ֎ͷλϒΛબ୒࣌ɺ ϗʔϜΩʔˠબ୒ͨ͠Ωʔͷॱ൪ ͰελοΫͷ࠷ޙʹੵΉ ʮϗʔϜʯҎ֎ͷλϒͷ໭Γ ઌ͕ʮϗʔϜʯʹͳΔ NavigationBarItem( selected = tabKey == backStack.last(), onClick = { backStack.apply { remove(TabKey.Home) if (tabKey != TabKey.Home) remove(tabKey) add(TabKey.Home) if (tabKey != TabKey.Home) add(tabKey) } }, ... )

Slide 139

Slide 139 text

 /BWJHBUJPO#BSͷҠߦ ʮϗʔϜʯͰ໭Δૢ࡞࣌ͷ໰୊ QPQͨ͠Α͏ʹݟ͔͚ͤͨखલͷ ελοΫʹ໭Εͯ͠·͏😨 ڧ੍తʹ"DUJWJUZΛด͡ΔΑ͏ͳ ॲཧΛೖΕͯ΋ɺ1SFEJDUJWF #BDL࣌ʹखલͷελοΫͷΤϯ τϦʔ͕ඳը͞ΕΔ😨

Slide 140

Slide 140 text

 /BWJHBUJPO#BSͷҠߦ ϗʔϜλϒΑΓલͷόοΫελο ΫΛແࢹ͢ΔͨΊͷΧελϜ 4DFOFΛ࡞੒͢Δ private class HomeTabSceneStrategy : SceneStrategy { @Composable override fun calculateScene( entries: List>, onBack: (Int) -> Unit ): Scene? { return object : Scene { override val key: Any = "HomeTabScene" override val entries: List> = listOf(entries.last) override val previousEntries: List> =ɹ emptyList() override val content: @Composable () -> Unit = { last.Content() } } } }

Slide 141

Slide 141 text

 /BWJHBUJPO#BSͷҠߦ QSFWJPVT&OUSJFTΛۭ഑ྻͱ͢Δ ͜ͱͰɺ͜ΕҎ্໭ΕΔΤϯτ Ϧʔ͕ແ͍͜ͱΛ/BW%JTQMBZʹ఻ ͑Δ QSFWJPVT&OUSJFT͕ۭͷͱ͖ɺ ໭Δૢ࡞͸ԼҐͷϋϯυϥ ʢ"DUJWJUZͳͲʣʹҕৡ͞ΕΔ private class HomeTabSceneStrategy : SceneStrategy { @Composable override fun calculateScene( entries: List>, onBack: (Int) -> Unit ): Scene? { return object : Scene { override val key: Any = "HomeTabScene" override val entries: List> = listOf(entries.last) override val previousEntries: List> =ɹ emptyList() override val content: @Composable () -> Unit = { last.Content() } } } }

Slide 142

Slide 142 text

 /BWJHBUJPO#BSͷҠߦ ʮϗʔϜʯλϒͷΤϯτϦʔʹ γʔϯઐ༻ͷϝλσʔλΛઃఆ private class HomeTabSceneStrategy : SceneStrategy { @Composable override fun calculateScene( ... ): Scene? { val last = entries.lastOrNull() ?: return null if (!last.metadata.containsKey(HOME_TAB_KEY)) return null return object : Scene { ... } } companion object { internal const val HOME_TAB_KEY = "HomeTab" fun homeTab() = mapOf(HOME_TAB_KEY to true) } } entry( metadata = homeTab() ) { ... }

Slide 143

Slide 143 text

 /BWJHBUJPO#BSͷҠߦ

Slide 144

Slide 144 text

ʲ෇࿥ʳ/BW&OUSZ%FDPSBUPSͱ 4DFOFΛΧελϜ͢Δ🛠

Slide 145

Slide 145 text

 /BW&OUSZ%FDPSBUPSͱ4DFOFΛΧελϜ͢Δ🛠 /BWJHBUJPOʹ͸ɺ/BW&OUSZ%FDPSBUPSͱ4DFOFͷ छྨͷΤϯτϦʔ֦ுϙΠϯτ͕͋Δ

Slide 146

Slide 146 text

 /BW&OUSZ%FDPSBUPSͱ4DFOFΛΧελϜ͢Δ🛠 ؍఺ /BW&OUSZ%FDPSBUPS 4DFOF ༻్ ΤϯτϦʔ಺෦ͷॲཧΛ֦ு͢Δʢঢ়ଶอ࣋ɺґଘ ੑ஫ೖɺϥΠϑαΠΫϧ؅ཧͳͲʣ ෳ਺ΤϯτϦʔΛ·ͱΊͯɺը໘্ͰͲ͏ߏ੒ɾඳը ͢Δ͔ΛܾΊΔ Өڹൣғ ݸʑͷΤϯτϦʔ୯Ґʹݶఆɻہॴతʹ௥Ճ΍ࠩ͠ ସ͕͑Մೳ ը໘શମ΍ෳ਺ΤϯτϦʔʹ·͕ͨΔɻ6*ߏ଄΍Ξ χϝʔγϣϯશൠʹӨڹ ར༻γʔϯ ΤϯτϦʔʹ7JFX.PEFMείʔϓΛ෇༩ 4BWFE4UBUFͷࣗಈ࿈ܞ όοΫελοΫ͔Β֎ΕͨࡍͷϦιʔεղ์ γϯάϧϖΠϯϚϧνϖΠϯ੾Γସ͑ μΠΞϩάɾϘτϜγʔτͷॏͶ߹Θͤ 1SFEJDUJWF#BDL࣌ͷද੍ࣔޚ ੹຿ͷཻ౓ ʮΤϯτϦʔͭʯʹดͨ͡੹຿ɻத਎ΛͲ͏ѻ͏͔ ʮΤϯτϦʔू߹ʯͱͯ͠ͷ੹຿ɻݟͤํɾ഑ஔɾભ ҠΛͲ͏ઃܭ͢Δ͔

Slide 147

Slide 147 text

 /BW&OUSZ%FDPSBUPSͱ4DFOFΛΧελϜ͢Δ🛠 ͬ͘͟Γɾɾ ݸʑͷΤϯτϦʔʹԣஅతͳϩδοΫΛࠩ͠ࠐΈ͍ͨͱ͖ ˠ/BW&OUSZ%FDPSBUPS  ʢෳ਺ͷʣΤϯτϦʔΛͲͷΑ͏ʹ഑ஔɾදࣔ͢Δ͔Λίϯτϩʔϧ͍ͨ͠ͱ͖ ˠ4DFOF

Slide 148

Slide 148 text

 /BW&OUSZ%FDPSBUPSͱ4DFOFΛΧελϜ͢Δ🛠 ҎԼͷཁ݅Λ֦ு͢ΔͳΒ Ͳ͕ͬͪద੾ʁ νϟΠϧυΞΧ΢ϯτͰͷද͕ࣔ ېࢭ͞Ε͍ͯΔը໘ʹ͓͍ͯɺ୅ ΘΓʹϒϩοΫը໘Λඳը͢Δ

Slide 149

Slide 149 text

 /BW&OUSZ%FDPSBUPSͱ4DFOFΛΧελϜ͢Δ🛠 ਖ਼ղ͸ɾɾɾ /BW&OUSZ%FDPSBUPSʂʂʂ

Slide 150

Slide 150 text

 /BW&OUSZ%FDPSBUPSͱ4DFOFΛΧελϜ͢Δ🛠 ʮνϟΠϧυΞΧ΢ϯτͷͱ͖ϒϩοΫը໘ʹࠩ͠ସ͑Δʯͷ͸ίϯςϯπͷ ॏཁͳखଓ͖ 4DFOFϨϕϧͩͱʮΤϯτϦʔ͕ͦͷ4DFOF্Ͱඳը͞Εͳ͍ͱνΣοΫ͞Ε ͳ͍ʯͷͰൈ͚࿙ΕͷϦεΫ͕͋Δ

Slide 151

Slide 151 text

 /BW&OUSZ%FDPSBUPSͱ4DFOFΛΧελϜ͢Δ🛠 νϟΠϧυΞΧ΢ϯτͰͷද͕ࣔ ېࢭ͞Ε͍ͯΔը໘ʹ͓͍ͯɺ୅ ΘΓʹϒϩοΫը໘Λඳը͢Δྫ const val KEY_BLOCK_FOR_CHILD = "blockForChild" fun blockForChild(): Map = mapOf(KEY_BLOCK_FOR_CHILD to true) @Composable fun rememberChildBlockNavEntryDecorator(isChild: Boolean) = remember> { navEntryDecorator { entry -> entry.metadata[KEY_BLOCK_FOR_CHILD] ?: return@navEntryDecorator if (isChild) BlockContent() else entry.Content() } }

Slide 152

Slide 152 text

·ͱΊ

Slide 153

Slide 153 text

 ·ͱΊ /BWJHBUJPO͸$PNQPTFઐ༻ʹ࠶ઃܭ͞ΕͨφϏήʔγϣϯϥΠϒϥϦ 6*ঢ়ଶͱભҠঢ়ଶ͕౷߹͞Εɺ$PNQPTFΒ͍͠γϯϓϧͳઃܭʹ 4DFOF4DFOF4USBUFHZʹΑͬͯΞμϓςΟϒϨΠΞ΢τ͕ॊೈʹ࣮ݱՄೳ ୯Ұը໘͔ΒϦετৄࡉϨΠΞ΢τɺμΠΞϩά΍ϘτϜγʔτ·ͰରԠ /BW&OUSZ%FDPSBUPSͰΤϯτϦʔ୯Ґͷ੹຿֦ு͕Մೳ ঢ়ଶอ࣋ɾ7JFX.PEFMɾݖݶ੍ޚͳͲɺԣஅతॲཧΛγϯϓϧʹ࣮૷ Ҡߦʹ޲͚ͨ՝୊੔ཧ͕ඞཁ ϢχʔΫΩʔରԠɺ!4FSJBMJ[BCMF੍໿ɺωετάϥϑ΍/BWJHBUJPO#BSͷ࠶ઃܭ

Slide 154

Slide 154 text

 ·ͱΊ ࠓ͔ΒͰ͖Δඋ͑ όοΫελοΫ؅ཧํ๏ΛܾΊΔ Ωʔͷઃܭ +FUQBDL$PNQPTF΁ͷҠߦΛਐΊΔ

Slide 155

Slide 155 text

 🐶͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠🐶