Recompose in Android Compose - android

I was implementing the outlinedTextField in android using the new compose library. But strangely the input data was not updated in the text field.
So I searched and found a topic called Recomposition in android compose. I didn't get it completely.
However, I did find the solution:
#Composable
fun HelloContent(){
var name:String by remember {mutableStateOf("")}
OutlinedTextField(
value = name,
onValueChange = {name = it},
label = {Text("Name")}
)
}
and I also read on the concept of State in jetpack compose. But I wasn't able to get it completely.
Can someone explain it in simple words?

Basically, recomposition is just an event in Compose, in which the Composable in concern is re-executed. In declarative coding, which is what Compose is based on, we write UI as functions (or methods, more commonly). Now, a recomposition is basically an event in which the UI is re-emitted, by executing the body of the said Composable "function" all over again. This is what recomposition is, at its core. Now on to when it is triggered.
Ok, so in order to trigger recompositions, we need a special type of variable. This type is built into compose and was specifically designed to let it know when to recompose. And the mentioned type is MutableState. As the name suggests, it is State, that can Mutate, i.e., change; vary.
So, we have a variable of type MutableState, what's next? Guess what, you DON'T have a variable of type MutableState because I didn't teach you how to create one! The most common assignment you will use in Compose is the mutableStateOf helper. This is a pre-defined method that returns a value of type MutableState, well, MutableState<T>, actually. T is the type of State here, see below
var a = mutableStateOf(999)
Above, as you can see, 999 is an Int, and so, mutableStateOf here will return a MutableState<Int> type value. Easy enough.
Now, we have a MutableState<Int> value, but honestly, that's kinda ugly. Every time you need to get the value out of the MutableState<T>, you would need to refer to a property conveniently named .value.
So, to get the 999 out of the above var a, you would need to call a.value. Now, this is fine for use at one or two places but calling this every time seems like a mess. That is where Kotlin Property Delegation Come In (I did not need to Capitalize the last two words, I know). We use the by keyword to retrieve the value out of the state, and assign that to our variable - That's all you should care about.
So, var a by mutableStateOf(999) will actually return 999 of type Int, and not of type MutableState<Int>, but the brilliant part is that Compose will still know that the variable a is a State-Holder. So basically mutableStateOf can be thought of as a registering-counter, which you just need to pass through once, in order to get registered in the list of State-Holders. As of when, a recomposition will trigger every time the value of one of the state-holders is changed. This is the rough idea, but let's get technical; Now on to the "how" of recomposition.
To trigger a recomposition, all you need to ensure is two things:
The Composable should be reading a variable, that is also a state-holder
The state-holder should experience a change in its current value
Everything's better with Perry Examples:-
var a by mutableStateOf(999)
Case 1: A Composable receives a as a parameter value, MyComposable(a), then I run a = 0,
Outcome 1: Recomposition Triggered
Case II: This declaration of variable a is actually inside a Composable itself, then I run a = 12344
Outcome II: Recomposition Triggered
Case III: I repeat cases 1 & II, but with a different variable, as follows: var b = 999
Outcome III: No Recompositions Triggered; Reason: b is not a state-holder
Great, we got the basics down now. So, this is the last phase of this lecture.
REMEMBER!!
You see when I say during recomposition, the entire Composable is re-executed, I mean the entire Composable is re-executed, that is, every single line and every single assignment, without exceptions. You see anything wrong with this yet? Lemme demonstrate
Let's say I want to have a Text Composable that displays a number, and increases that number when I click on it.
I could implement something as simple as this
#Composable
fun CountingText(){
var n = mutableStateOf(0) //Starts at 0
Text(
value = n.toString(), //The Composable only accepts strings, while n is of Int type
modifier = Modifier
.clickable { n++ }
)
}
Ok so this is the implementation that we might think would work. If you are unfamiliar with Modifiers, just leave that for now and trust me that it just triggers the code inside the clickable braces, when you actually click on the Text. Now, let's picture how this will be executed.
Firstly Compose will register the variable n as a state-holder. Then it will render the Text Composable with the initial value 0 of n.
Now, the Text is actually clicked. The block inside clicakble will be executed, which in this case is just n++, which will update the value of n. Compose sees that the value of n is updated, and runs through the list of state-holders. Compose finds that n is indeed a state-holder, and then decides to trigger a recomposition. Now, the entire Composable reading the value of n will be recomposed. In this case, that Composable is CountingText since a Text inside it is reading the value of n (To display it). Hence, CountingText will be "re-executed". Let's walk through the re-execution here.
First line in the Composable,
var n = mutableStateOf(0)
n became 0.
Next lines:-
Text(
value = n.toString(), //Just displays 0
modifier = Modifier
.clickable { n++ } //Just tells it to increase n upon click
)
So you see, the catch here is that upon re-execution, n is completely created from scratch as if it never existed before. It was removed from the Composable's memory. To counter this, we need the Composable to remember n. That way, Compose knows that this is a state-holder AND holds a value that needs to be re-assigned to it upon recomposition. So, here's the updated first line (the rest is the same, just the initialization is updated)
var n by remember { mutableStateOf(0) }
Now, upon first execution, n will receive 0, since it is actually the very first time n is created. Thanks to remember, n now has access to the Composable's memory, and thus will be stored in the memory for future usage.
So, during recomposition, this is what happens - When the executor (???) reaches the line where n is assigned,
var n by remember { mutableStateOf(0) }
remember actually acts as a gatekeeper, and does not allow the executor to enter the block contained in it. Instead, it passes it the previously remembered value and asks it to move on. Since when the user clicked the Text, it already incremented the value of n to 1, this was retained in memory and so, now this works as expected.
This is the same case for your TextField problem. The field initially reads an empty value and the value is updated every time the user types a letter, triggering a recomposition and finally displaying the correct value on the screen.
Could it get simpler enough? Let me know I spent half an hour typing this.

Recomposition is used in Compose to recompose (reload the parts that changed) the screen. In your example you have user input which changes the state of the screen. You have to use var name:String by remember {mutableStateOf("")}, by the way, you can leave the :String out because you set the type here: mutableStateOf("") anyway, you need to use remember that the composable remembers the old content and can then add the new content.
If you type in h e l l o everytime you type one letter it recomposes, it remembers the value h, then he, then hel and so on.

Try this
I know there is not difference but leaving imports
Compose_version 1.0.4
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
#Composable
fun YourAnswer() {
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("Name") }
)
}

Related

Unexplained Snapping Behaviour In A Programmatically Scrolled "Lazy List""

I stumbled upon this issue while trying to solve a case. To summarise, the patient needed complete control over the 'speed' of the scroll. Now, Android's sensible defaults have the item-scroll follow the user's finger at all times, but apparently an un-shared condition/requirement of this patient lead them to want the user scrolling further than the item actually scrolls. So, I suggested, as originally proposed by David in his solution, to disable the user's direct control over the lazy list, and manually scroll it by observing events from a touch detection callback, like the ones provided by the pointerInput(...) Modifier. Hence, the implementation:
#Composable
fun LazyStack(sdd: Int = 1) { // Scroll-Distance-Divisor
val lazyStackState = rememberLazyListState()
val lazyStackScope = rememberCoroutineScope()
LazyColumn(
modifier = Modifier.pointerInput(Unit) {
detectVerticalDragGestures { _, dragAmount ->
lazyStackScope.launch {
lazyStackState.scrollBy(-dragAmount / sdd)
}
}
},
state = lazyStackState,
userScrollEnabled = false
) {
items((0..500).map { LoremIpsum(randomInt(5)) }) {
Text(text = it.values.joinToString(" "))
}
}
}
Now, the treatment actually worked, except a .. tiny side-effect, apparently.
You see, the list actually scrolls perfectly as intended for the most part. For example, if I pass the sdd parameter as 2, the scroll distance is reduced to half its original value. But, the side-effect is "snapping".
Apparently while scrolling smoothly through the list, there are certain "points" on both sides of which, the list "snaps", WHILE THE USER HAS THIER INPUT POINTER IN USE (a finger, a mouse, or any other pointers, are actively "holding" the list). Now, this is a point, which, if you cross in either direction, the list snaps, and apparently by the exact same value every single time.
The list above uses Lorem Ipsum as a convention, but perhaps a numbered list would help the diagnose the problem.
So, a synopsis of the symptoms:
There seem to be certain "points" in the list, occurring at inconsistent intervals/positions throughout the list/screen which tend to make the list instantly scroll through a lot of pixels. Once discovered, the points are consistently present and can be used to re-produce the symptom unless recomposed. The value of the "snap" is apparently not returned to be a massive float when continuously logging the value returned by the scrollBy method. This makes the issue untraceable by regular diagnostic techniques, which is why we, the diagnosticians are here.
You have the history, you have the symptoms. Let the differential diagnosis begin.
The snapping behavior is caused by incorrectly used randomInt(5), within a composable. List generation should be inside the viewmodel then there is no regeneration of the list during scrolling and everything works perfectly.
Also using detectVerticalDragGestures does not work as smoothly as my original suggestion of using modfiier.scrollable(...) but maybe that's a desired effect.

Compose: Why does a list initiated with "remember" trigger differently to Snapshot

I've been messing around with Jetpack Compose and currently looking at different ways of creating/managing/updating State.
The full code I'm referencing is on my github
I have made a list a piece of state 3 different ways and noticed differences in behavior. When the first list button is pressed, it causes all 3 buttons to be recomposed. When either of the other 2 lists are clicked though they log that the list has changed size, update their UI but trigger no recompose of the buttons ?
To clarify my question, why is that when I press the button for the firsList I get the following log messages, along with size updates:
Drawing first DO list button
Drawing List button
Drawing second DO list button
Drawing List button
Drawing third DO list button
Drawing List button
But when I press the buttons for the other 2 lists I only get the size update log messages ?
Size of list is now: 2
Size of list is now: 2
var firstList by remember{mutableStateOf(listOf("a"))}
val secondList: SnapshotStateList<String> = remember{ mutableStateListOf("a") }
val thirdList: MutableList<String> = remember{mutableStateListOf("a")}
Row(...) {
println("Drawing first DO list button")
ListButton(list = firstList){
firstList = firstList.plus("b")
}
println("Drawing second DO list button")
ListButton(list = secondList){
secondList.add("b")
}
println("Drawing third DO list button")
ListButton(list = thirdList){
thirdList.add("b")
}
}
When I click the button, it adds to the list and displays a value. I log what is being re-composed to help see what is happening.
#Composable
fun ListButton(modifier: Modifier = Modifier,list: List<String>, add: () -> Unit) {
println("Drawing List button")
Button(...,
onClick = {
add()
println("Size of list is now: ${list.size}")
}) {
Column(...) {
Text(text = "List button !")
Text(text = AllAboutStateUtil.alphabet[list.size-1])
}
}
}
I'd appreciate if someone could point me at the right area to look so I can understand this. Thank you for taking the time.
I'm no expert (Well,), but this clearly related to the mutability of the lists in concern. You see, Kotlin treats mutable and immutable lists differently (the reason why ListOf<T> offers no add/delete methods), which means they fundamentally differ in their functionality.
In your first case, your are using the immutable listOf(), which once created, cannot be modified. So, the plus must technically be creating a new list under the hood.
Now, since you are declaring the immutable list in the scope of the parent Composable, when you call plus on it, a new list is created, triggering recompositions in the entire Composable. This is because, as mentioned earlier, you are reading the variable inside the parent Composable's scope, which makes Compose figure that the entire Composable needs to reflect changes in that list object. Hence, the recompositions.
On the other hand, the type of list you use in the other two approaches is a SnapshotStateList<T>, specifically designed for list operations in Compose. Now, when you call its add, or other methods that alter its contents, a new object is not created, but a recomposition signal is sent out (this is not literal, just a way for you to understand). The way internals of recomposition work, SnapshotStateList<T> is designed to only trigger recompositions when an actual content-altering operation takes place, AND when some Composable is reading it's content. Hence, the only place where it triggered a recomposition was the list button that was reading the list size, for logging purposes.
In short, first approach triggers complete recompositions since it uses an immutable list which is re-created upon modification and hence the entire Composable is notified that something it is reading has changed. On the other hand, the other two approaches use the "correct" type of lists, making them behave as expected, i.e., only the direct readers of their CONTENT are notified, and that too, when the content (elements of the list) actually changes.
Clear?
EDIT:
EXPLANATION/CORRECTION OF BELOW PROPOSED THEORIES:
You didn't mention MutableListDos in your code, but I'm guessing it is the direct parent of the code you provided. So, no, your theory is not entirely correct, as in the immutable list is not being read in the lambda (only), but the moment and the exact scope where you are declaring it, you send the message that this value is being read then and there. Hence, even if you removed the lambda (and modified it from somewhere else somehow), it will still trigger the recompositions. The Row still does have a Composable scope, i.e., it is well able to undergo independent recompositions, but the variable itself is being declared (and hence read) in the parent Composable, outside the scope of the Row, it causes a recomp on the entire parent, not just the Row Composable.
I hope we're clear now.

KOTLIN Why when i changed a List it is also accidentally changing another list

Hello i want to know why is my program changing selectedDataEdited List when i only changing editTransactionList ?
var editTransactionList: MutableList<Transaction>? = mutableListOf()
var selectedDataEdited: List<Transaction>? = listOf()
editTransactionList = listTest as MutableList<Transaction>
selectedDataEdited = listTest
var position = 0
println("edit $editTransactionList")
println("select $selectedDataEdited")
editTransactionList.get(position).apply {
amount = 2000
name = "K"
}
println("edit $editTransactionList")
println("select $selectedDataEdited")
editTransactionList.get(position).apply {
amount = 3000
name = "Z"
}
println("edit $editTransactionList")
println("select $selectedDataEdited")
the output is
edit [Transaction(amount=1000, name=T, test=1)]
select [Transaction(amount=1000, name=T, test=1)]
edit [Transaction(amount=2000, name=K, test=1)]
select [Transaction(amount=2000, name=K, test=1)]
edit [Transaction(amount=3000, name=Z, test=1)]
select [Transaction(amount=3000, name=Z, test=1)]
Variables are basically references. When you store an object in a variable you actually say "when using this variable please refer to this object". So if you "store" the same object into 2 different variables, each of them still refers to that same object. Getting the object using the first variable, making changes to it, and then accessing the second variable, will still get you that changed object.
You will need to copy the list to prevent the unwanted behavior. Keep in mind though that you would probably need a deep copy. Simply calling toList() on it for example only makes a shallow copy, which means that even though it will be a different list, the objects inside it will still refer to the original.
It's hard to tell what would work without knowing what Transaction looks like. If Transaction is a data class then selectedDataEdited = listTest.map { it.copy() } might work. See this example https://pl.kotl.in/Q_o8pYXVs
KOTLIN Why when i changed a List it is also accidentally changing another list
Because you don't have "another" list. You only have one list.
When you do selectedDataEdited = listTest, you assign a second reference to the same list. If you want two separate lists, you must create them, possibly by cloning the original list.
Instead of using as MutableList use toMutableList:
editTransactionList = listTest.toMutableList()
It will make a copy of your list instead of passing a reference to the same list.

React Native: Change Component (Text) on Navigator Pop

I'm creating a Component/View that has a list of things to select from similar to a table view.
When I'm in View A I can jump to this table view by doing a navigator push. Once an item is selected I do a pop. However, I'd like a text component in View A to be updated with the appropriate value.
I was thinking of passing a reference to this text component but it doesn't sound right. Any other ways I could achieve that?
I couldn't find any table view that would work on both platforms, let me know if you have any good suggestions.
The React way to achieve this is to pass a callback function to a named attribute, let's say in this case onSelect.
MyParentComponent extends Component {
...
MySelectHandler(value) {
this.setState({
valueSelected: value
});
},
...
}
Creating your picker component:
<MyCustomTableView onSelect={this.MySelectHandler}/>
And in your component:
MyCustomTableView extends Component {
...
onValueSelected(value) {
this.props.onSelect(value);
}
...
}
Considering your "re-render" problem, React only updates Component who have changed depending on the previous state. As your inputs probably don't depend on the state, they don't get updated because nothing changed for them.
If you want to clear all your inputs when the valueSelected key state gets updated, you can use the lifecycle method componentDidUpdate(), and manually clear your inputs after each value update.
// In MyParentComponent
componentDidUpdate(prevProps, prevState) {
if (prevState.valueSelected !== this.state.valueSelected)
this.refs.myInput.value = ''; // Do this for each input, you'll need to add a unique ref attribute for each one
}
...
I solved it by passing {this} to the table view as a prop and calling this.props.parent.setState({selection: selectedThing}) in the table view.
I thought setState would re-render the whole view and get rid of any user inputs but it seems to only re-render changed components

How to alter the active text field in AS3?

I have 6 text fields on the stage along with a keypad with the numbers 0-9.
Ultimately this is going to be an Android app. And I want to be able the select a text field and then press the keypad buttons and have the numbers appear in the active text field.
I've been trying to Google active field and similar searches and can't seem to find a reference.
Keep in mind I'm fumbling around in the dark from what I've tried to gather from multiple tutorials. This code is probably complete garbage:
package {
public class main {
import flash.display.MovieClip;
import flash.events.MouseEvent;
import fl.managers.FocusManager;
var focus:FocusManager = new FocusManager(this);
btn_1.addEventListener(MouseEvent.MOUSE_DOWN, inputNumber);
public function inputNumber(m:MouseEvent){
focus.text = 1;
}
public function main() {
// constructor code
}
}
}
Current Errors:
C:\Users\Otaku\Documents\lottocount\main.as, Line 19, Column 35 1118: Implicit coercion of a value with static type flash.display:InteractiveObject to a possibly unrelated type flash.text:TextField.
C:\Users\Otaku\Documents\lottocount\main.as, Line 14, Column 44 1120: Access of undefined property onMyButtonClick.
C:\Users\Otaku\Documents\lottocount\main.as, Line 14, Column 3 1120: Access of undefined property btn_1.
C:\Users\Otaku\Documents\lottocount\main.as, Line 13, Column 47 1120: Access of undefined property onFocusIn.
C:\Users\Otaku\Documents\lottocount\main.as, Line 13, Column 3 1120: Access of undefined property stage.
If you access the focus object, you'll have a reference to your currently active (focused on) object.
But in reality, just having a TextField that allows input will open a keyboard view (only on mobile) on touching it.
You can also tell Flash not to automatically show the keyboard, if you plan to use your own, then you can use the focus or make your own system that would track the appropriate TextField depending on where the last touch was.
EDIT:
Here's a simplistic example of how it could be done.
In this example I have two TextFields: tf1 and tf2. Also I've added a button for the sake of covering a situation where you need to save the last focus on a concrete type of object. The buttons name is myButton.
var lastSelectedTextField:TextField; // this will hold our last selected TextField
// make sure stage exists. If you're writing script in a frame, don't mind, if you use OOP approach you can do so by adding an eventListener for the event ADDED_TO_STAGE
stage.addEventListener(FocusEvent.FOCUS_IN, onFocusIn); // onFocusIn will trigger every time a focus is changed.
myButton.addEventListener(MouseEvent.CLICK, onMyButtonClick);
function onFocusIn(e:FocusEvent):void
{
if (stage.focus is TextField)
lastSelectedTextField = stage.focus as TextField;
}
function onMyButtonClick(e:MouseEvent):void
{
trace("Text of the last selected text field is:", lastSelectedTextField.text);
}

Categories

Resources