What’s the best way to extract state value from a ...
# compose
v
What’s the best way to extract state value from a reused component? Something like
Copy code
fun Screen() {
  Entry()
  Entry()
  Entry()
  Entry()
  //Which entry value is smallest number on screen
}

fun Entry() {
  TextField()
}
a
What's the end goal?
v
Just a simple golf scoring project I’m making. I want to know which of the four entries has the lowest, and which has the highest score for that screen.
a
Using your UI as data isn't the most sound approach
Your data should be informing your UI
You should NEVER use your UI as a source of truth
v
…then how do enter data?
The whole idea is to enter four numbers from the user and find the smallest and the largest
a
v
I’ve read it
Entry()
is supposed to a be a composable that has multiple customizations not just
TextField
so that I don’t have to copy paste them 4 times to keep in a single method.
It sounds like your recommendation is to simply create a ViewModel and pass it to the method and track it that way which…fine but I was hoping there was a way to extract it out so I knew which of the four entries (1-4) was highest and lowest more easily.
a
If entry is meant to house the textfield logic, then keep it as dumb as possible and don't store state there, bubble up any events to be stored elsewhere
If this is screen level state, it would make sense to store it ina viewmodel
v
Yes but I was hoping there was an easier way to extract the state of entry without passing it a view model
it seems there’s not.
a
If you are opposed to using a viewmodel, it can be stored inside the parent component
Your parent component should know the most (or be informed of the most via a viewmodel, then your child components should know less and less
v
I get that
a
This sounds more like an architecture problem, no?
v
I took OOP 101 too
That’s what I’m asking
a
(I'm not trying to be a dick or anything, the example just looked vague 😅)
v
I know you’re not, I’m sorry. I’m just tired and being grumpy.
a
You're good!
Just wanted to make sure I wasn't coming across as such
v
In old Android I could create a view that had say an RxJava and “listen” for the inputs out.
a
So In this case, your entries, which looks like your text inputs would send up an event when they change
v
Nah you’re good, it’s just me furstrated with the declarative nature of Compose and not presenting enough base knowledge to skip the basics.
a
And that would be passed in as a parameter in your Entry to be called by the input
v
Yea, I was hoping there was a way to avoid the parameter passing, decoupling the view entry from the parent view like I could with Rx
a
Ah, yeah, passing down a parameter to bubble up your state is the best way to go to keep things generic
v
It seems that’s not really possible, short of a global variable. It just means that I that have to track an extra variable for which entry point was used. It’s icky to me cause I love my modular things.
a
Otherwise, having a viewmodel be your state container, and modifying it directly
v
Yea basically, I gotta pass the view model down and track all four values in it. Not the answer wanted but what I was looking for.
Thanks for the convo and sorry for being chippy, honestly. Nothing you did really just me in a mood.
a
You're all good! Sometimes I'm not aware off I come off a certain way 😅
t
Why cant he just use a remember derivedState for the minimum? Long term, maybe a view model makes sense, maybe not. I think at the learning/prototyping stage, it’s better to get away with as much as you can using as little as possible. Less is more
v
So derivedState isn’t something I’m familiar with?
I like atypical structures
a
derrivedStateOf is used when one state is computed from another
v
Interesting looking at the docs…what’s the difference between that and combine?
Still not quite what I’d hoped to achieve. Gonna just have to pass a view model and track each of the four entries and combine them and stuff which isn’t my fave.
a
Yeah, unidirectional data flow + events bubbling up (your view is a function of state, not the case here since your views are emitted from state, applies a lot here) is the defacto
t
I’m not at a computer, on my phone. But basically, do something like this at the Screen level. var values = remember { mutableStateList() } var minimum by remember { derivedStateOf { values.minimum() } } Now modify your Entry to take a value argument and an onChange argument which is a closure.
I’ll check back in tomorrow morning. derivedState is one of the coolest things about compose, and it’s really more about the snapshot system that compose leverages
v
Yea I get that but it used be I could do say
Copy code
fun Screen() {
  Entry().value().observe{}
  Entry().value().observe{}
}

class Entry() {
  fun value(): Observable {
    Observable.just(1)
  }
}
With Rx so my view didn’t need a view model to render and could just “publish” it’s results for processing to anyone listening. I was hoping to find a way to do it without pushing a view model in to each view that way. Total IOC and purely agnostic. Passing
Copy code
EntryModel : ViewModel() { 
entry1 = mutableStateOf(1) 
entry2 = mutableStateOf(2)...
}
And pass that is my best bet
But glad I asked; now I know about derivedState. Thanks y’all. Sorry for being a dick 😞
a
You weren't a dick! Don't worry about it!
Lots are learning here ☺️
t
Agreed! The ones who are dicks, are the ones who I work with, who will not ask questions in forums or any other format.
v
Do you people end up sharing commits/PR’s with these kinds of questions on their personal projects to learn?
a
For me, it's more so looking around, digging for info, trial and error, and if all else fails, drop a question here, especially since the SMEs from Google are here and know portions of the implementation
t
How did you make out @Vince? I threw this together now that I'm in front of a keyboard.
Copy code
@Composable
fun DerivedDemoScreen() {
   var foodsToPack = remember {
      mutableStateListOf<String>(
         "Dr Pepper",
         "Clif Bar",
         "Something Fowl",
         "Healthy Chips",
         "More Dr Pepper"
      )
   }
   val aMouthfulToSay by remember { derivedStateOf { foodsToPack.maxBy { foodName -> foodName.length } } }
   val whatWasThat by remember { derivedStateOf { foodsToPack.minBy { foodName -> foodName.length } } }
   Column {
      LazyColumn(
         modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally
      ) {
         itemsIndexed(items = foodsToPack) { index, foodName ->
            Entry(value = foodName,
               modifier = Modifier.fillMaxWidth(),
               onChange = { updated -> foodsToPack[index] = updated })
         }
      }
      Text(text = "Longest: $aMouthfulToSay")
      Text(text = "Shortest: $whatWasThat")
   }
}

@Composable
private fun Entry(
   value: String,
   modifier: Modifier = Modifier,
   onChange: UnaryAction<String> = { _ -> }
) {
   TextField(value = value, onValueChange = onChange, modifier = modifier)
}

@Preview
@Composable
private fun DerivedDemosScreenPreview() {
   StatusDemoTheme {
      DerivedDemoScreen()
   }
}
I'm happy to explain my "take" on how to move to this stuff, if you're still banging away at it.
v
Actually just now sitting down to do it. What I’m going to end up with is essentially
Copy code
class HoleScoreViewModel : ViewModel() {

  private var _highScore: MutableLiveData<Int> = MutableLiveData<Int>(0)
  private var _lowScore: MutableLiveData<Int> = MutableLiveData<Int>(0)

  private var player1Score: MutableLiveData<Int> = MutableLiveData(0)
  private var player2Score: MutableLiveData<Int> = MutableLiveData(0)
  private var player3Score: MutableLiveData<Int> = MutableLiveData(0)
  private var player4Score: MutableLiveData<Int> = MutableLiveData(0)
  /* score setters for each entry field */
  fun getHighScore(): Int { 
   //return player number with highest score
  }
}

@Composable
fun EnterScore(matchViewModel: MatchViewModel, navController: NavHostController) {
  val scoreViewModel: HoleScoreViewModel = HoleScoreViewModel()

  Column(
    modifier = Modifier.padding(12.dp),
    verticalArrangement = Arrangement.Center
  ) {

   PlayerScore() // times four
   Button(modifier = Modifier
      .fillMaxWidth()
      .height(48.dp),
           content = {
               Text(text = "Next Hole")
           },
           onClick = {
             //TODO do actual point entries
             //val playerScore = scoreViewModel.getHighScore()
             //matchViewModel.updateMatchScore(playerScore)
           })
    }
}

@Composable
fun PlayerScore() {
//TODO add a lambda for updating the player score?
 Row(
    modifier = Modifier
      .background(background),
    verticalAlignment = Alignment.CenterVertically,
  ) {
    OutlinedTextField(
      value = score.value,
      label = { Text("Score") },
      onValueChange = {
        if (it.isEmpty()) {
          score.value = ""
        } else if (it.isDigitsOnly() && it.toInt() <= 9) {
          score.value = it
          //use lambda to set player score on scoreViewModel
        }
      },
      keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
    )
}
I was hoping I could just pull the value of the TextField entry on button press, and know which player had the highest (and lowest) number. It seems I need to store that in a score view model for each hole, then report that back to a match view model that will live for the entire match. Some of this was a bit easier with XML when I built it but it was admittedly less clean and robust.
Thanks for the help and follow up @Travis Griggs
t
Best of luck. My little snippet was to demonstrate that you don't need a view model to do this. Which doesn't mean a ViewModel is a bad thing. It's a common end game evolution in UI code. But for the learner, I think it can just create one more "tool on the bench that I have to learn how to use before I can make things in this woodshop". Glad you've found some confidence and are off the races with something that's working for you.
v
Yea that makes sense