Going to start this with a question, but will add ...
# compose
d
Going to start this with a question, but will add more context in a thread, in case I’m asking the wrong thing. The question: Is it possible to indicate to compose that a composable I want to call should not get recomposed like it normally would? Just initialized the first time and then state reused forever from that point forward?
In other words, in the following code:
Copy code
@Composable
fun Common(content: @Composable () -> Unit) {
  val someValue = remember { … }
  …
}

@Composable
fun First() {
  Common {
   …
  }
}

@Composable
fun Second() {
  Common {
   …
  }
}
I would love to tell compose that no matter how I enter
Common
, I want the remembered state (i.e.
someValue
) not to be reset.
---------- For some concrete context, I’m working in the world of web dev, where there’s a page registered per URL. Users are encouraged to create common layouts that are shared across pages, such as a layout with a sidebar and header bar, for example. Currently, I have users create a composable which represent the page and then they register that page with the URL it is associated with. A page method might look like:
Copy code
@Composable
fun SomePage() {
  SidebarLayout { ... }
}

@Composable
fun AnotherPage() {
  SidebarLayout { ... }
}
And then later registered:
Copy code
router.register("/some-page") { 
  SomePage() 
}

router.register("/another-page") {
  AnotherPage()
}
Of course, every time a page is visited, the layout composable, since it is a child of the page, is recomposed, and any state within it (such as which sidebar elements are expanded / collapsed) is reset.
i
Just to clarify, are you saying that every screen should have the same state for Common or that you want each screen to have its own copy of Common's state (you just want that state to be saved)?
d
The state for Common should be global across all pages
Changing it on one page should affect another
Normally how I do this is save state to disk on every interaction I want to keep. But I feel like doing that is against the spirit of compose
I assume in an Android app, things would look like this:
Copy code
Common {
   when (page) {
      "some-page" -> SomePage()
      "another-page" -> AnotherPage()
   }
}
but I'm unfortunately inverting this relationship
i
It sounds like that's exactly what moveable content is for: composables and state you want to re-parent: https://betterprogramming.pub/exploring-movablecontentof-in-jetpack-compose-6807a43047cd
d
Yeah I actually didn't want this thread to get too complex, so I didn't mention I tried this already, but I want
movableContentOf
but for parent containers
I tried really hard to get it to work but I couldn't find a way
i
I don't understand what you mean. The parent container isn't what you are reparenting, it is the specific shared UI within that container
d
Revisiting the
register
example above, if I extract layouts:
Copy code
router.register(
  "/some-page",
  layout = { CommonLayout() }
) { 
  SomePage() 
}

router.register(
  "/another-page",
  layout = { CommonLayout() }
) {
  AnotherPage()
}
but because of the wrapping lambda, Compose still recomposes the layout
So because I'm using a router, basically I have a callback stored in a variable
i
You would need to pass in the moveable content declared at a higher level then execute it in the lambda, yes.
Copy code
layout = { sharedContent ->
  sharedContent()
  // Other content
}
d
So in this simple example, the layout is the same, but other pages may use different layouts
Copy code
@Composable
fun SomePage() {
  SidebarLayout { ... }
}

@Composable
fun AnotherPage() {
  SidebarLayout { ... }
}

@Composable
fun TutorialPage() {
  DocsLayout { ... }
}
i
Yes, you might need multiple separate moveable contents if you want multiple shared UIs that re-parent their state
d
Yeah I tried that, but it didn't work
In a test project, I had
Copy code
val layoutA = movableContentOf(...)
val layoutB = ...
val layoutC = ...

when (page) {
   0 -> layoutA { ... }
   1 -> layoutA { ... }
   2 -> layoutB { ... }
   3 -> layoutB { ... }
   4 -> layoutC { ... }
}
and that basically treated each branch as a new call and reset my state
It's possible there's user error here
but fundamentally, I would love to tell Compose that if a particular composable ends up in the same place in the call hierarchy after a recompose, I'd love to leave it alone.
Copy code
App -> SomePage -> CommonLayout
App -> AnotherPage -> CommonLayout // don't recompose
App -> TutorialPage -> DocsLayout // ok to recompose
I appreciate this may not be possible, or I might even be thinking about things wrong, but I thought I'd ask in case there was a tool in the compose toolkit I wasn't aware of
I really thought
movableContentOf
would let me do it but I couldn't figure it out.
------------------- Here's a more fully concrete psuedo-example:
Copy code
@Composable
fun Common(content: @Composable () -> Unit) {
    val commonValue = remember { Random.nextInt() }
    println("Common value is $commonValue")
    content()
}

@Composable
fun SomePage() {
    Common { println("Some page") }
}

@Composable
fun AnotherPage() {
    Common { println("Another page") }
}

@Composable
fun PageSelector() {
    var currentPage by remember { mutableStateOf(0) }
    Button(onClick = { currentPage = (currentPage + 1) % 2 }) { Text("Change the current page") }

    when (currentPage) {
        0 -> SomePage()
        1 -> AnotherPage()
    }
}
Of course here the
when
block is hand written, but in practice, the current page is being driven by the user typing a URL into the browser, which in turn updates the active page and re-renders it. Still, the above case should help exemplify the problem I have. if you have the console open, you'll see that the int value is reset each time a new page is composed, whereas I want to figure out how to keep that int value in
Common
the same across pages.
------- And here's an attempt to use
movableContentOf
that doesn't work, potentially exposing a misunderstanding I have of the concept:
Copy code
@Composable
fun Common(content: @Composable () -> Unit) {
    val commonValue = remember { Random.nextInt() }
    println("Common value is $commonValue")
    content()
}

@Composable
fun SomePage(layout: @Composable (@Composable () -> Unit) -> Unit) {
    layout { println("Some page") }
}

@Composable
fun AnotherPage(layout: @Composable (@Composable () -> Unit) -> Unit) {
    layout { println("Another page") }
}

@Page
@Composable
fun HomePage() {
    var currentPage by remember { mutableStateOf(0) }
    Button(onClick = { currentPage = (currentPage + 1) % 2 }) { Text("Change the current page") }

    val layout = movableContentOf<@Composable () -> Unit> { content -> Common(content) }

    when (currentPage) {
        0 -> SomePage(layout)
        1 -> AnotherPage(layout)
    }
}