====== Pathway 3: Interact with UI and state ====== https://developer.android.com/courses/pathways/android-basics-compose-unit-2-pathway-3 This is a part of [[Android Basics with Compose]] course. ===== 1. Understanding state in Compose ===== Optional video. var input by remember { mutableStateOf("") } * State can be saved: * locally * in the UI element * outside * if it is needed by other UI elements * this is named "state hoisting" ===== 2. Intro to tip calculator ===== ... Tip = Gratuity (Trinkgeld in DE) ===== 3. Intro to state in Compose ===== ==== 3.1 Before you begin ==== ... ==== 3.2 Get started ==== ... ==== 3.3 Get Starter Code ==== * clone repo and switch to ''starter'' branch * add ''TextField'' to the ''TipTimeLayout'' in ''MainActivity'' ==== 3.4 Use state in Compose ==== ... ==== 3.5 The Composition ==== * If a state change happens, Compose re-executes the affected composable functions with the new state, which creates an updated UI—this is called //recomposition//. Compose schedules a recomposition for you. * You use the ''State'' and ''MutableState'' types in Compose to make state in your app observable, or tracked, by Compose. The ''State'' type is immutable, so you can only read the value in it, while the ''MutableState'' type is mutable. You can use the ''mutableStateOf()'' function to create an observable ''MutableState''. It receives an initial value as a parameter that is wrapped in a State object, which then makes its value observable. * ... ==== 3.6 Use remember function to save state ==== ... @Composable fun EditNumberField(modifier: Modifier = Modifier) { var amountInput by remember { mutableStateOf("") } TextField( value = amountInput, onValueChange = { amountInput = it }, modifier = modifier ) } * Adding the delegate's getter and setter imports lets you read and set ''amountInput'' without referring to the MutableState's value property. ... ==== 3.7 State and recomposition in action ==== ... ==== 3.8 Modify the appearance ==== @Composable fun EditNumberField(modifier: Modifier = Modifier) { var amountInput by remember { mutableStateOf("") } TextField( value = amountInput, onValueChange = { amountInput = it }, singleLine = true, label = { Text(stringResource(R.string.bill_amount)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = modifier ) } ==== 3.9 Display the tip amount ==== ==== 3.10 State hoisting ==== * The state (contained in the variable ''amountInput'') will be moved from ''EditNumberField'' one level up => to the ''TipTimeLayout''. @Composable fun TipTimeLayout() { var amountInput by remember { mutableStateOf("") } val amount = amountInput.toDoubleOrNull() ?: 0.0 val tip = calculateTip(amount) Column( modifier = Modifier .statusBarsPadding() .padding(horizontal = 40.dp) .verticalScroll(rememberScrollState()) .safeDrawingPadding(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text( text = stringResource(R.string.calculate_tip), modifier = Modifier .padding(bottom = 16.dp, top = 40.dp) .align(alignment = Alignment.Start) ) EditNumberField( value = amountInput, onValueChange = { amountInput = it }, modifier = Modifier .padding(bottom = 32.dp) .fillMaxWidth() ) Text( text = stringResource(R.string.tip_amount, tip), style = MaterialTheme.typography.displaySmall ) Spacer(modifier = Modifier.height(150.dp)) } } @Composable fun EditNumberField( value: String, onValueChange: (String) -> Unit, modifier: Modifier = Modifier ) { TextField( value = value, onValueChange = onValueChange, singleLine = true, label = { Text(stringResource(R.string.bill_amount)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = modifier ) } ==== 3.11 Get the solution code ==== ... ==== 3.12 Conclusion ==== * Summary * //State// in an app is any value that can change over time. * The //Composition// is a description of the UI built by Compose when it executes composables. Compose apps call composable functions to transform data into UI. * //Initial composition// is a creation of the UI by Compose when it executes composable functions the first time. * //Recomposition// is the process of running the same composables again to update the tree when their data changes. * //State hoisting// is a pattern of moving state to its caller to make a component stateless. ===== 4. Calculate a custom tip ===== ==== 4.1. Before you begin ==== ... ==== 4.2 Get the starter code ==== ... ==== 4.3 Starter app overview ==== ... ==== 4.4 Add a tip-percentage text field ==== ... * add ''label'' parameter to the ''EditNumberField'' ... * ''@StringRes'' annotation is a type-safe way to use string resources. ... ==== 4.5 Set an action button ==== ... keyboardOptions = KeyboardOptions.Default.copy( keyboardType = KeyboardType.Number, imeAction = ImeAction.Next ), ==== 4.5 Add a switch ==== ... Switch( checked = roundUp, onCheckedChange = onRoundUpChanged, modifier = modifier .fillMaxWidth() .wrapContentWidth(Alignment.End), ) ... ==== 4.7 Add support for landscape orientation ==== ... @Composable fun TipTimeLayout() { // ... Column( modifier = Modifier .padding(40.dp) .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { //... } } ==== 4.8 Add leading icon to text fields (optional) ==== ... TextField( value = value, leadingIcon = { Icon(painter = painterResource(id = leadingIcon), null) }, //... ) ==== 4.9 Get the solution code ==== ... ==== 4.10 Conclusion ==== ... ===== 5. Write automated tests ===== ==== 5.1 Before you begin ==== ... ==== 5.2 Get the starter code ==== ... ==== 5.3 Automated tests ==== * Type of automated tests * Local tests * Local tests are a type of automated test that directly test a small piece of code to ensure that it functions properly. With local tests, you can test functions, classes, and properties. * Local tests are executed on your workstation, which means they run in a development environment without the need for a device or emulator. * Instrumentation tests * An instrumentation test is a UI test. * When you run an instrumentation test on Android, the test code is actually built into its own Android Application Package (APK) like a regular Android app. ... * ''@VisibleForTesting'' annotation ... ... ==== 5.4 Write a local test ==== Local tests directly test methods from the app code, so the methods to be tested must be available to the testing classes and methods. So: * => designate ''private'' methods as ''internal'' * add ''@VisibleForTesting'' annotation * this makes the method public, but only for testing purposes The following list of concepts apply to local and instrumentation tests: * Write automated tests in the form of methods. * Annotate the method with the @Test annotation. This lets the compiler know that the method is a test method and runs the method accordingly. * Ensure that the name clearly describes what the test tests for and what the expected result is. * Test methods don't use logic like regular app methods. They aren't concerned with how something is implemented. They strictly check an expected output for a given input. That is to say, test methods only execute a set of instructions to assert that an app's UI or logic functions correctly. You don't need to understand what this means yet because you see what this looks like later, but remember that test code may look quite different from the app code that you're used to. * Tests typically end with an assertion, which is used to ensure that a given condition is met. Assertions come in the form of a method call that has assert in its name. For example: the ''assertTrue()'' assertion is commonly used in Android testing. ... ==== 5.5 Write an instrumentation test ==== * usually located in ''src/androidTest/java'' ... ==== 5.6 Get the solution code ==== ... ==== 5.7 Conclusion ==== Summary: * What automated tests are. * Why automated tests are important. * The difference between local tests and instrumentation tests * Fundamental best practices for writing automated tests. * Where to find and place local and instrumentation test classes in an Android project. * How to create a test method. * How to create local and instrumentation test classes. * How to make assertions in local and instrumentation tests. * How to use test rules. * How to use ''ComposeTestRule'' to launch the app with a test. * How to interact with composables in an instrumentation test. * How to run tests. ===== 6. Project: Create an Art Space app ===== ==== 6.1 Before you begin ==== ... ==== 6.2 Build static UI with composables ==== ... ==== 6.3 Make the app interactive ==== ... ==== 6.4 Challenge: Build for different screen sizes ==== ... ==== 6.5 Good luck! ==== ... ===== 7. What's next? ===== ...