├── .gitignore
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── local.properties
├── settings.gradle
└── src
└── main
├── AndroidManifest.xml
└── kotlin
└── com
└── github
└── takahirom
└── compose
├── DefaultChoreographerFrameClock.kt
├── GlobalSnapshotManager.kt
├── Main.kt
├── MainActivity.kt
├── launchComposeInsideLogger.kt
└── rootNodeToString.kt
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | .idea/
3 | .gradle/
4 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | // TODO: remove after new build is published
4 | mavenLocal().mavenContent {
5 | includeModule("org.jetbrains.compose", "compose-gradle-plugin")
6 | }
7 | google()
8 | jcenter()
9 | maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
10 | maven {
11 | url 'https://jitpack.io'
12 | }
13 | }
14 |
15 | dependencies {
16 | // __LATEST_COMPOSE_RELEASE_VERSION__
17 | classpath("org.jetbrains.compose:compose-gradle-plugin:1.0.0-alpha4-build344")
18 | classpath("com.android.tools.build:gradle:4.0.2")
19 | // __KOTLIN_COMPOSE_VERSION__
20 | classpath(group: 'org.jetbrains.kotlin', name: 'kotlin-gradle-plugin', version: '1.5.30')
21 | classpath 'com.github.takahirom:decomposer:main-SNAPSHOT'
22 | }
23 | }
24 | plugins {
25 | id 'org.jetbrains.kotlin.multiplatform' version '1.5.30'
26 | }
27 | apply plugin:"com.android.application"
28 | apply plugin:"org.jetbrains.compose"
29 | apply plugin: 'com.github.takahirom.decomposer'
30 |
31 | group 'org.example'
32 | version '1.0-SNAPSHOT'
33 |
34 | repositories {
35 | mavenCentral()
36 | jcenter()
37 | google()
38 | maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
39 | }
40 |
41 | android {
42 | compileSdkVersion 29
43 |
44 | defaultConfig {
45 | applicationId "com.github.takahirom.compose"
46 | minSdkVersion 21
47 | targetSdkVersion 29
48 |
49 | versionCode 1
50 | versionName "1.0.0"
51 | }
52 | buildTypes {
53 | getByName("release"){
54 | signingConfig = signingConfigs.getByName("debug")
55 | debuggable = true
56 | }
57 | }
58 | compileOptions {
59 | sourceCompatibility 1.8
60 | targetCompatibility 1.8
61 | }
62 | }
63 |
64 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
65 | kotlinOptions {
66 | jvmTarget = "1.8"
67 | }
68 | }
69 |
70 | kotlin {
71 | jvm()
72 | android()
73 |
74 | sourceSets {
75 | commonMain {
76 | dependencies {
77 | implementation kotlin('stdlib-common')
78 | implementation compose.runtime
79 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0"
80 | }
81 | }
82 | commonTest {
83 | dependencies {
84 | implementation kotlin('test-common')
85 | implementation kotlin('test-annotations-common')
86 | }
87 | }
88 | jvmMain {
89 | dependencies {
90 | compileOnly "org.jetbrains.compose.compiler:compiler-hosted:1.0.0-alpha4-build344"
91 | }
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 | android.useAndroidX=true
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/takahirom/simple-compose-for-learning-inside-compose/873568ae64965d50cd68abe238e1c5dcb6b30fcc/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Aug 28 15:26:21 JST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/local.properties:
--------------------------------------------------------------------------------
1 | ## This file must *NOT* be checked into Version Control Systems,
2 | # as it contains information specific to your local configuration.
3 | #
4 | # Location of the SDK. This is only used by Gradle.
5 | # For customization when using a Version Control System, please read the
6 | # header note.
7 | #Sun Sep 12 16:53:05 JST 2021
8 | sdk.dir=/Users/takahirom/Library/Android/sdk
9 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'compose'
2 |
3 |
--------------------------------------------------------------------------------
/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/github/takahirom/compose/DefaultChoreographerFrameClock.kt:
--------------------------------------------------------------------------------
1 | package com.github.takahirom.compose
2 |
3 | import android.view.Choreographer
4 | import androidx.compose.runtime.MonotonicFrameClock
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.runBlocking
7 | import kotlinx.coroutines.suspendCancellableCoroutine
8 |
9 | object DefaultChoreographerFrameClock : MonotonicFrameClock {
10 | private val choreographer = runBlocking(Dispatchers.Main.immediate) {
11 | Choreographer.getInstance()
12 | }
13 |
14 | override suspend fun withFrameNanos(
15 | onFrame: (frameTimeNanos: Long) -> R
16 | ): R = suspendCancellableCoroutine { co ->
17 | val callback = Choreographer.FrameCallback { frameTimeNanos ->
18 | co.resumeWith(runCatching { onFrame(frameTimeNanos) })
19 | }
20 | choreographer.postFrameCallback(callback)
21 | co.invokeOnCancellation { choreographer.removeFrameCallback(callback) }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/github/takahirom/compose/GlobalSnapshotManager.kt:
--------------------------------------------------------------------------------
1 | package com.github.takahirom.compose
2 |
3 | import androidx.compose.runtime.ExperimentalComposeApi
4 | import androidx.compose.runtime.snapshots.ObserverHandle
5 | import androidx.compose.runtime.snapshots.Snapshot
6 | import kotlinx.coroutines.CoroutineScope
7 | import kotlinx.coroutines.Dispatchers
8 | import kotlinx.coroutines.SupervisorJob
9 | import kotlinx.coroutines.launch
10 |
11 | // from: https://github.com/ShikaSD/compose-browser-demo/blob/18b25325b8f5366b9b547cd05c49d2e130e52dea/prelude/src/main/kotlin/compose/web/internal/GlobalSnapshotManager.kt
12 | internal object GlobalSnapshotManager {
13 | private var started = false
14 | private var commitPending = false
15 | private var removeWriteObserver: (ObserverHandle)? = null
16 |
17 | private val scheduleScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
18 |
19 | @OptIn(ExperimentalComposeApi::class)
20 | fun ensureStarted() {
21 | if (!started) {
22 | started = true
23 | removeWriteObserver = Snapshot.registerGlobalWriteObserver(globalWriteObserver)
24 | }
25 | }
26 |
27 | @OptIn(ExperimentalComposeApi::class)
28 | private val globalWriteObserver: (Any) -> Unit = {
29 | // Race, but we don't care too much if we end up with multiple calls scheduled.
30 | if (!commitPending) {
31 | commitPending = true
32 | schedule {
33 | commitPending = false
34 | Snapshot.sendApplyNotifications()
35 | }
36 | }
37 | }
38 |
39 | /**
40 | * List of deferred callbacks to run serially. Guarded by its own monitor lock.
41 | */
42 | private val scheduledCallbacks = mutableListOf<() -> Unit>()
43 |
44 | /**
45 | * Guarded by [scheduledCallbacks]'s monitor lock.
46 | */
47 | private var isSynchronizeScheduled = false
48 |
49 | /**
50 | * Synchronously executes any outstanding callbacks and brings snapshots into a
51 | * consistent, updated state.
52 | */
53 | private fun synchronize() {
54 | scheduledCallbacks.forEach { it.invoke() }
55 | scheduledCallbacks.clear()
56 | isSynchronizeScheduled = false
57 | }
58 |
59 | private fun schedule(block: () -> Unit) {
60 | scheduledCallbacks.add(block)
61 | if (!isSynchronizeScheduled) {
62 | isSynchronizeScheduled = true
63 | scheduleScope.launch { synchronize() }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/github/takahirom/compose/Main.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("FunctionName")
2 |
3 | package com.github.takahirom.compose
4 |
5 | import androidx.compose.runtime.AbstractApplier
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.Composition
8 | import androidx.compose.runtime.LaunchedEffect
9 | import androidx.compose.runtime.Recomposer
10 | import androidx.compose.runtime.ReusableComposeNode
11 | import androidx.compose.runtime.getValue
12 | import androidx.compose.runtime.mutableStateOf
13 | import androidx.compose.runtime.remember
14 | import androidx.compose.runtime.setValue
15 | import kotlinx.coroutines.CoroutineScope
16 | import kotlinx.coroutines.Dispatchers
17 | import kotlinx.coroutines.InternalCoroutinesApi
18 | import kotlinx.coroutines.MainScope
19 | import kotlinx.coroutines.delay
20 | import kotlinx.coroutines.launch
21 | import kotlinx.coroutines.yield
22 |
23 |
24 | sealed class Node {
25 | val children = mutableListOf()
26 |
27 | class RootNode : Node() {
28 | override fun toString(): String {
29 | return rootNodeToString()
30 | }
31 | }
32 |
33 | data class Node1(
34 | var name: String = "",
35 | ) : Node()
36 |
37 | data class Node2(
38 | var name: String = "",
39 | ) : Node()
40 | }
41 |
42 |
43 | @OptIn(InternalCoroutinesApi::class)
44 | fun runApp() {
45 | val composer = Recomposer(Dispatchers.Main)
46 |
47 | GlobalSnapshotManager.ensureStarted()
48 | val mainScope = MainScope()
49 | mainScope.launch(DefaultChoreographerFrameClock) {
50 | composer.runRecomposeAndApplyChanges()
51 | }
52 |
53 | val rootNode = Node.RootNode()
54 | Composition(NodeApplier(rootNode), composer).apply {
55 | setContent {
56 | Content()
57 | }
58 | launchNodeLogger(mainScope, rootNode)
59 | launchComposeInsideLogger(composer, mainScope)
60 | }
61 |
62 | }
63 |
64 | private fun launchNodeLogger(
65 | mainScope: CoroutineScope,
66 | node: Node.RootNode
67 | ) {
68 | mainScope.launch {
69 | var nodeString = ""
70 | while (true) {
71 | val newNodeString = node.toString()
72 | if (nodeString != newNodeString) {
73 | nodeString = newNodeString
74 | println(nodeString)
75 | }
76 | yield()
77 | }
78 | }
79 | }
80 |
81 | @Composable
82 | fun Content() {
83 | var state by remember { mutableStateOf(true) }
84 | LaunchedEffect(Unit) {
85 | delay(10000)
86 | state = false
87 | }
88 | if (state) {
89 | Node1()
90 | }
91 | Node2()
92 | }
93 |
94 | @Composable
95 | private fun Node1(name: String = "node1") {
96 | ReusableComposeNode(
97 | factory = {
98 | Node.Node1()
99 | },
100 | update = {
101 | set(name) { this.name = it }
102 | },
103 | )
104 | }
105 |
106 | @Composable
107 | private fun Node2(name: String = "node2") {
108 | ReusableComposeNode(
109 | factory = {
110 | Node.Node2()
111 | },
112 | update = {
113 | set(name) { this.name = it }
114 | },
115 | )
116 | }
117 |
118 |
119 | class NodeApplier(node: Node) : AbstractApplier(node) {
120 | override fun onClear() {
121 | println("onClear")
122 | current.children.clear()
123 | }
124 |
125 | override fun insertBottomUp(index: Int, instance: Node) {
126 | // use top down
127 | }
128 |
129 | override fun insertTopDown(index: Int, instance: Node) {
130 | println("insert$index")
131 | current.children.add(index, instance)
132 | }
133 |
134 | override fun move(from: Int, to: Int, count: Int) {
135 | current.children.move(from, to, count)
136 | }
137 |
138 | override fun remove(index: Int, count: Int) {
139 | println("remove$index")
140 | current.children.remove(index, count)
141 | }
142 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/github/takahirom/compose/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.github.takahirom.compose
2 |
3 | import android.app.Activity
4 | import android.os.Bundle
5 |
6 | class MainActivity : Activity() {
7 |
8 | override fun onCreate(savedInstanceState: Bundle?) {
9 | super.onCreate(savedInstanceState)
10 | runApp()
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/github/takahirom/compose/launchComposeInsideLogger.kt:
--------------------------------------------------------------------------------
1 | package com.github.takahirom.compose
2 |
3 | import androidx.compose.runtime.Composition
4 | import androidx.compose.runtime.Recomposer
5 | import kotlinx.coroutines.CoroutineScope
6 | import kotlinx.coroutines.GlobalScope
7 | import kotlinx.coroutines.flow.collect
8 | import kotlinx.coroutines.launch
9 | import kotlinx.coroutines.yield
10 |
11 | fun Composition.launchComposeInsideLogger(composer: Recomposer, mainScope: CoroutineScope) {
12 | mainScope.launch {
13 | composer.state.collect {
14 | println("composer:$it")
15 | }
16 | }
17 | val slotTable = Class.forName("androidx.compose.runtime.CompositionImpl")
18 | .getDeclaredField("slotTable")
19 | .apply {
20 | isAccessible = true
21 | }
22 | .get(this)
23 | GlobalScope.launch {
24 | var lastSlotTableString = ""
25 | while (true) {
26 | val slotTableString = Class.forName("androidx.compose.runtime.SlotTable")
27 | .getMethod("asString")
28 | .apply {
29 | isAccessible = true
30 | }
31 | .invoke(slotTable) as String
32 |
33 | if (slotTableString != lastSlotTableString) {
34 | lastSlotTableString = slotTableString
35 | val groups = Class.forName("androidx.compose.runtime.SlotTable")
36 | .getDeclaredField("groups")
37 | .apply {
38 | isAccessible = true
39 | }
40 | .get(slotTable) as IntArray
41 | val slots = Class.forName("androidx.compose.runtime.SlotTable")
42 | .getDeclaredField("slots")
43 | .apply {
44 | isAccessible = true
45 | }
46 | .get(slotTable) as Array
47 |
48 |
49 | println("------")
50 | println("slotTable:")
51 | println(slotTableString)
52 | println("groups:")
53 | groups.toList().windowed(
54 | size = 5,
55 | step = 5,
56 | partialWindows = false
57 | )
58 | .forEachIndexed { index, group ->
59 | val (key, groupInfo, parentAnchor, size, dataAnchor) = group
60 | println(
61 | "index: $index, " +
62 | "key: $key, " +
63 | "groupInfo: $groupInfo, " +
64 | "parentAnchor: $parentAnchor, " +
65 | "size: $size, " +
66 | "dataAnchor: $dataAnchor"
67 | )
68 | }
69 | println("slots:")
70 | println(slots.mapIndexed { index, slot -> index to slot }.joinToString("\n") { (index, slot) ->
71 | "$index: $slot(${slot?.javaClass})"
72 | })
73 | }
74 | yield()
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/github/takahirom/compose/rootNodeToString.kt:
--------------------------------------------------------------------------------
1 | package com.github.takahirom.compose
2 |
3 | fun Node.RootNode.rootNodeToString(): String {
4 | return buildString {
5 | appendLine("RootNode")
6 | children.forEachIndexed { index, node ->
7 | if (index == children.lastIndex) {
8 | appendLine("└── $node")
9 | } else {
10 | appendLine("├── $node")
11 | }
12 | }
13 | }
14 | }
15 |
16 |
--------------------------------------------------------------------------------