Slide 1

Slide 1 text

Jetpack Compose A to Z ✨૑ࢿ࠼

Slide 2

Slide 2 text

👋૑ࢿ࠼ 🏠TVOHCJO 🏰TVOHCJOMBOE 🐙HJUIVCDPNKJTVOHCJO

Slide 3

Slide 3 text

$PNQPTFࣗѐ $PNQPTFҳࢿਃࣗ )PXJUXPSLT $PNQJMFS%FCVHHJOH #FTU1SBDUJDF 8SBQVQ 📖ݾର

Slide 4

Slide 4 text

↟ BMQIBӝળ ↟ ௏٘ࣁࠗࢎ೦ࢤۚ 📝द੘ೞӝ੹ী

Slide 5

Slide 5 text

1. Compose ࣗѐ ஹನૉܳ৵ॄঠೡө

Slide 6

Slide 6 text

ழझథٜ࠭ܳ݅ӝਤೠҗ੿੉ցޖӡ׮ ܻࢎ੉௿۞࠭৬э੉೙ࣻ੸ੋஹನք౟ܳҳࢿೞחؘҗ੿੉ցޖӡ׮ ࢚కҙܻоࣻز੉ۄपࣻೞӝऔ׮ 😵💫ӝઓ6*ѐߊ੄ޙઁ੼

Slide 7

Slide 7 text

tࢤ࢑ࢿ੷ೞu

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

җ੿੉হ׮

Slide 12

Slide 12 text

→ࢤ࢑ࢿ߂੤ࢎਊࢿૐо

Slide 13

Slide 13 text

2. Compose ҳࢿਃࣗ ஹನૉܳઁ؀۽੉೧ೞӝਤೠӝࠄѐ֛

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

/** * Composable য֢ప੉࣌਷ ஹನૉ ೣࣻীࢲ ೙ࣻ੸ਵ۽ ࢎਊغݴ ௼ѱ 3о૑੄ ৉ೡਸ ыणפ׮. * * 1. ݵ١ࢿ੄ ࠁ੢ * 2. ਤ஖ ݫݽ੉ઁ੉࣌ ഝࢿച * 3. ؘ੉ఠ ઁҕ ژח ஹನ੷࠶ ߑ୹ */ @MustBeDocumented @Retention(AnnotationRetention.BINARY) @Target( AnnotationTarget.FUNCTION, AnnotationTarget.TYPE, AnnotationTarget.TYPE_PARAMETER, AnnotationTarget.PROPERTY_GETTER ) annotation class Composable

Slide 16

Slide 16 text

// э਷ input ਸ ߉਷ ஹನ੷࠶ਸ ݵ١ࢿ੄ ࠁ੢ਵ۽ ೦࢚ ੤प೯ਸ Ѥցګ׮. // ݵ١ࢿ: э਷ ো࢑੄ Ѿҗח ೦࢚ زੌೞ׮ח ࢿ૕ @Composable fun Idempotent() { // ݅ড UI јन੉ ೙ਃೞ׮ݶ... Text(text = "ࢿ࠼ے٘") // ੉ ஹನ੷࠶਷ ೠ ߣ݅ प೯ػ׮. Text(text = System.currentTimeMillis().toString()) // ੉ ஹನ੷࠶਷ ೦࢚ ੤प೯ػ׮. }

Slide 17

Slide 17 text

// ஹನ੷࠶਷ ۠ఋ੐ীࢲ Ѿ੿ػ ਋ࢶࣽਤী ݏѱ ߽۳۽ प೯ؽਵ۽ ஹನ੷࠶੄ प೯ ࣽࢲী ੄ઓೞח Ѥ ০૑ ঋ׮. // ٮۄࢲ ݵ١ࢿਸ ࠁ੢ೞח Ѫ੉ ઺ਃೞ׮. lateinit var text: String @Composable fun PureFunction() { LoadText() ShowText() } @Composable private fun LoadText() { text = "Bye, world!" Text(text = "Load Text...") } @Composable private fun ShowText() { Text(text = text) // text is not initialized properly оמࢿ ੓਺ }

Slide 18

Slide 18 text

// ஹನ੷࠶ ೣࣻ੄ दӒפ୊৬ call-site, ӒܻҊ प೯ػ ࣽࢲܳ ӝળਵ۽ Ҋਬೠ key о ࢤࢿغҊ, // ஹನૉח ೧׼ key ܳ ӝળਵ۽ ݽٚ ؘ੉ఠܳ ੷੢ೞҊ ҙܻೠ׮. // э਷ key ܳ о૓ ஹನ੷࠶੉ э਷ input ਵ۽ ੤प೯ ػ׮ݶ ӝઓী प೯ػ Ѿҗܳ Ӓ؀۽ ੤ࢎਊೠ׮. // -> ݵ١ࢿҗ ਤ஖ ݫݽ੉ઁ੉࣌੄ Ѿҗ // ਤ஖ ݫݽ੉ઁ੉࣌: э਷ ਤ஖ীࢲ э਷ input ਵ۽ प೯غݶ ೦࢚ э਷ output ੉ աৢ Ѫ੉ۄҊ о੿ೞҊ, // чਸ நयೞҊ ੤ࢎਊ ೞח ӝࣿ @Composable fun PositionMemoization() { repeat(5) { Text(text = "Bye, world!") // э਷ call-site ੉૑݅, ஹನ੷࠶ ؘ੉ఠо ׮ ׮ܲ ҕрী ੷੢ؽ } }

Slide 19

Slide 19 text

/** * @Composable (input) -> output * ੉ ೣࣻ੄ ҃਋ input ਷ ܻࣗझ ই੉٣, output ਷ ܻࣗझ чܳ աఋշפ׮. * * ੉ ೣࣻ੄ @Composable ਷ ؘ੉ఠܳ ઁҕೞח ৉ೡਸ ೤פ׮. */ @Composable fun stringResource(@StringRes id: Int): String { val resources = resources() return resources.getString(id) } /** * @Composable (input) -> Unit * ੉ ೣࣻ੄ ҃਋ input җ output ੉ হणפ׮. * * ੉ ೣࣻ੄ @Composable ਷ ஹನ੷࠶ਸ ߑ୹ೞח ৉ೡਸ ೤פ׮. */ @Composable fun ByeWorld() { Text(text = "Bye, world!") }

Slide 20

Slide 20 text

/** * ஹನૉ੄ ݽٚ ؘ੉ఠܳ ੷੢ೞҊ ҙܻೞח ௿ېझ ੑפ׮. * Gap Buffer ੗ܐҳઑ৬ ےؒ ঘࣁझ۽ ҳഅعҊ, ղࠗ ؘ੉ఠח Array ী ੷੢ؾפ׮. */ internal class SlotTable : CompositionData, Iterable

Slide 21

Slide 21 text

Gap Buffer: زੌೠ ਤ஖ Ӕ୊ী ௿۞झఠ݂ػ ബਯ੸ੋ ࢗੑ ߂ ࢏ઁ ੘সਸ ೲਊೞח ز੸ ߓৌ

Slide 22

Slide 22 text

Gap Buffer: زੌೠ ਤ஖ Ӕ୊ী ௿۞झఠ݂ػ ബਯ੸ੋ ࢗੑ ߂ ࢏ઁ ੘সਸ ೲਊೞח ز੸ ߓৌ 1, ୡӝ Gap ࢤࢿ: O(N) [(v)_, _, _, _, _, _, _, _, _, _] // _ ח Gap, (v) ח അ੤ cursor ܳ աఋն

Slide 23

Slide 23 text

Gap Buffer: زੌೠ ਤ஖ Ӕ୊ী ௿۞झఠ݂ػ ബਯ੸ੋ ࢗੑ ߂ ࢏ઁ ੘সਸ ೲਊೞח ز੸ ߓৌ 1, ୡӝ Gap ࢤࢿ: O(N) [(v)_, _, _, _, _, _, _, _, _, _] // _ ח Gap, (v) ח അ੤ cursor ܳ աఋն 2. 0ߣ૩ ੋؙझী Hi ࢗੑ [(v)H, i, _, _, _, _, _, _, _, _] // ݽف Gap ী ࢗੑغ޲۽ ୶о Gap ࢤࢿ੉ ೙ਃೞ૑ ঋই O(1) ী ৮ܐؽ

Slide 24

Slide 24 text

Gap Buffer: زੌೠ ਤ஖ Ӕ୊ী ௿۞झఠ݂ػ ബਯ੸ੋ ࢗੑ ߂ ࢏ઁ ੘সਸ ೲਊೞח ز੸ ߓৌ 1, ୡӝ Gap ࢤࢿ: O(N) [(v)_, _, _, _, _, _, _, _, _, _] // _ ח Gap, (v) ח അ੤ cursor ܳ աఋն 2. 0ߣ૩ ੋؙझী Hi ࢗੑ [(v)H, i, _, _, _, _, _, _, _, _] // ݽف Gap ী ࢗੑغ޲۽ ୶о Gap ࢤࢿ੉ ೙ਃೞ૑ ঋই O(1) ী ৮ܐؽ 3. 1ߣ૩ ੋؙझ੄ i ઁѢ [H, (v)_, _, _, _, _, _, _, _, _] // cursor ܳ i о ੓ח ਤ஖۽ ৤ӝӝ ਤ೧ O(N) ੉ ѦܻҊ // ੉റ Gap ীࢲ ч ઁѢо ૓೯غ޲۽ O(1) ݅ী ৮ܐؽ (ч ઁѢח ೧׼ чਸ Gap ਵ۽ ߸҃ೞח Ѫਵ۽ ҳഅؾפ׮)

Slide 25

Slide 25 text

Gap Buffer: زੌೠ ਤ஖ Ӕ୊ী ௿۞झఠ݂ػ ബਯ੸ੋ ࢗੑ ߂ ࢏ઁ ੘সਸ ೲਊೞח ز੸ ߓৌ 1, ୡӝ Gap ࢤࢿ: O(N) [(v)_, _, _, _, _, _, _, _, _, _] // _ ח Gap, (v) ח അ੤ cursor ܳ աఋն 2. 0ߣ૩ ੋؙझী Hi ࢗੑ [(v)H, i, _, _, _, _, _, _, _, _] // ݽف Gap ী ࢗੑغ޲۽ ୶о Gap ࢤࢿ੉ ೙ਃೞ૑ ঋই O(1) ী ৮ܐؽ 3. 1ߣ૩ ੋؙझ੄ i ઁѢ [H, (v)_, _, _, _, _, _, _, _, _] // cursor ܳ i о ੓ח ਤ஖۽ ৤ӝӝ ਤ೧ O(N) ੉ ѦܻҊ // ੉റ Gap ীࢲ ч ઁѢо ૓೯غ޲۽ O(1) ݅ী ৮ܐؽ (ч ઁѢח ೧׼ чਸ Gap ਵ۽ ߸҃ೞח Ѫਵ۽ ҳഅؾפ׮) 4. 0ߣ૩ ੋؙझ੄ H ܳ Bye ۽ ߸҃ [(v)B, y, e, _, _, _, _, _, _, _] // cursor ܳ H о ੓ח ਤ஖۽ ৤ӝӝ ਤ೧ O(N) ੉ ѦܻҊ // ੉റ ݽف Gap ীࢲ ч সؘ੉౟о ૓೯غ޲۽ O(1) ݅ী ৮ܐؽ

Slide 26

Slide 26 text

var isLoading = false @Composable fun Main() { Column { // 1ߣ૩ ਤ஖ (അ੤ cursor) Text(text = "Column Data") if (isLoading) { Text(text = "Loading...") } } } Column Text “Column Data” Gap Gap Gap Gap Gap Gap ߣ૩ਤ஖

Slide 27

Slide 27 text

var isLoading = true @Composable fun Main() { Column { // 1ߣ૩ ਤ஖ (੉੹ cursor) Text(text = "Column Data") if (isLoading) { // 2ߣ૩ ਤ஖ (അ੤ cursor) Text(text = "Loading...") } } } Column Text “Column Data” Gap Gap Gap Gap Text “Loading…” ߣ૩ਤ஖ ߣ૩ਤ஖

Slide 28

Slide 28 text

/** * Gap ਸ ޖदೞҊ ౠ੿ ਤ஖੄ ੺؀੸ੋ ੋؙझܳ оܰఃח ېಌੑפ׮. (ےؒ ঘࣁझ ഝࢿച) * * ےؒ ঘࣁझ: ؘ੉ఠܳ ੷੢ೞח ࠶۾ਸ ೠߣী ৈ۞ ѐ ঘࣁझೞח Ѫ੉ ইפۄ ౠ੿ ਤ஖۽ ߄۽ ੽Ӕೞৈ ೠ ߣী ೞա੄ ࠶۾݅ਸ ঘࣁझೞח ߑध */ internal class Anchor(loc: Int) { internal var location = loc val valid get() = location != Int.MIN_VALUE fun toIndexFor(slots: SlotTable) = slots.anchorIndex(this) fun toIndexFor(writer: SlotWriter) = writer.anchorIndex(this) }

Slide 29

Slide 29 text

var isLoading = true @Composable fun Main() { Column { Text(text = "Column Data") if (isLoading) { // Anchor ۽ ߄۽ ੽Ӕ Text(text = "Loading...") } } } Column Text “Column Data” Gap Gap Gap Gap Text “Loading…” "ODIPS۽߄۽੽Ӕ

Slide 30

Slide 30 text

var isColumn = true @Composable fun Main() { if (isColumn) { Column { Text(text = "Column Data") } } else { Row { repeat(10) { index -> Text(text = "Row Data - $index") } } } } Column Text “Column Data” Gap Gap Gap Gap Gap Gap

Slide 31

Slide 31 text

var isColumn = false @Composable fun Main() { if (isColumn) { Column { Text(text = "Column Data") } } else { Row { repeat(10) { index -> Text(text = "Row Data - $index") } } } } Row Text “Row Data - 0” Text “Row Data - 1” Text Text Text “Row Data - 2” “Row Data - 3”

Slide 32

Slide 32 text

var isLoading = false @Composable fun Main() { Column { Text(text = "Column Data") if (isLoading) { Text(text = "Loading...") } } } Column Text “Column Data” Gap Gap Gap Gap Gap Gap Composition

Slide 33

Slide 33 text

var isLoading = true @Composable fun Main() { Column { Text(text = "Column Data") if (isLoading) { Text(text = "Loading...") } } } Column Text “Column Data” Gap Gap Gap Gap Text “Loading…” Recomposition !$PNQPTBCMF੄ݵ١ࢿࠁ੢ ܻஹನ૑࣌ࢤۚ4NBSU3FDPNQPTJUJPO

Slide 34

Slide 34 text

var isLoading = true @Composable fun Main() { Column { Text(text = "Column Data") if (isLoading) { Text(text = "Loading...") } } } Emit Column Text Text

Slide 35

Slide 35 text

var isLoading = true @Composable fun Main() { Column { Text(text = "Column Data") if (isLoading) { Text(text = "Loading...") } } } Materializing

Slide 36

Slide 36 text

var isLoading = true @Composable fun Main() { Column { Text(text = "Column Data") if (isLoading) { Text(text = "Loading...") } } } Column Text “Column Data” Gap Gap Gap Gap Text “Loading…”

Slide 37

Slide 37 text

val isLoading = mutableStateOf(false) // Snapshot System isLoading.value = true @Composable fun Main() { Column { Text(text = "Column Data") if (isLoading) { Text(text = "Loading...") } } } Column Text “Column Data” Gap Gap Gap Gap Text “Loading…”

Slide 38

Slide 38 text

3. How it works? ↟$PNQJMFS ↟3VOUJNF

Slide 39

Slide 39 text

@Composable fun TextWrapper() { Text(text = "SungbinLand") } dependencies { // annotation processor XXX implementation "androidx.compose.foundation:foundation:1.3.0-alpha01" }

Slide 40

Slide 40 text

@Composable fun TextWrapper() { Text(text = "SungbinLand") } buildFeatures { compose true // ஹನૉ ௏ౣܽ ஹ౵ੌ۞ ഝࢿച /** * ஹನૉ ஹ౵ੌ җ੿ਸ ௏ౣܽ ঱য੄ ஹ౵ੌ җ੿ਵ۽ ੐ߓ٘ೞৈ ୊ܻ ࣘبܳ ֫੉Ҋ, য֢ప੉࣌ ೐۽ࣁࢲח ೡ ࣻ হח ֫ਸ ࣻળ੄ ௏٘ ੽Ӕਸ ೞৈ ஹ౵ੌؾפ׮. * ৘ܳ ٜয, ௏ౣܽҗ ஹನૉח ݣ౭೒ۖಬਸ ؀࢚ਵ۽ ೞ޲۽ ஹ౵ੌ җ੿ীࢲ ࢤࢿغח IR ਸ അ੤ ೒ۖಬী ݏח ജ҃ਵ۽ ࣻ੿ೞҊ lowering ਸ ૓೯೤פ׮. */ } // or… gradle.kts ܳ ࢎਊೠ׮ݶ import org.jetbrains.kotlin.gradle.plugin.PLUGIN_CLASSPATH_CONFIGURATION_NAME dependencies { add(PLUGIN_CLASSPATH_CONFIGURATION_NAME, "org.jetbrains.compose.compiler:compiler:$version") }

Slide 41

Slide 41 text

௏ౣܽஹ౵ੌ۞ߡ੹୓௼ ௏٘੿੸࠙ࢳ߂ӝઓ҃Ҋরઁ ஹನૉ۠ఋ੐ߡ੹୓௼ *3ࢤࢿ ௿ېझউ੿ࢿ୶ۿ -JWF-JUFSBMഝࢿച ۈ׮୭੸ച $PNQPTFS઱ੑ ࠺Ү੹౵ഝࢿച %FGBVMU"SHVNFOUT੤ҳഅ ஹನ੷࠶Ӓܛࢤࢿ ,MJC EFDPZࢤࢿ

Slide 42

Slide 42 text

௏ౣܽஹ౵ੌ۞ߡ੹୓௼ ௏٘੿੸࠙ࢳ߂ӝઓ҃Ҋরઁ ஹನૉ۠ఋ੐ߡ੹୓௼ *3ࢤࢿ ௿ېझউ੿ࢿ୶ۿ -JWF-JUFSBMഝࢿച ۈ׮୭੸ച $PNQPTFS઱ੑ ࠺Ү੹౵ഝࢿച %FGBVMU"SHVNFOUT੤ҳഅ ஹನ੷࠶Ӓܛࢤࢿ ,MJC EFDPZࢤࢿ

Slide 43

Slide 43 text

௏٘੿੸࠙ࢳ߂ӝઓ҃Ҋরઁ j j j j

Slide 44

Slide 44 text

௏٘੿੸࠙ࢳ߂ӝઓ҃Ҋরઁ j j j j

Slide 45

Slide 45 text

௏٘੿੸࠙ࢳ߂ӝઓ҃Ҋরઁ j j j j

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

Stability System ௿ېझউ੿ࢿ୶ۿ j j j j

Slide 48

Slide 48 text

/** * উ੿ ࢚కח ௼ѱ 3о૑੄ ઑѤਸ ٮܵפ׮. * * - ч੉ ߸҃عਸ ҃਋ ஹನ੷࠶ীѱ ঌ۰ઉঠ ೤פ׮. (૊, [State] ۽ ੘زظঠ ೣ) * - ੉੹ ੋझఢझ৬ അ੤ ੋझఢझо زੌ೧ঠ ೤פ׮. * - ݽٚ ҕѐ ೙ٜ٘ب ׮ উ੿ ࢚కৈঠ ೤פ׮. * * ੉ 3о૑ ઑѤਸ ٮܲ׮ݶ ஹನૉח ೧׼ ೙٘о উ੿੸ੋ ࢚కۄҊ ౵ঈೞҊ, * ೧׼ ೙٘੄ ч੉ ߄Շ૑ ঋও׮ݶ ࢎਊػ ஹನ੷࠶੄ ܻஹನ૑࣌ਸ ࢤۚ೤פ׮. * * @see Immutable * @see Stable */ @Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) annotation class StableMarker

Slide 49

Slide 49 text

/** * ࢤࢿػ ੉റ۽ ݽٚ ҕѐ੸ੋ ೙٘о ੺؀ ߸ೞ૑ ঋח׮ח Ѫਸ աఋղݴ ௿ېझী ੸ਊؼ ࣻ ੓णפ׮. * * ࢤࢿ ੉റ ч੉ ߸҃غ૑ ঋਵ޲۽ উ੿੄ ୐ ߣ૩ ӏ஗ੋ * "ч੉ ߸҃عਸ ҃਋ ஹನ੷࠶ীѱ ঌ۰ઉঠ ೤פ׮." о ޖदؾפ׮. */ @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) @StableMarker annotation class Immutable fun main() { val list = mutableListOf(1) list.add(2) // ੋझఢझח زੌೞ૑݅ ч੉ ߄Չ ࣻ ੓णפ׮. // @Immutable ਷ ੉۞ೠ ߸҃ب ೲਊೞ૑ ঋणפ׮. }

Slide 50

Slide 50 text

/** * ч੉ ߸҃ؼ ࣻ ੓ח ࢚క੉ݴ, ੸ਊ ؀࢚ী ٮۄ ডрঀ ৉ೡ੉ ׳ۄ૘פ׮. * * ఋੑী ੸ਊػ׮ݶ ୶о ৉ೡ হ੉ [StableMarker] ੄ ৉ೡਸ Ӓ؀۽ оઉцפ׮. * * ೣࣻա ೐۽ಌ౭ী ੸ਊػ׮ݶ [StableMarker] ੄ ৉ೡী ୶оغח ৉ೡ੉ ࢤӤפ׮. * э਷ input ী ੓যࢲח ೦࢚ زੌೠ output ਸ ٜ݅যղݴ(ࣽࣻ ೣࣻ), ೣࣻ੄ ҃਋ ੋ੗ٜ ৉द ݽف উ੿੸ੋ ࢚కۄח Ѫਸ ডࣘ೤פ׮. * ݅ড input ੉ زੌೞ׮ݶ output ژೠ زੌೡ Ѫ੉ӝ ٸޙী ܻஹನ૑࣌ਸ झఈೞѱ ؾפ׮. */ @Target( AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY ) @Retention(AnnotationRetention.BINARY) @StableMarker annotation class Stable

Slide 51

Slide 51 text

// উ੿ࢿ਷ ੹౵ػ׮. @Immutable interface SungbinLand class SungbinLandImpl : SungbinLand // ߈ജೞח SungbinLand о উ੿ ࢚క੉ӝ ٸޙী // ೣࣻী StableMarker о হযب ੉ ೣࣻח উ੿ ࢚క۽ р઱ػ׮. fun provideSungbinLand(): SungbinLand = SungbinLandImpl() // data ੋ੗੄ ఋੑ੉ উ੿ ࢚క੉ӝ ٸޙী data ੋ੗ח উ੿ ࢚కо غҊ, // ੉ ஹನ੷࠶਷ skippable ࢚కо ػ׮. @Composable fun SungbinLandDisplay(data: SungbinLand = provideSungbinLand()) { Text(text = data.toString()) }

Slide 52

Slide 52 text

// উ੿ࢿ਷ ੹౵ػ׮. interface SungbinLand @Immutable class SungbinLandImpl : SungbinLand // ߈ജೞח SungbinLand ী ؀ೠ উ੿ࢿ ੿ࠁо হӝ ٸޙী ੉ ೣࣻח ࠛউ੿ ࢚కо ػ׮. fun provideSungbinLand(): SungbinLand = SungbinLandImpl() // data ੋ੗੄ ఋੑ਷ ࠛউ੿ ࢚క੉૑݅, provideSungbinLand() ೣࣻীࢲ чਸ ઁҕೞחؘ ࢎਊೞח // SungbinLandImpl ௿ېझח উ੿ ࢚క੉ӝ ٸޙী data ੋ੗੄ ӝࠄ ч੉ উ੿ ࢚కۄ // ੉ ஹನ੷࠶਷ skippable ࢚కо ػ׮. (ױ, ৈ੹൤ data ੋ੗ח ࠛউ੿ ࢚క۽ թח׮) @Composable fun SungbinLandDisplay(data: SungbinLand = provideSungbinLand()) { Text(text = data.toString()) }

Slide 53

Slide 53 text

@Composable fun ListDisplay(texts: List) { Text(text = texts.joinToString()) }

Slide 54

Slide 54 text

public interface MutableList : List, MutableCollection @Composable fun ListDisplay(texts: List = mutableListOf()) { Text(text = texts.joinToString()) }

Slide 55

Slide 55 text

@Immutable class ImmutableListWrapper(val value: List) @Composable fun ListDisplay(texts: ImmutableListWrapper) { Text(text = texts.value.joinToString()) }

Slide 56

Slide 56 text

// https://github.com/Kotlin/kotlinx.collections.immutable @Composable fun ListDisplay(texts: ImmutableList) { Text(text = texts.joinToString()) }

Slide 57

Slide 57 text

/** * ஹ౵ੌ द੼ী ݽٚ ௿ېझٜ੄ উ੿ࢿਸ ੗زਵ۽ ୶ۿ೤פ׮. * * @param parameters উ੿ࢿਸ ୶ۿೞחؘ ب਑੉ ؼ ࣻ ੓ѱ ੋ੗ٜ੄ ࠺౟݃झ௼ܳ աఋշפ׮. */ @ComposeCompilerApi @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) annotation class StabilityInferred(val parameters: Int) // ਬੌೠ ೙٘ੋ value о উ੿ ࢚క੉Ҋ ࠛ߸ೞӝ ٸޙী Name ௿ېझח উ੿ ࢚క۽ ౸ױػ׮. class Name(val value: String) // ਬੌೠ ೙٘ੋ value ੄ ఋੑ੉ ઁ֎ܼ੉ۄ ఋੑਸ ౸ױೡ ࣻ হӝী ࢎ੹ী ࠺౟݃झఊػ чਸ ӝળਵ۽ উ੿ ࢚కܳ ୶ۿೠ׮. // ೞ૑݅ ೙٘о о߸ ࢚క੉ӝ ٸޙী T ఋੑ੉ উ੿੉ৈب NameWithGeneric ௿ېझח ࠛউ੿ ࢚క۽ ౸ױػ׮. class NameWithGeneric(var value: T) ௿ېझউ੿ࢿ୶ۿ j j j j

Slide 58

Slide 58 text

LiveLiteral -JWF-JUFSBMഝࢿച j j j j

Slide 59

Slide 59 text

-JWF-JUFSBM Stability

Slide 60

Slide 60 text

fun earth() { print("Bye World") }

Slide 61

Slide 61 text

fun earth() { print(LiveLiterals$EarthKt.`getString$arg-0$call-print$fun-earth`()) } object LiveLiterals$EarthKt { var `String$arg-0$call-print$fun-earth` = "Bye World" var `State$String$arg-0$call-print$fun-earth`: MutableState? = null fun `getString$arg-0$call-print$fun-earth`(): String { val field = this.`String$arg-0$call-print$fun-earth` val state = if (field == null) { val tmp = liveLiteral( "String$arg-0$call-print$fun-earth", this.`String$arg-0$call-print$fun-earth` ) this.`String$arg-0$call-print$fun-earth` = tmp tmp } else field return field.value } }

Slide 62

Slide 62 text

fun earth() { print(LiveLiterals$EarthKt.`getString$arg-0$call-print$fun-earth`()) } object LiveLiterals$EarthKt { var `String$arg-0$call-print$fun-earth` = "Bye World" var `State$String$arg-0$call-print$fun-earth`: MutableState? = null fun `getString$arg-0$call-print$fun-earth`(): String { val field = this.`String$arg-0$call-print$fun-earth` val state = if (field == null) { val tmp = liveLiteral( "String$arg-0$call-print$fun-earth", this.`String$arg-0$call-print$fun-earth` ) this.`String$arg-0$call-print$fun-earth` = tmp tmp } else field return field.value } }

Slide 63

Slide 63 text

fun earth() { print(LiveLiterals$EarthKt.`getString$arg-0$call-print$fun-earth`()) } object LiveLiterals$EarthKt { var `String$arg-0$call-print$fun-earth` = "Bye World" var `State$String$arg-0$call-print$fun-earth`: MutableState? = null fun `getString$arg-0$call-print$fun-earth`(): String { val field = this.`String$arg-0$call-print$fun-earth` val state = if (field == null) { val tmp = liveLiteral( "String$arg-0$call-print$fun-earth", this.`String$arg-0$call-print$fun-earth` ) this.`String$arg-0$call-print$fun-earth` = tmp tmp } else field return field.value } }

Slide 64

Slide 64 text

fun earth() { print(LiveLiterals$EarthKt.`getString$arg-0$call-print$fun-earth`()) } object LiveLiterals$EarthKt { var `String$arg-0$call-print$fun-earth` = "Bye World" var `State$String$arg-0$call-print$fun-earth`: MutableState? = null fun `getString$arg-0$call-print$fun-earth`(): String { val field = this.`String$arg-0$call-print$fun-earth` val state = if (field == null) { val tmp = liveLiteral( "String$arg-0$call-print$fun-earth", this.`String$arg-0$call-print$fun-earth` ) this.`String$arg-0$call-print$fun-earth` = tmp tmp } else field return field.value } }

Slide 65

Slide 65 text

fun earth() { print(LiveLiterals$EarthKt.`getString$arg-0$call-print$fun-earth`()) } object LiveLiterals$EarthKt { var `String$arg-0$call-print$fun-earth` = "Bye World" var `State$String$arg-0$call-print$fun-earth`: MutableState? = null fun `getString$arg-0$call-print$fun-earth`(): String { val field = this.`String$arg-0$call-print$fun-earth` val state = if (field == null) { val tmp = liveLiteral( "String$arg-0$call-print$fun-earth", this.`String$arg-0$call-print$fun-earth` ) this.`String$arg-0$call-print$fun-earth` = tmp tmp } else field return field.value } }

Slide 66

Slide 66 text

fun earth() { print(LiveLiterals$EarthKt.`getString$arg-0$call-print$fun-earth`()) } object LiveLiterals$EarthKt { var `String$arg-0$call-print$fun-earth` = "Bye World" var `State$String$arg-0$call-print$fun-earth`: MutableState? = null fun `getString$arg-0$call-print$fun-earth`(): String { val field = this.`String$arg-0$call-print$fun-earth` val state = if (field == null) { val tmp = liveLiteral( "String$arg-0$call-print$fun-earth", this.`String$arg-0$call-print$fun-earth` ) this.`String$arg-0$call-print$fun-earth` = tmp tmp } else field return field.value } }

Slide 67

Slide 67 text

fun earth() { print(LiveLiterals$EarthKt.`getString$arg-0$call-print$fun-earth`()) } object LiveLiterals$EarthKt { var `String$arg-0$call-print$fun-earth` = "Bye World" var `State$String$arg-0$call-print$fun-earth`: MutableState? = null fun `getString$arg-0$call-print$fun-earth`(): String { val field = this.`String$arg-0$call-print$fun-earth` val state = if (field == null) { val tmp = liveLiteral( "String$arg-0$call-print$fun-earth", this.`String$arg-0$call-print$fun-earth` ) this.`String$arg-0$call-print$fun-earth` = tmp tmp } else field return field.value } }

Slide 68

Slide 68 text

private val liveLiteralCache = HashMap>() @InternalComposeApi @ComposeCompilerApi fun liveLiteral(key: String, value: T): State { return liveLiteralCache.getOrPut(key) { mutableStateOf(value) } as State }

Slide 69

Slide 69 text

@InternalComposeApi fun updateLiveLiteralValue(key: String, value: Any?) { var needToUpdate = true val stateObj = liveLiteralCache.getOrPut(key) { needToUpdate = false mutableStateOf(value) } if (needToUpdate) { stateObj.value = value } }

Slide 70

Slide 70 text

private fun findEffectiveRecomposeScope(group: Int): RecomposeScopeImpl? { var current = group while (current > 0) { for (data in DataIterator(this, current)) { if (data is RecomposeScopeImpl) { return data } } current = groups.parentAnchor(current) } return null }

Slide 71

Slide 71 text

// @Composable XXX fun earth() { print(LiveLiterals$EarthKt.`getString$arg-0$call-print$fun-earth`()) } object LiveLiterals$EarthKt { var `String$arg-0$call-print$fun-earth` = "Bye World" var `State$String$arg-0$call-print$fun-earth`: MutableState? = null fun `getString$arg-0$call-print$fun-earth`(): String { val field = this.`String$arg-0$call-print$fun-earth` val state = if (field == null) { val tmp = liveLiteral( "String$arg-0$call-print$fun-earth", this.`String$arg-0$call-print$fun-earth` ) this.`String$arg-0$call-print$fun-earth` = tmp tmp } else field return field.value } }

Slide 72

Slide 72 text

/** * LiveLiteral ੉ ெઉ ੓ח ҃਋ীب ੸ਊغח ߧਤ ղীࢲ LiveLiteral ਸ ࢤࢿೞ૑ ঋب۾ ஹನૉ ஹ౵ੌ۞ী ಴दೞח ؘ ࢎਊؾפ׮. */ @Target( AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.FILE ) @Retention(AnnotationRetention.SOURCE) annotation class NoLiveLiterals // @file:NoLiveLiterals @NoLiveLiterals fun earth() { print("Bye World") }

Slide 73

Slide 73 text

$PNQPTFS઱ੑ j j j j // ஹ౵ੌ ੹ @Composable fun TextWrapper() { Text(text = "SungbinLand") } // ஹ౵ੌ റ, Composer: ஹನૉ ۠ఋ੐੉ ஹನ੷࠶җ ࢚ഐ ੘ਊ ೡ ࣻ ੓ѱ ب৬઱ח ੋఠಕ੉झ @Composable fun TextWrapper(composer: Composer, changed: Int) { composer.startRestartGroup(-137853230) composer.sourceInformation("C(TextWrapper)22@513L26:MainActivity.kt#dmu2em") if (changed == 0 && composer.skipping) { composer.skipToGroupEnd() } else { Text(text = "SungbinLand") } composer.endRestartGroup()?.updateScope { composer, _ -> TextWrapper(composer, changed or 1) } }

Slide 74

Slide 74 text

@Composable fun TextWrapper(composer: Composer, changed: Int) { composer.startRestartGroup(-137853230) composer.sourceInformation("C(TextWrapper)22@513L26:MainActivity.kt#dmu2em") if (changed == 0 && composer.skipping) { composer.skipToGroupEnd() } else { Text(text = "SungbinLand") } composer.endRestartGroup()?.updateScope { composer, _ -> TextWrapper(composer, changed or 1) } } Group ஹನ੷࠶Ӓܛࢤࢿ j j j j

Slide 75

Slide 75 text

@Composable fun Main() { Column { Text(text = "Column Data") } } Column Text “Column Data” Gap Gap Gap Gap Gap Gap

Slide 76

Slide 76 text

@Composable fun Main() { Column { Text(text = "Column Data") } } “Column Data” Gap Gap Gap Gap Gap Gap Group Group

Slide 77

Slide 77 text

// RestartableGroup: ܻஹನ૑࣌੉ ੌযզ ࣻ ੓ח ஹನ੷࠶ ઱ਤ۽ ࢤࢿػ׮. // ߹ب੄ ২࣌੉ হ׮ݶ ݽٚ ஹನ੷࠶ী ؀೧ ࢤࢿغח Ӓܛ੉Ҋ, // ܻஹನ૑࣌ ೞח ߑߨਸ оܰ஘׮. // ݽٚ RestartableGroup ਷ ೧׼ ߧਤ ݅ఀ // ੗୓੄ ܻஹನ૑࣌ झ௏೐(RecomposeScope)ܳ ഋࢿೠ׮. @Composable fun RestartableContainer() { Text(text = "SungbinLand") } RestartableGroup “SungbinLand” Gap Gap Gap Gap Gap Gap RestartableGroup 3FTUBSUBCMF$POUBJOFS 3FDPNQPTF4DPQF 5FYU 3FDPNQPTF4DPQF

Slide 78

Slide 78 text

// ReplaceableGroup: ࠙ӝী ٮۄ ੤ߓ஖ ؼ ࣻ ੓ח ஹನ੷࠶ ઱ਤ۽ ࢤࢿػ׮. // Ӓܛਸ Ү୓೧ঠ ೡ ٸ ੘ࢿػ ؘ੉ఠܳ ੿ܻೞח ߑߨਸ оܰ஘׮. @Composable fun ReplaceableContainer(isColumn: Boolean = true) { when (isColumn) { true -> Text(text = "Column") else -> Text(text = "Row") } } RestartableGroup “Column” Gap Gap Gap Gap Gap ReplaceableGroup 3FQMBDFBCMF$POUBJOFS XIFO RestartableGroup 5FYU

Slide 79

Slide 79 text

/** * ஹನ੷࠶੉ ߓ஖ػ ਤ஖о ׳ۄ૑ݶ ਤ஖ ݫݽ੉ઁ੉࣌੄ key о ׳ۄઉࢲ ؘ੉ఠо ୡӝചؾפ׮. * ؘ੉ఠܳ ਬ૑ೠ ࢚క۽ ਤ஖ܳ ৤ӝӝ ਤ೧ࢶ key ஹನ੷࠶ਸ ੉ਊ೧ * ਤ஖ ݫݽ੉ઁ੉࣌ীࢲ ଵҊೡ key ܳ ૒੽ ੿੄೧ঠ ೤פ׮. * * key ஹನ੷࠶ਸ ੉ਊ೧ ߓ஖ػ ஹನ੷࠶਷ ઱ਤ۽ MovableGroup ੉ ࢤࢿؾפ׮. * MovableGroup ਷ ஹನ੷࠶੉ ਤ஖ ݫݽ੉ઁ੉࣌ key ৬ ؘ੉ఠܳ ೦࢚ ࠁઓೞݴ * ੉زೞח ߑߨਸ оܰ஝פ׮. */ @Composable inline fun key( vararg keys: Any?, block: @Composable () -> T ) @Composable fun MovableContainer() { key(Any()) { Text(text = "Key") } } RestartableGroup “Key” Gap Gap Gap Gap Gap MovableGroup .PWBCMF$POUBJOFS LFZ RestartableGroup 5FYU

Slide 80

Slide 80 text

↟3FTUBSUBCMF(SPVQ ↟3FQMBDFBCMF(SPVQ ↟.PWBCMF(SPVQ ஹನ੷࠶Ӓܛࢤࢿ j j j j

Slide 81

Slide 81 text

3. How it works? ↟$PNQJMFS ↟3VOUJNF

Slide 82

Slide 82 text

🎨 🖼️ 🖌️ 🧰 Material ݠఠܻ঴٣੗ੋदझమ 5FYU #VUUPO #PUUPN4IFFU $IJQ "MFSU%JBMPH *DPOT 4VSGBDF Foundation о੢ӝୡ੸ੋ٣੗ੋ -B[Z-JTU $PMVNO $BOWBT 4IBQF (FTUVSFT 0WFSTDSPMM&GGFDU UI ஹನૉ6*5PPMLJU੄ӝࠄࢸ҅ -BZPVU %JBMPH $PNQPTF7JFX"EBQUFS 1SFWJFX"DUJWJUJZ Runtime ஹನૉউ٘۽੉٘োѾ *OWBMJEBUJPO 4OBQTIPU4ZTUFN 4MPU5BCMF -JWF-JUFSBM

Slide 83

Slide 83 text

Runtime Process (full)

Slide 84

Slide 84 text

Runtime Process (simply)

Slide 85

Slide 85 text

class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Text(text = "SungbinLand") } } }

Slide 86

Slide 86 text

// ஹನૉ ۠ఋ੐җ UI ੄ োѾ fun ComponentActivity.setContent( parent: CompositionContext? = null, content: @Composable () -> Unit ) { ComposeView(this).apply { setParentCompositionContext(parent) setContent(content) setOwners() setContentView(this, DefaultActivityContentLayoutParams) } }

Slide 87

Slide 87 text

// ComposeView#setContent ۽ ੉ ೣࣻо प೯ؽ private fun ensureCompositionCreated() { setContent(/* Recomposer о ٜ݅য૑Ҋ ઱ੑؾפ׮ */) { content() } }

Slide 88

Slide 88 text

/** * ܻஹನ૑࣌ਸ ࣻ೯ೞҊ ೞա ੉࢚੄ ஹನ੷࠶ী সؘ੉౟ܳ ੸ਊೞӝ ਤೠ झா઴۞ ੑפ׮. */ class Recomposer( effectCoroutineContext: CoroutineContext ) : CompositionContext() /** * Handler ௒ߔ ژח Choreographer ੄ গפݫ੉࣌ ೐ۨ੐ ױ҅ ઺ * ݢ੷ بېೞח ױ҅ীࢲ ٣झಁ஖ܳ ࣻ೯ೞח CoroutineDispatcher ੑפ׮. */ class AndroidUiDispatcher private constructor( val choreographer: Choreographer, private val handler: Handler ) : CoroutineDispatcher()

Slide 89

Slide 89 text

/** * ܻஹನ૑࣌ਸ ࣻ೯ೞҊ ೞա ੉࢚੄ ஹನ੷࠶ী সؘ੉౟ܳ ੸ਊೞӝ ਤೠ झா઴۞ ੑפ׮. */ class Recomposer( effectCoroutineContext: CoroutineContext ) : CompositionContext() /** * Handler ௒ߔ ژח Choreographer ੄ গפݫ੉࣌ ೐ۨ੐ ױ҅ ઺ * ݢ੷ بېೞח ױ҅ীࢲ ٣झಁ஖ܳ ࣻ೯ೞח CoroutineDispatcher ੑפ׮. */ class AndroidUiDispatcher private constructor( val choreographer: Choreographer, private val handler: Handler ) : CoroutineDispatcher()

Slide 90

Slide 90 text

suspend fun runRecomposeAndApplyChanges() = recompositionRunner { parentFrameClock -> val toRecompose = mutableListOf() while (shouldKeepRecomposing) { awaitWorkAvailable() parentFrameClock.withFrameNanos { frameTime -> try { toRecompose.fastForEach { composition -> performRecompose(composition) } } finally { toRecompose.clear() } } } }

Slide 91

Slide 91 text

private suspend fun recompositionRunner(block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) -> Unit) { // Recomposer ীࢲ ߉਷ Choreographer ੄ ېಌੋ MonotonicFrameClock ܳ оઉৡ׮. val parentFrameClock: MonotonicFrameClock = coroutineContext.monotonicFrameClock withContext(broadcastFrameClock) { // broadcastFrameClock: ஹನૉ ۠ఋ੐ীࢲ ҟ৉ਵ۽ ࢎਊغח MonotonicFrameClock ੄ ېಌ Snapshot.registerApplyObserver { _, _ -> // Snapshot ੄ write ੉߮౟ܳ ߉ח׮. deriveStateLocked()?.resume(Unit) } coroutineScope { block(parentFrameClock) } } }

Slide 92

Slide 92 text

private suspend fun recompositionRunner(block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) -> Unit) { // Recomposer ীࢲ ߉਷ Choreographer ੄ ېಌੋ MonotonicFrameClock ܳ оઉৡ׮. val parentFrameClock: MonotonicFrameClock = coroutineContext.monotonicFrameClock withContext(broadcastFrameClock) { // broadcastFrameClock: ஹನૉ ۠ఋ੐ীࢲ ҟ৉ਵ۽ ࢎਊغח MonotonicFrameClock ੄ ېಌ Snapshot.registerApplyObserver { _, _ -> // Snapshot ੄ write ੉߮౟ܳ ߉ח׮. deriveStateLocked()?.resume(Unit) } coroutineScope { block(parentFrameClock) } } }

Slide 93

Slide 93 text

suspend fun runRecomposeAndApplyChanges() = recompositionRunner { parentFrameClock -> val toRecompose = mutableListOf() // ܻஹನ૑࣌੉ ೙ਃೠ RecomposeScope о ׸ӟ׮. (׸ח җ੿ ࢤۚ) while (shouldKeepRecomposing) { // shouldKeepRecomposing: Recomposer о ܻஹನ૑࣌ ਃ୒ਸ х૑೧ঠ ೞח૑ܳ աఋմ׮. awaitWorkAvailable() // ࢜۽਍ ܻஹನ૑࣌ ਃ୒੉ ੓ਸ ٸ ө૑ suspend ࢚క۽ ӝ׮ܽ׮. parentFrameClock.withFrameNanos { frameTime -> // Choreographer#postFrameCallback ਸ ഐ୹ೠ׮. try { toRecompose.fastForEach { composition -> // RecomposeScope ܳ ࣽഥೞݴ performRecompose(composition) // ܻஹನ૑࣌ ૓೯! } } finally { toRecompose.clear() } } } }

Slide 94

Slide 94 text

private suspend fun awaitWorkAvailable() { suspendCancellableCoroutine { co -> if (hasSchedulingWork) { // ؀ӝ઺ੋ ܻஹನ૑࣌ ਃ୒੉ ੓׮ݶ ߄۽ resume ೞҊ co.resume(Unit) } else { // ؀ӝ઺ੋ ܻஹನ૑࣌ ਃ୒੉ হ׮ݶ workContinuation ਵ۽ CancellableContinuation ੷੢ റ suspend ਬ૑ workContinuation = co } } }

Slide 95

Slide 95 text

private fun deriveStateLocked(): CancellableContinuation? { // ࠁܨ ઺ੋ ੘স: ܻஹನ૑࣌ ਃ୒ ١١ ஹನૉ ۠ఋ੐ ղࠗীࢲ ૓೯غח ৈ۞ ੘স ਃ୒ val newState = when { /* അ੤ ࠁܨ ઺ੋ ੘স ৈࠗী ٮۄ ࢜۽਍ ࢚క ҅࢑ */ } return if (newState == State.PendingWork) { // ࠁܨ ઺ੋ ੘স੉ ੓׮ݶ workContinuation.also { // workContinuation ߈ജ ߂ workContinuation ୡӝച workContinuation = null } } else null }

Slide 96

Slide 96 text

private suspend fun recompositionRunner(block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) -> Unit) { val parentFrameClock = coroutineContext.monotonicFrameClock withContext(broadcastFrameClock) { Snapshot.registerApplyObserver { _, _ -> deriveStateLocked()?.resume(Unit) } coroutineScope { block(parentFrameClock) } } }

Slide 97

Slide 97 text

suspend fun runRecomposeAndApplyChanges() = recompositionRunner { parentFrameClock -> val toRecompose = mutableListOf() while (shouldKeepRecomposing) { awaitWorkAvailable() // ܻஹನ૑࣌ ਃ୒੉ ੓ਸ ٸ ө૑ suspend parentFrameClock.withFrameNanos { frameTime -> // ׮਺ ೐ۨ੐ীࢲ try { toRecompose.fastForEach { composition -> performRecompose(composition) } // Recomposition! } finally { toRecompose.clear() } } } } private suspend fun recompositionRunner(block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) -> Unit) { val parentFrameClock = coroutineContext.monotonicFrameClock withContext(broadcastFrameClock) { Snapshot.registerApplyObserver { _, _ -> deriveStateLocked()?.resume(Unit) } // Snapshot ч ߸҃੉ ੓ਸ ٸ resume coroutineScope { block(parentFrameClock) } } }

Slide 98

Slide 98 text

// ComposeView#setContent ۽ ੉ ೣࣻо प೯ؽ private fun ensureCompositionCreated() { setContent(/* Recomposer о ٜ݅য૑Ҋ ઱ੑؾפ׮ */) { content() } }

Slide 99

Slide 99 text

// this: Composer private fun doCompose(content: @Composable () -> Unit) { startRoot() // ठܶ ప੉࠶ ୡӝച startGroup() // ஹನ੷࠶੉ ٜযт Ӓܛ ࢤࢿ invokeComposable(this, content) endGroup() endRoot() } // performRecompose ب ѾҴ ੉ ೣࣻܳ प೯ೠ׮. internal actual fun invokeComposable(composer: Composer, composable: @Composable () -> Unit) { val realFn = composable as Function2 realFn(composer, 1) } Root Group Composable Gap Gap Gap Gap Gap Gap

Slide 100

Slide 100 text

4. Compiler Debugging ↟CVJMENFUSJDT ↟CVJMESFQPSUT

Slide 101

Slide 101 text

CVJMENFUSJDT // ஹ౵ੌ۞о х૑ೠ ஹನ੷࠶ ೣٜࣻ੄ ੿ࠁܳ աఋմ׮. kotlinOptions { freeCompilerArgs += [ "-P", "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + "${rootProject.file(".").absolutePath}/report/compose-metrics" ] }

Slide 102

Slide 102 text

<ݽٕݺ>@<࠽٘ఋੑ>NPEVMFKTPO { { "skippableComposables": 48, // skippable ࢚కੋ ஹನ੷࠶ ѐࣻ "restartableComposables": 67, "readonlyComposables": 0, "totalComposables": 67, // ੹୓ ஹನ੷࠶ ѐࣻ "restartGroups": 67, "totalGroups": 72, "staticArguments": 130, "certainArguments": 28, "knownStableArguments": 793, // উ੿ ࢚క۽ ঌ۰૓ ੋ੗ ѐࣻ "knownUnstableArguments": 30, // ࠛউ੿ ࢚క۽ ঌ۰૓ ੋ੗ ѐࣻ "unknownStableArguments": 8, "totalArguments": 831, "markedStableClasses": 0, "inferredStableClasses": 16, // উ੿ ࢚క۽ ୶ۿػ ௿ېझ ѐࣻ "inferredUnstableClasses": 2, // ࠛউ੿ ࢚క۽ ୶ۿػ ௿ېझ ѐࣻ "inferredUncertainClasses": 0, "effectivelyStableClasses": 16, "totalClasses": 18, "memoizedLambdas": 54, "singletonLambdas": 5, "singletonComposableLambdas": 8, "composableLambdas": 31, "totalLambdas": 89 }

Slide 103

Slide 103 text

CVJMESFQPSUT // ஹ౵ੌ۞о х૑ೠ ஹನ੷࠶ ೣٜࣻ੉ ࢎਊ઺ੋ ௿ېझ৬ ੋ੗ী ؀ೠ উ੿ࢿ ੿ࠁܳ աఋմ׮. kotlinOptions { freeCompilerArgs += [ "-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + "${rootProject.file(".").absolutePath}/report/compose-reports" ] }

Slide 104

Slide 104 text

<ݽٕݺ>@<࠽٘ఋੑ>DMBTTFTUYU /* ——— classes ——— */ sealed class UiState { object Loading : UiState() object Done : UiState() data class Exception(val throwable: Throwable) : UiState() } /* ——— reports ——— */ stable class Loading { = Stable } stable class Done { = Stable } unstable class Exception { unstable val throwable: Throwable = Unstable }

Slide 105

Slide 105 text

<ݽٕݺ>@<࠽٘ఋੑ>DPNQPTBCMFTDTW /* ——— composables ——— */ @Composable fun DisplayText(text: String = "Hi") { Text(text = text) } @Composable fun DisplayTexts(texts: List) { Text(text = texts.joinToString()) } /* ——— reports ——— */

Slide 106

Slide 106 text

<ݽٕݺ>@<࠽٘ఋੑ>DPNQPTBCMFTUYU /* ——— composables ——— */ @Composable fun DisplayText(text: String = "Hi") { Text(text = text) } @Composable fun DisplayTexts(texts: List) { Text(text = texts.joinToString()) } /* ——— reports ——— */ restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun DisplayText( stable text: String? = @dynamic LiveLiterals$MainActivityKt.String$param-text$fun-DisplayText() ) restartable /* skippable ੉ হ਺ */ scheme("[androidx.compose.ui.UiComposable]") fun DisplayTexts( unstable texts: List )

Slide 107

Slide 107 text

<ݽٕݺ>@<࠽٘ఋੑ>DPNQPTBCMFTUYU /* ——— composables ——— */ @Composable fun DisplayText(text: String = "Hi") { Text(text = text) } @Composable fun DisplayTexts(texts: List) { Text(text = texts.joinToString()) } /* ——— reports ——— */ restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun DisplayText( stable text: String? = @dynamic LiveLiterals$MainActivityKt.String$param-text$fun-DisplayText() ) restartable scheme("[androidx.compose.ui.UiComposable]") fun DisplayTexts( unstable texts: List )

Slide 108

Slide 108 text

<ݽٕݺ>@<࠽٘ఋੑ>DPNQPTBCMFTUYU /* ——— composables ——— */ @NoLiveLiterals // new @Composable fun DisplayText(text: String = "Hi") { Text(text = text) } @Composable fun DisplayTexts(texts: List) { Text(text = texts.joinToString()) } /* ——— reports ——— */ restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun DisplayText( stable text: String? = @static "Hi" ) restartable scheme("[androidx.compose.ui.UiComposable]") fun DisplayTexts( unstable texts: List )

Slide 109

Slide 109 text

5. Best Practice ↟ܻஹನ૑࣌ߧਤ઴੉ӝ ↟ܻஹನ૑࣌হগӝ ↟ࠛ೙ਃೠ࢚క୶੸ઁѢ ↟"05ஹ౵ੌഝࢿച

Slide 110

Slide 110 text

EPOVUIPMFTLJQQJOH setContent { var number by remember { mutableStateOf(0) } println("setContent recomposition") Text( modifier = Modifier.clickable { number++ }, text = number.toString() ).also { println("Text recomposition") } } /* ܻஹನ૑࣌੉ য٣ী ૓೯ؼөਃ? 1, setContent content ৔৉ 2. Text 3. setContent content ৔৉ + Text */

Slide 111

Slide 111 text

EPOVUIPMFTLJQQJOH setContent { var number by remember { mutableStateOf(0) } println("setContent recomposition") Text( modifier = Modifier.clickable { number++ }, text = number.toString() ).also { println("Text recomposition") } } /* [first-composition] setContent recomposition Text recomposition [re-composition] setContent recomposition Text recomposition ੉റ زੌ ۽Ӓ ߈ࠂ */

Slide 112

Slide 112 text

EPOVUIPMFTLJQQJOH setContent { var number by remember { mutableStateOf(0) } println("setContent recomposition") Button(onClick = { number++ }) { Text( text = number.toString() ).also { println("Text recomposition") } }.also { println("Button recomposition") } } /* [first-composition] setContent recomposition Text recomposition Button recomposition [re-composition] Text recomposition ੉റ زੌ ۽Ӓ ߈ࠂ */

Slide 113

Slide 113 text

EPOVUIPMFTLJQQJOH // ஹ౵ੌ ੹ @Composable fun TextWrapper(text: String) { Text(text = text) } // ஹ౵ੌ റ (೨ब ࠗ࠙݅ ಴द) @Composable fun TextWrapper(composer: Composer, text: String) { composer.startRestartGroup(-199242123) // ղࠗীࢲ addRecomposeScope() ۽ ܻஹನ૑࣌ झ௏೐ܳ ୶оೞҊ ੓਺ Text(text = text) composer.endRestartGroup() }

Slide 114

Slide 114 text

EPOVUIPMFTLJQQJOH setContent { var number by remember { mutableStateOf(0) } println("setContent recomposition") Text( modifier = Modifier.clickable { number++ }, text = number.toString() ).also { println("Text recomposition") } } setContent { var number by remember { mutableStateOf(0) } println("setContent recomposition") Button(onClick = { number++ }) { Text( text = number.toString() ).also { println("Text recomposition") } }.also { println("Button recomposition") } } Text RecomposeScope 
 Text RecomposeScope 


Slide 115

Slide 115 text

EPOVUIPMFTLJQQJOH setContent { var number by remember { mutableStateOf(0) } println("setContent recomposition") Button(onClick = { number++ }) { Text( text = number.toString() ).also { println("Text recomposition") } }.also { println("Button recomposition") } } Text RecomposeScope 
 Button RecomposeScope

Slide 116

Slide 116 text

4UBCJMJUZ4ZTUFN setContent { var number by remember { mutableStateOf(1) } TextWrapper( modifier = Modifier.clickable { number++ }, text = number.toString() ) } @Composable fun TextWrapper( modifier: Modifier = Modifier, text: String ) { Text(modifier = modifier, text = text) }

Slide 117

Slide 117 text

4UBCJMJUZ4ZTUFN setContent { var number by remember { mutableStateOf(1) } TextWithLambda( modifier = Modifier.clickable { number++ }, text = { number.toString() } ) } @Composable fun TextWithLambda( modifier: Modifier = Modifier, text: () -> String // Function0 ) { Text(modifier = modifier, text = text()) }

Slide 118

Slide 118 text

5. Best Practice ↟ܻஹನ૑࣌ߧਤ઴੉ӝ ↟ܻஹನ૑࣌হগӝ ↟ࠛ೙ਃೠ࢚క୶੸ઁѢ ↟"05ஹ౵ੌഝࢿച

Slide 119

Slide 119 text

NPWBCMF$POUFOU0G val content = remember<@Composable () -> Unit> { { repeat(2) { Box(modifier = Modifier.size(100.dp).background(color = Color.Green)) } } } Column { Button(onClick = { isRow = !isRow }) { Text(text = "Switch") } if (isRow) { Row { content() } } else { Column { content() } } }

Slide 120

Slide 120 text

NPWBCMF$POUFOU0G val content = remember { movableContentOf { // movableContentWithReceiverOf repeat(2) { Box(modifier = Modifier.size(100.dp).background(color = Color.Green)) } } } Column { Button(onClick = { isRow = !isRow }) { Text(text = "Switch") } if (isRow) { Row { content() } } else { Column { content() } } }

Slide 121

Slide 121 text

!3FBE0OMZ$PNQPTBCMF object MaterialTheme { val colors: Colors @Composable @ReadOnlyComposable get() = LocalColors.current val typography: Typography @Composable @ReadOnlyComposable get() = LocalTypography.current // … } @Composable @ReadOnlyComposable fun stringResource(@StringRes id: Int): String { val resources = resources() return resources.getString(id) }

Slide 122

Slide 122 text

!/PO3FTUBSUBCMF$PNQPTBCMF @Composable @NonRestartableComposable fun DisposableEffect(key1: Any?, effect: DisposableEffectScope.() -> DisposableEffectResult) { remember(key = key1) { DisposableEffectImpl(effect) } } @Composable @NonRestartableComposable fun Image( modifier: Modifier = Modifier, colorFilter: ColorFilter? = null, imageVector: ImageVector, contentDescription: String?, alpha: Float = DefaultAlpha, alignment: Alignment = Alignment.Center, contentScale: ContentScale = ContentScale.Fit ) { Image( modifier = modifier, alignment = alignment, contentScale = contentScale, alpha = alpha, colorFilter = colorFilter, painter = rememberVectorPainter(imageVector), contentDescription = contentDescription ) }

Slide 123

Slide 123 text

5. Best Practice ↟ܻஹನ૑࣌ߧਤ઴੉ӝ ↟ܻஹನ૑࣌হগӝ ↟ࠛ೙ਃೠ࢚క୶੸ઁѢ ↟"05ஹ౵ੌഝࢿച

Slide 124

Slide 124 text

TUBUJD$PNQPTJUJPO-PDBM0G val LocalContext = staticCompositionLocalOf { noLocalProvidedFor("LocalContext") } val LocalClipboardManager = staticCompositionLocalOf { noLocalProvidedFor("LocalClipboardManager") } val LocalDensity = staticCompositionLocalOf { noLocalProvidedFor("LocalDensity") } val LocalFocusManager = staticCompositionLocalOf { noLocalProvidedFor("LocalFocusManager") }

Slide 125

Slide 125 text

!/P-JWF-JUFSBMT @file:NoLiveLiterals @NoLiveLiterals

Slide 126

Slide 126 text

5. Best Practice ↟ܻஹನ૑࣌ߧਤ઴੉ӝ ↟ܻஹನ૑࣌হগӝ ↟ࠛ೙ਃೠ࢚క୶੸ઁѢ ↟"05ஹ౵ੌഝࢿച

Slide 127

Slide 127 text

#BTFMJOFQSPGJMFT #BTFMJOFQSPGJMFT਷ݠन௏٘ী؀ೠ઺ਃೠ҃۽ܳࢎ੹ஹ౵ੌೞӝਤ೧ 
 ࢸ஖઺ী"OESPJE۠ఋ੐ "35 ীࢲࢎਊೞח"1,ীನೣػ௿ېझ߂ݫࢲ٘ݾ۾ੑפ׮ IUUQTEFWFMPQFSBOESPJEDPNUPQJDQFSGPSNBODFCBTFMJOFQSPGJMFT

Slide 128

Slide 128 text

#BTFMJOFQSPGJMFT .BDSPCFODINBSLݽٕࢸ੿

Slide 129

Slide 129 text

#BTFMJOFQSPGJMFT .BDSPCFODINBSLݽٕࢸ੿ ௏٘੘ࢿ CBTFMJOFQSPGJMFTࢤࢿ @ExperimentalBaselineProfilesApi @RunWith(AndroidJUnit4::class) class BaselineProfileGenerator { @get:Rule val baselineProfileRule = BaselineProfileRule() @Test fun startup() = baselineProfileRule.collectBaselineProfile( packageName = AppPackageName ) { pressHome() startActivityAndWait() } }

Slide 130

Slide 130 text

#BTFMJOFQSPGJMFT .BDSPCFODINBSLݽٕࢸ੿ ௏٘੘ࢿ CBTFMJOFQSPGJMFT੸ਊ੹റஏ੿ @RunWith(AndroidJUnit4::class) class BaselineProfileBenchmark { @get:Rule val benchmarkRule = MacrobenchmarkRule() // … private fun startup(compilationMode: CompilationMode) { benchmarkRule.measureRepeated( packageName = AppPackageName, metrics = listOf(StartupTimingMetric()), iterations = 10, startupMode = StartupMode.COLD, compilationMode = compilationMode ) { pressHome() startActivityAndWait() } } }

Slide 131

Slide 131 text

#BTFMJOFQSPGJMFT .BDSPCFODINBSLݽٕࢸ੿ ௏٘੘ࢿ CBTFMJOFQSPGJMFT੸ਊ੹റஏ੿ @RunWith(AndroidJUnit4::class) class BaselineProfileBenchmark { @get:Rule val benchmarkRule = MacrobenchmarkRule() @Test fun startupNoCompilation() { startup(compilationMode = CompilationMode.None()) // JIT ஹ౵ੌ } @Test fun startupBaselineProfile() { startup(compilationMode = CompilationMode.Partial(baselineProfileMode = BaselineProfileMode.Require)) // AOT ஹ౵ੌ } // … }

Slide 132

Slide 132 text

#BTFMJOFQSPGJMFT .BDSPCFODINBSLݽٕࢸ੿ ௏٘੘ࢿ #VJME7BSJBOUTࢸ੿

Slide 133

Slide 133 text

#BTFMJOFQSPGJMFT ܖ౟ӂೠഝࢿച

Slide 134

Slide 134 text

#BTFMJOFQSPGJMFT adb root

Slide 135

Slide 135 text

#BTFMJOFQSPGJMFT ܖ౟ӂೠഝࢿച 3VO

Slide 136

Slide 136 text

#BTFMJOFQSPGJMFT ܖ౟ӂೠഝࢿച 3VO adb pull

Slide 137

Slide 137 text

#BTFMJOFQSPGJMFT ܖ౟ӂೠഝࢿച 3VO adb pull CBTFMJOFQSPGUYUਵ۽౵ੌݺ߸҃റTSDNBJOਵ۽੉ز

Slide 138

Slide 138 text

#BTFMJOFQSPGJMFT ܖ౟ӂೠഝࢿച 3VO adb pull CBTFMJOFQSPGUYUਵ۽౵ੌݺ߸҃റTSDNBJOਵ۽੉ز ੸ਊ੹റ࠺Ү

Slide 139

Slide 139 text

#BTFMJOFQSPGJMFT ܖ౟ӂೠഝࢿച 3VO adb pull CBTFMJOFQSPGUYUਵ۽౵ੌݺ߸҃റTSDNBJOਵ۽੉ز ੸ਊ੹റ࠺Ү

Slide 140

Slide 140 text

#BTFMJOFQSPGJMFT ܖ౟ӂೠഝࢿച 3VO adb pull CBTFMJOFQSPGUYUਵ۽౵ੌݺ߸҃റTSDNBJOਵ۽੉ز ੸ਊ੹റ࠺Ү ੸ਊೡݽٕী੄ઓࢿ୶о dependencies { implementation("androidx.profileinstaller:profileinstaller:1.2.0-beta01") }

Slide 141

Slide 141 text

6. Wrap-up ↟ة݀ߡ੹ҙܻ୓҅द੘

Slide 142

Slide 142 text

6. Wrap-up ↟नӏ"1*DPMMFDU"T4UBUF8JUI-JGFDZDMF

Slide 143

Slide 143 text

6. Wrap-up ↟ஹನૉղࠗખ؊੗ࣁ൤ࠁӝ IUUQTTVOHCJODPNQPTFEJWF

Slide 144

Slide 144 text

хࢎ೤פ׮ ஹನૉ ղࠗ ௏ٜ٘਷ cs.android.com ীࢲ оઉ৳णפ׮. ੉ ೐ۨઃప੉࣌ীח షझ౱ীࢲ ઁҕೠ షझಕ੉झо ੸ਊغয ੓णפ׮. QnA🙋