I've seen two different suggested methods of hiding widgets in Compose.
Set alpha to zero, analogous to Android's View.INVISIBLE because the widget still takes up space.
Text("I'm visible", Modifier.alpha(if (shouldShowIt) 1f else 0f))
Completely omit the element if it shouldn't be shown, analogous to Android's View.GONE.
if (shouldShowIt) {
Text("I'm visible")
}
I would assume that in case 1, Compose will reuse the same widget element after a recomposition and merely draw it differently.
However, in case 2, does Compose have to allocate a new Text widget instance from scratch if the text was not shown during the previous recomposition but then becomes visible again in another recomposition? Or, does it use intelligent pooling of these widget classes?
From briefly browsing the source code, as far as I can tell, the nodes that we are building are automatically pooled. I'm specifically wondering about the UI elements that are generated from the nodes (the classes that are internal to the library but are responsible for the actual rendering). Or are these the same classes as the nodes themselves?
If pooling is not used, I would guess that the first method would be better for performance, even though the code is uglier.
Related
In my app I want to display some sort of tutorial to guide users through the app - something like this. The app already uses tutorials in other parts therefore I want to integrate Compose UI support into the existing tutorial framework.
For this purpose I need to get the position and size of several composables. Therefore I want to set semantic
properties to the views I want to identify like this:
Text(text = text, Modifier.semantics {
set(customPropertyKey, "CustomValue")
})
How can I query the semantics tree in order to find out details like position and size about the composable with the value "CustomValue"?
This is possible in Junit tests using composeTestRule.onNode(...). However, how can I do that at runtime (not in a unit test)?
I know that reading the position and size might work using the onGloballyPositioned callbacak but this would lead to much boilerplate code which I try to avoid.
#CommonsWare's solution seems viable. Just create a boolean to denote whether tutorial is active, then use it inside the Composables to render accordingly. If you want the size and position of the items, you could simply store them inside the viewmodel. To get the values, just use the onGloballyPositioned{...} Modifier. You can retrieve the size by calling it.size (or name the labda parameter) inside it and the coordinates with the same approach.
Also I would suggest that you implement the stuff from the ground up. Maybe migrate slowly in a scratch project. If you try to migrate with the same sematics as the view system, creating an unbalanced hybrid, you will not be able to get the full advantage of the declarative paradigm that Compose is built on.
I am going through the Compose pathway and I keep seeing all of this emphasis on state hoisting. Is this in some way more relevant in the new Compose framework as compared to the old paradigm? It seems like it is just a good programming technique in general. I am worried I am missing a particular advantage in the Composable system.
From the perspective of an Activity or a Fragment they are nearly identical. A stream of states come in and the Activity or Fragment needs to react to the changes by updating its content.
In Compose, however, the composables are themselves tiny fragments of UI all the way down, so to speak. In Compose you can arbitrarily break a UI into smaller and smaller pieces that each react to state changes. It is in this decomposition (so to speak) that state hoisting becomes important and allows these smaller UI pieces to be reusable. Even when you use a Text or a Button they are just composable functions that layout and draw text or map user input to click events, etc.
If state is already in the UI model then it is already hoisted. No additional hoisting is necessary. State hoisting is only important if the composable has its own model of information. The TextField, for example, takes its state as a parameter instead of creating and holding it internally. This allows the state to be held directly in the UI model instead of needing to be synchronized with it. The model can decide if an when the state changes, the TextField just requests the state to change, it doesn't control it. Traditionally, a TextField would have it own internal model of the text and just send out onChange notifications when the value changed. This means there are at least two models of the value of the field, one held by the control and one held by the application model. Synchronizing these can be tricky.
Allowing hoisting of composable state allows there to be a single source of truth, the application model, instead of several states all needing to be synchronized to a single value. Compose does not dictate what the model is or how it is stored, it just needs to know when the value changes so it knows when to update the UI.
In normal Xml activity building exists an <include> property that allows reuse designs in different activities like top and bottom bars. I have been searching for something like that in Jetpack Compose but I cannot find anything.
I know that #Composable functions work as independent elements that can be used whenever you want, but if I'd used this approach these elements would be reloaded whenever the activity changed.
There's no need: just call the function where you've defined the design/layout you want to use.
In traditional Android layouts, the <include> tag is needed because XML doesn't have any concept of "calling" another XML file. In Compose, every layout and every component is a function, so you can simply call the function wherever you want to reuse that layout.
There's almost no difference between including the contents of a composable function directly inside another composable function and calling that composable function (there are slight differences in the runtime's internal bookkeeping, but generally nothing that users of Compose need to worry about).
In fact, you can mark a composable function inline to achieve this more directly and have the contents inlined into the calling function.
I have a weird problem(I assume, not common in Android). I'am working on a product which has its UI "elements" defined by a individual(admin) using our website. Positioning, characteristic and order of the UI elements is decided by the admin. However, type of UI elements is pre selected. Some UI elements are - checkbox, editText,spinner etc.
Although we handle this programatically, I was wondering if same can be achieved by pre-defined XML(since we know the UI elements we support) and dynamically modify the position and characteristic of the UI elements.
I wanted to do this because it is becoming very painful to manage the UI using the java code.
Any suggestions is appreciated,
SKU
The question is puzzling. You say that your admin defines the UI elements, "however", the type of the UI elements is pre-selected? Preselected by who? Exactly what in XML is pre-defined, and what isn't?
If a widget class is defined in XML (a widget is something like , , etc.), you can modify some aspects of the widget's appearance by inflating its layout (which is done automatically for the main layout), finding the widget within the layout tree, and changing the widget's characteristics.
You can't change the widget's type. You'd have to delete the widget from the layout and add in a new one that fits into roughly the same space.
My answer to this question was just accepted but I started to wonder when exactly one needs to invalidate() a View and when it is not necessary?
After a bit of thinking I came to realization that it should work more or less like this:
actual drawing of "everything" occurs after onResume()
in "free" time parts of the screen can be redrawn but only those that were invalidated (and everything underneath)
Therefore, it would seem, if I change something after onResume() (e.g. as a response to a button click, I should invalidate() the changed View).
However, from what scana in this question says, it must be more complex then that and it depends somethimes on what method one uses.
E.g. on whether one uses
lastClicked.setImageBitmap();
or
lastClicked.setImageResource();
So, when it's necessary to execute invalidate() on a View and how does it really work ?
(Do consider accepting some answers)
Generally, invalidate() means 'redraw on screen' and results to a call of the view's onDraw() method. So if something changes and it needs to be reflected on screen, you need to call invalidate(). However, for built-in widgets you rarely, if ever, need to call it yourself. When you change the state of a widget, internal code will call invalidate() as necessary and your change will be reflected on screen. For example, if you call TextView.setText(), after doing a lot of internal processing (will the text fit on screen, does it need to be ellipsised, etc.), TextView will call invalidate() before setText() returns. Similarly for other widgets.
If you implement a custom view, you will need to call invalidate() whenever the backing model changes and you need to redraw your view. It can also be used to create simple animations, where you change state, then call invalidate(), change state again, etc.
Usually, the system handles resizing, hiding, showing and a ton of other things for your widgets automatically but it sometimes has issues if the underlying buffer for drawn pixels or backing data has changed or is stale (you swap the image resource on a View or the raw dataset changes). This occurs because there is no way that the OS can know that the data changed in the specific manner that it did.
In these cases where you are dealing with drawing, you have to tell the system that its underlying data is not in a good state with Widget.invalidate() and the re-drawing gets queued on the main thread just as you mentioned. Depending on the system implementation and Android version what is tracked for changes by the system varies but what I normally do is assume that system resources (byte arrays, char arrays, resource indexes, manual drawing on the context) are not tracked and need an invalidate and everything else will be handled by the system.
Please remember that drawing on the screen is frequent process, whenever you update a view, that change should be propogated and redrawn to notify such change right. invalidate() is a trigger method,that signals force reDrawing of any view you wish to show changes for.
I had this problem when I wanted to draw a textPaint!
My code was
canvas.drawPaint(textPaintNumber)
canvas.drawText("MyText", 30F, 63F, textPaintNumber)
I cleared the first lint and the problem was solved
canvas.drawText("MyText", 30F, 63F, textPaintNumber)