galex
07/19/2020, 4:13 PMMark Murphy
07/19/2020, 4:19 PMCalculatorState
be a data class
with val
properties, then use copy()
to create the mutated object that you use to update calculatorState
Or, you could have the individual properties of CalculatorState
be mutableStateOf()
property delegates.
Those correspond to alternatives 1 and 2 for the replacement of @Model
as noted in this Gerrit entry: https://android-review.googlesource.com/c/platform/frameworks/support/+/1311293/Adam Powell
07/19/2020, 4:30 PMAdam Powell
07/19/2020, 4:33 PMMutableState<CalculatorState>
, great, but if you make CalculatorState
mutable by way of mutableStateOf
property delegation, don't use State<CalculatorState>
to hold itAdam Powell
07/19/2020, 4:33 PMvar list: List<T>
or val list: MutableList<T>
but not var list: MutableList<T>
Adam Powell
07/19/2020, 4:34 PMgalex
07/19/2020, 4:49 PMmutableStateOf()
for its properties. Using copy
feels less clean memory-wise, but a good option if the object is not mine (ex: 3rd library)Adam Powell
07/19/2020, 4:56 PMmutableStateOf
creates a mutable box, and it does so with generics so anything you put in it is boxed as well. (We may do some additional optimizations around that last part later.) We also track historical values of MutableState
objects to some extent in the form of the snapshot records. Consider the API ergonomics you're trying to create here first and please report back if you experience issues and we'll see what we can do to optimize.Adam Powell
07/19/2020, 4:59 PM@Composable
fun CalculatorScreen(
calculatorState: CalculatorState = remember { CalculatorState() }
) {
// ...
Adam Powell
07/19/2020, 5:01 PMCalculatorScreen
be self-sufficient in the simple case, but a caller that wants to also manipulate its CalculatorState
via its public API can do so. This is also great for testability, since it means that CalculatorScreen
itself is stateless and you can pass it any configuration you like to control and test its behavior.Adam Powell
07/19/2020, 5:02 PMCalculatorState
object becomes a lot more testable, so you can decouple testing of your logic (in CalculatorState
) from testing the UI presentation itselfgalex
07/19/2020, 5:12 PMremember
! I haven’t gotten to testing yet, but it seems a thousand years easier than with the old UI framework.galex
07/19/2020, 5:13 PMmutableStateOf
but now it makes sense 🙂galex
07/20/2020, 5:45 AMCalculatorState
in a junit simple test (not AndroidTest) and could not:
java.lang.IllegalStateException: Not in a frame
at androidx.compose.frames.FramesKt.currentFrame(Frames.kt:180)
at androidx.compose.FramedMutableState.setValue(MutableState.kt:296)
at il.co.galex.alexpizzapp.feature.calculator.CalculatorState.setNumberOfBalls(CalculatorState.kt:14)
at il.co.galex.alexpizzapp.feature.calculator.CalculatorTest.fourPizzasOf280(CalculatorTest.kt:22)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
So I am bound to test this code as an AndroidTest? In this case a simpler object using copy
would be easier to test, right?Adam Powell
07/20/2020, 2:18 PMAdam Powell
07/20/2020, 2:19 PMgalex
07/20/2020, 5:09 PM