COMP 1601 2022W Assignment 3 Solutions 1. [2] How does the syntax for dictionaries in Swift compare to mutable maps in Kotlin? How similar are they? A: A dictionary in Swift is specified with square brackets, with the keys and values being separated by colons: let analysis: [String: (String) -> String] = [ "Upper Case": countUpper, "Pets Mentioned": countPetsMentioned ] In contrast, in Kotlin, we have to say mutableMapOf (or, mapOf) and then the entries are inside of parentheses and keys and values separated by the word "to". val analysis: MutableMap> = mutableMapOf( "Upper Case" to ::countUpper, "Pets Mentioned" to ::countPetsMentioned ) 2. [2] How can you change the code so that, when rotated, the mode is never "None" and if it was, it is changed to "Count"? Other modes should be preserved, and when the app is started it should behave as before. Explain why your change works. A: We just have to change the onCreate so that when there is a valid savedMode, we check for whether analysisMode is "None". Specifically, we add this code between lines 34 and 35 (after the analysisMode = savedMode line): if (analysisMode == "None") { analysisMode = "Count" } Because we won't have a saved mode when the app starts, this code won't be triggered. Afterwards, there will be a saved mode, and if it was None it will be changed to Count. (You could also do this check elsewhere, but this is the easiest place to make sure it doesn't happen when the app starts but happens otherwise.) 3. [2] How can you change the app so that when you switch to another app and then return, the result area shows "Welcome Back!"? Explain why your change works. A: When we switch to another app, and then return, the app is paused and then resumes. So, we need to add appropriate logic to the onPause() and OnResume(). We also need to change onCreate() so we can be sure that doesn't register as a pause because onResume() is also called when the app is first started. First, add a state variable to MainActivity, say at line 24: var wasPaused = false Add "wasPaused = false" at the end of onCreate() because if we're creating the activity, we haven't been paused. Add "wasPaused = true" to onPause() because then, we've been paused. And finally, add the following to onResume(): if (wasPaused) { analysisResult.setText("Welcome Back!") } 4. [1] How can you fix the results area so it is horizontally centered, as it was before? Add the following to the analysisResult TextView (say between lines 41 and 42 activity_main.xml): app:layout_constraintEnd_toEndOf="parent" This was present previously. 5. [2] How can you move the menu button back to the top? Why do your changes work? In activity_main.xml, analysisMenuButton is currently positioned between analysisResult and the bottom of the screen. We should change it to be between the top of the screen and appTitle, and we have to fix analysisResult so it is anchored to the bottom of the screen and appTitle to be anchored to the bottom of analysisMenuButton. We make these specific changes. First, to analysisMenuButton, change app:layout_constraintTop_toBottomOf="@+id/analysisResult" app:layout_constraintBottom_toBottomOf="parent" to app:layout_constraintTop_toTopOf="parent" (We don't want the bottom constraint because we want the button at the very top of the screen.) Then, change appTitle from app:layout_constraintTop_toTopOf="parent" to app:layout_constraintTop_toBottomOf="@+id/analysisMenuButton" And change analysisResult from app:layout_constraintBottom_toTopOf="@+id/analysisMenuButton" to app:layout_constraintBottom_toBottomOf="parent" 6. [2] In the constraints for the menu button, there in a specifier for marginTop (line 49 of activity_main.xml). In the original layout, does this specifier affect the app's appearance? What about once the menu button is moved to the top of the screen, does it change the app's appearance? A: In the original layout, it does slightly lower the position of the button but it is difficult to tell. In the new layout the difference is very noticable because without it the button is at the very top of the screen, and with it there is a reasonable margin at the top (just 26 display pixels but that makes a difference. 7. [2] What is the twatcher object for? What provides the same functionality in SwiftUI? A: The twatcher is there to make sure updateAnalysis() is called every time the user types in the text entry box t. In SwiftUI we can get the same functionality by designating the text field variable as an @State variable, as any changes to such variables cause any referencing views to automatically be redrawn as needed - no explicit update call is needed. 8. [2] If we were to add a new entry to the analysis dictionary (mutable map) in the onResume method, would this new entry show up in the menu? Why or why not? Explain. A: Yes it would, because the menu is built from scratch whenever the menu button is pressed. We see this because the analysisButtonMenu has showMenu() as the onClick handler (in activity_main.xml) and showMenu() creates a new PopupMenu and adds entries to it based on the keys in the analysis mutable map. 9. [5] How could you change the program so that it no longer used the analysis dictionary? * [1] Change it so that instead of the mutable map, it use an array of strings to generate the menu items. * [2] Make updateAnalysis() either call the analysis functions explicitly or incorporate their code directly. * [1] How does your code handle the "None" case? How does it compare to the original version? * [1] Which version of the code do you like better? Why? To use an array, we first, before showMenu we define a new array, modes: var modes = arrayOf("Count", "Upper Case", "Empty", "Pets Mentioned", "None") We then change the for loop in showMenu and subsequent add from for (s in analysis.keys) { menu.menu.add(s) } menu.menu.add("None") To for (s in modes) { menu.menu.add(s) } Then, we change updateAnalysis so it directly computes the results based on the current analysis mode. The new function is much longer as it incorporates the functionality of the separate four functions. Almost all of the code has been replaced because we're no longer looking up functions in analysis and then calling them: fun updateAnalysis() { val inputText: Editable? = t.text var result = "" var count = 0 if (inputText == null) { // This should never be true return } val s = inputText.toString() if (analysisMode == "Count") { val charcount = s.length result = charcount.toString() } else if (analysisMode == "Upper Case") { val upperCase = setOf("A","B","C","D","E","F","G","H","I","J","K","L","M", "N","O","P","Q","R","S","T","U","V","W","X","Y","Z") for (c in s) { if (upperCase.contains(c.toString())) { count += 1 } } result = count.toString() } else if (analysisMode == "Empty") { if (s == "") result = "Yes" else result = "No" } else if (analysisMode == "Pets Mentioned") { val pets = setOf("Roshi", "Tab", "Shift") for (p in pets) { if (s.contains(p)) { count += 1 } } result = count.toString() } if (result != "") { analysisResult.setText(analysisMode + ": " + result) } else { analysisResult.setText("Please choose an analysis mode.") } } Note the above code handles "None" essentially as before - if our chain of if statements doesn't find the current mode, we change the result to "Please choose..." as before. If analysisMode is anything except the four explicitly defined here, it will also display this text. The only difference is instead of the lookup failing into a map, we're not finding a match in a series of string matches. (A lookup in a map would generally be faster than this set of if statements, but I am pretty sure it would be very hard to detect the difference in a benchmark in this case.) I personally like the older version because 1) looking things up in a map/dictionary is inherently scalable, you can have hundreds or thousands of options and it should still run at the same speed, and 2) the code is easier to work with because we just have to change a data structure to add functionality and logically different functionality is in different functions. (We could have had different functions here, but when adding a new menu option we'd also have to update the cascading if statements, which I think is more annoying than changing a data structure.) (If you have a reasonable argument/perspective other answers are acceptable!)