2

Im trying to createa a @composable function that is able to keep track of all its children. The first Parent TextExecutionOrder should be able to tell that it has 3 Children of the same composable function TestExecutionOrder("2"), TestExecutionOrder("3") and TestExecutionOrder("10").

@Preview
@Composable
fun test() {

    TestExecutionOrder("1") {
    TestExecutionOrder("2") {
        TestExecutionOrder("15") {}
        TestExecutionOrder("5") {
            TestExecutionOrder("6") {}
            TestExecutionOrder("7") {}
        }
    }
    TestExecutionOrder("3") {}
    TestExecutionOrder("10") {}
   }
}

For Example the above Code could have a datastructure like a Stack, CircularArray or anything else where it stores the following.

  • Parent{1} -> Childs{2,3,10}
  • Parent{2} -> Childs{15,5}
  • Parent{15} -> Childs{}
  • Parent{5} -> Childs{6,7}
  • Parent{6} -> Childs{}
  • Parent{7} -> Childs{}
  • Parent{3} -> Childs{}
  • Parent{10} -> Childs{}

v

data class Faaa(val name: String)//val size : IntSize,val pos: Offset)

@Composable
fun TestExecutionOrder(
    text: String,
    arr: CircularArray<Faaa>,
    stack: Stack<CircularArray<Faaa>>,
    content: @Composable () -> Unit,

) {
   //TODO 
   content()
}

In QML I would be able to iterate through the children elements of a parent and then be able to add all Items that are an instance of TestExecutionOrder inside my desired data structure.

I tried to use State-hoisting where my Stack data structure is at top of my test() function and then passed through all children. Where each children will only get the stack().peek() reference of the current circular array but Kotlin is pass by value so this also doesn't work.

Pass By Reference Solution that obv doesnt work :D

@Composable
fun TestExecutionOrder(
    text: String,
    arr: CircularArray<Faaa>,
    stack: Stack<CircularArray<Faaa>>,
    content: @Composable () -> Unit,
    ) {
    
    arr.addLast(Faaa(text)) // Same reference for all children
    stack.push(CircularArray<Faaa>()) // create new circularArray for new children
    content()
}

data class Faaa(val name: String)//val size : IntSize,val pos: Offset)

@Preview
@Composable
fun test() {
    val stack = Stack<CircularArray<Faaa>>()
    stack.push(CircularArray<Faaa>())

    TestExecutionOrder("1",stack.peek(),stack) {
        var referenceCir = stack.peek()
        TestExecutionOrder("2",referenceCir,stack) {
            var referenceCir2 = stack.peek()
            TestExecutionOrder("15",referenceCir2,stack) {}
            TestExecutionOrder("5",referenceCir2,stack) {
                var referenceCir3 = stack.peek()
                TestExecutionOrder("6",referenceCir3,stack) {}
                TestExecutionOrder("7",referenceCir3,stack) {}
            }
        }
        TestExecutionOrder("3",referenceCir,stack) {}
        TestExecutionOrder("10",referenceCir,stack) {}
    }
}

I am assuming I am overthinking this stuff because I came from a QML/C++ Environment. How can one achieve this kind of stuff?

The Goal is to make this thing self managing I wrap my composable function around other functions and it automatically knows how many children of the same type it has without me explicitly passing it as a parameter.

EDIT1: Im aware that compose function can execute in any order

Ojav
  • 678
  • 6
  • 22

2 Answers2

2

I am not sure that I understood your question correctly, but:

I would recommend you to think of a composable function as of a way to describe the UI, not an object.

So you should describe your UI in a way, that is not very tied up to execution order, since indeed it is a bit hard to predict.

Assuming your goal, I recommend you to create a single composable function that will draw all "children" and will also manage the movement of the box. It is unlikely that parent composable will execute after children composables, since composable functions are being called. Therefore to call the child function the system needs to call the parent first.

Steyrix
  • 2,796
  • 1
  • 9
  • 23
  • the docs are stating `If a composable function contains calls to other composable functions, those functions might run in any order.` so I guess the parent will always be called first, only the childs can execute in any order. My UI is not tied to the execution order of my children. I am aware that a composable function is not an Object. – Ojav Nov 09 '22 at 13:27
  • I want to achieve this so I dont have to keep track of every thing manually. If I can deduce how many children of the same composable function I have I can save alot of time. – Ojav Nov 09 '22 at 13:27
  • Im still thanking you obv <3 – Ojav Nov 09 '22 at 13:28
  • You are welcome :) Why don't you create a single composable that creates multiple UI-elements and keeps a variable with the number of elements? If your number is not constant but dynamic, you can track it in the ViewModel. I really recommend you to incapsulate that kind of logic outside the composables – Steyrix Nov 09 '22 at 13:45
  • @Ojav did you have success with this task after all? – Steyrix Nov 10 '22 at 16:56
  • yes i posted the answer below. receiving the globalpos/absolute pos of my children is not working but keeping track of my children does. But you are probably right I probably have to start to think different when using compose @Steyrix – Ojav Nov 14 '22 at 13:41
0

I created a custom Tree DataStructure and used Kotlins "pass by reference" and it works. It is able to keep track of its children and also of its size (only the global/absolute position is not working(any suggestions are appreciated).

The basic Idea is to create a child at the beginning of the composable then continue with the passed Lambda-Scope(content) and after that go back to the parent.


Track Item Composable

class ToggleViewModel : ViewModel() { //View Model acts as a pass by reference @see todo add stackoverflow  
 var tree: TreeNode<Data> = TreeNode(Data("root"))  
}

data class Data(  
 val name: String,  
 var size: IntSize = IntSize(0, 0),  
 var pos: Offset = Offset(0f, 0f)  
)

@Composable  
fun TrackItem(  
 text: String,  
 toggleViewModel: ToggleViewModel = viewModel(),  
 content: @Composable () -> Unit,  
 ) {


//empty the list after a recomposition  
if(toggleViewModel.tree.value.name == "root"){  
    toggleViewModel.tree.children = mutableListOf()  
}  
toggleViewModel.tree.addChild(Data(text))  

//step 1 new tree is the child  
val lastChildrenIndex = toggleViewModel.tree.children.size - 1  
toggleViewModel.tree = toggleViewModel.tree.children[lastChildrenIndex]  

var relativeToComposeRootPosition by remember { mutableStateOf(Offset(0f, 0f)) }  
var size by remember { mutableStateOf(IntSize(0, 0)) }  
//assign new children pos and size after recomposition  
toggleViewModel.tree.value = Data(text, size, relativeToComposeRootPosition)  


Box(modifier = Modifier  
    .onGloballyPositioned { coordinates ->
            relativeToComposeRootPosition = coordinates.positionInRoot()
     }
    .onSizeChanged {  
         size = it  
    }) {  
    content()  
}  

//reverse step1  
if (toggleViewModel.tree.parent != null) {  
    toggleViewModel.tree = toggleViewModel.tree.parent!!  
}  

}

Tree DataStructure

class TreeNode<T>(var value: T){
    var parent:TreeNode<T>? = null

    var children:MutableList<TreeNode<T>> = mutableListOf()

    var currentChildren: TreeNode<T>? = null;
    private var currentIndex = 0

    fun addChild(nodeValue: T){
        val node = TreeNode(nodeValue)
        children.add(node)
        node.parent = this
        if(children.size == 1){ //change this once adding removeChild fun()
            currentChildren = children[0]
        }
    }


    fun nextChildren(){
        currentIndex++
        if(children.size == currentIndex +1){
            currentIndex = 0
        }
        currentChildren = children[currentIndex]
    }

    fun previousChildren(){
        currentIndex--
        if(0 > currentIndex){
            currentIndex = children.size - 1
        }
        currentChildren = children[currentIndex]
    }

    override fun toString(): String {
        var s = "$value"
        if (children.isNotEmpty()) {
            s += " {" + children.map { it.toString() } + " }"
        }
        return s
    }
}

Example Usage

@Preview
@Composable
fun ToggleFunctionality() {


    TrackItem("0") {
        Box(
            modifier = Modifier
                .width(PixelToDp(pixelSize = 200))
                .offset(x = PixelToDp(pixelSize = 100), y = PixelToDp(pixelSize = 50))
                .height(PixelToDp(pixelSize = 200))
                .background(Color.Red)
        ) {


            TrackItem("1") {
                Column(
                ) {
                    TrackItem("2") {
                        Box(
                            Modifier
                                .size(PixelToDp(pixelSize = 20))
                                .background(Color.Green)
                        )
                    }

                    TrackItem("3") {
                        Box(
                            Modifier
                                .size(PixelToDp(pixelSize = 20))
                                .background(Color.Blue)
                        )
                    }
                }
            }
        }
    }


    val toggleViewModel: ToggleViewModel = viewModel()


    Text(text= "test", modifier = Modifier
        .clickable {
            log("TEST")
            for (item in toggleViewModel.tree.children) {
                log(item.toString())
            }
        })
}

which prints the following

Data(name=0, size=200 x 200, pos=Offset(0.0, 0.0)) {[Data(name=1, size=20 x 40, pos=Offset(100.0, 50.0)) {[Data(name=2, size=20 x 20, pos=Offset(100.0, 50.0)), Data(name=3, size=20 x 20, pos=Offset(100.0, 70.0))] }] }
Ojav
  • 678
  • 6
  • 22