Welcome, Guest User :: Click here to login

Logo 67443

Lab 2A: TempConverter (Android)

Due Date: September 15

Objectives

  • extend experience building Android apps
  • do some minor customization of the app interface
  • provide a simple intro to activities and fragments
  • reinforce Kotlin programming knowledge

README.md

Temp Converter

This lab will build off our TempConverter that we have been working with in 67-272 and in the previous lab and turn it into a working Android app. The app will take some user input, convert it (if valid) from either Celsius to Fahrenheit or Fahrenheit to Celsius. There will also be a toggle switch to change the direction of the conversion and a simple about page describing your app. A sample screenshot of the iOS version can be seen below. (Feel free to use different colors)

  1. Create a new project which is an "Empty Views Activity" and call it "TempConverterApp". After you have the basic project, add a new Kotlin file called TempConverter.kt to hold your model. At the end of the lab is a partial model file if you are completely stuck and need a jump start to get going, but given your previous experience you can probably figure out an appropriate model on your own and I strongly urge you to try as it will maximize your learning experience.

    The model should be able to convert Fahrenheit temps to Celsius and likewise convert Celsius to Fahrenheit if the user wants to go the other way. The model should be sure to have a variable which will interact with the Activity class to display the temperature as a string. This variable should either be the final temperature after conversion, or should display 'N/A' if the temperature the user inputs is invalid because it is either below absolute zero or is not a number. Check the code at the bottom of the lab for a better idea on this (using the variable convertedTempDisplay).

After building up your model, check out the boilerplate code at the bottom of the instructions. This time, we are separating out the functions for Fahrenheit to Celsius and Celsius to Fahrenheit conversions, which allows for fewer conditionals in our code (and a bit more readable logic). You don't have to implement your model in this fashion, but at least take a look to get a sense of the differences in architecture between this model and the one we wrote last week.

  1. For now we will just assume all temp conversions are from Fahrenheit to Celsius (will add the switch later) but we want to be mindful when we build the layout that the switch is coming and labels will have to adjust. Fill in your layout with the appropriate TextViews and Buttons.

You will need to setup the constraints as we did in the last lab to ensure that your widgets are positioned relative to the screen. Remember one quick way is to drag the white circles around widgets to the edges of the screen or other widgets.

For you text field, you have multiple options, but the TextInputLayout is the most comprehensive. Use that and add the following to your xml android:hint="@string/strInput" to set the hint prompt to "Enter Temp". Add the matching strings into the res/values/strings.xml file. There is also a suffixText property you can make use of for the end unit.

Set the text field so that the input (which will be numerical) is right-aligned. Note that the final temp display is actually two different labels -- one which will show the numeric value and the other to show the units it's being converted to. Later the units in that label will toggle when we hit a switch, so better to keep them separate from the start.

Adjust the view background to some color. Figure out your favorite color and add it to the strings.xml file like so <color name="cardinal">#C41E3A</color> then change the background using android:background="@color/cardinal" at the top of main_activity.xml

  1. Add the onclick attribute of your button to refer to a respective function in MainActivity.kt. Hint: add a stub with a View for a parameter for the function to show in the layout designer inspector. Edit the text of the button to "Convert" using another string attribute.

  2. In the Activity file (i.e. our Controller), create an instance of the TempConverter model that the controller can work with. In the activity you might also want to create a method called updateLabels() and another called updateDisplay() that other methods might call later, similar to what we did with the demo app in class. updateLabels will eventually update the temperature text views upon switching the input temperature (to be added later) while updateDisplay will update the final temperature text view with the converted temperature, after converting the input temperature through the model.

  3. Now fill in the event handler in the Activity that will call the convert function when the button is pushed. If I just put in

val userData: String = tempTextInput.text

then I get an error because the text field is a character sequence instead of a string. This is easily remedied by using .toString() which will have "" if the sequence was empty. We can handle that case (set blank to -500 so the 'N/A' message appears) as follows:

val userData: String = tempTextInput.text.toString()
if(userData == ""){
        converter.setTemp(-500)
    }
else { ...

If you don't have a reference to tempTextInput, remember that you need to do findViewById(...) in order to get a reference to the UI. Refer to the previous lab if you need a refresher on how to do so.

Now we just have to convert userData from the string with the user's input to an int, which I could do with Integer.parseInt(userData). But if the user puts in some nonsense text like 'tom brady', this method would throw an Exception. How do we handle that? Catch the Exception and set to -500.

converter.setTemp(try{Integer.parseInt(userData)} catch (e: Exception){-500})

Once the converter class instance's input temp is properly set in the View Controller, call the converter class instance's convert() method, update the temperature display in the Activity and we are set.

  1. Now that this is working, we can go back to the Layout and add a Switch element found under the Buttons group. We can customize the switch using the attributes inspector on the right specifically the attributes textOff and textOn (remember to enable showText and use Strings).

We now need to create a listener in the Activity which will have the model toggle the units and then call our other Activity method to update the temperature labels so we see the switch is working. Below is a simple method to do so:

unitSwitch?.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { _ , isChecked ->
            // isChecked is true when the switch is ON, otherwise false
            converter.toggleUnits()
            updateDisplay()
        })

A simple test in the simulator should show the temp labels toggle between C and F as the switch is tapped.

If you're using an earlier version of Android and your swtich looks non-material like, add the following to your Switch’s xml 'tools:ignore="UseSwitchCompatOrMaterialXml"'.

  1. Finally, add an info button at the lower right corner of the app that will be used to take us to an about page. To navigate to new "pages" in Android, we could either use a new Activity, which has its own lifecycle, or a fragment, which would live in the hierarchy of the Activity, but can be reused between different Activities. We suggest briefly reading about Activities and Fragments, especially if you're considering to continue with Android development.

We'll use Fragments for this exercise. Right-click on the Layout resource folder and select New > Fragment > Fragment (Blank) and call it fragment_info.xml and InfoFragment.kt, if you open the layout there should be nothing in it and the class file should only have autogenerated code (notice how parameters are passed, but we will not need these).

We’ll now need to do two things to the main layout. First add an id of homeLayout to the main layout (don’t have anything selected and add an id). Then add a button with the text of ‘info’ (or a FloatingActionButton if you're feeling fancy). Finally add an onclick method to MainActivity.kt whenever the info button is clicked that has the following code:

fun onClickInfo(view: View){
        val fragmentManager = supportFragmentManager
        val transaction = supportFragmentManager.beginTransaction()
        val fragment = InfoFragment()
        transaction.replace(R.id.homeLayout, fragment)
        transaction.addToBackStack(null)
        transaction.commit()
    }
  1. Then make the background of the new fragment white and add the following text A simple about us. Use the back button to return to the app. There will be a bug where your buttons will appear on top of the Fragment, we will ignore this for now.

  2. To this point you've likely been running your app in the simulator and using your laptop keyboard for input. Now try to deploy the app to your phone by plugging in your device via USB and selecting it rather than virtual phone. (May take a few moments for your laptop to finish its usual updating whenever a device is connected before you can actually deploy through Developer Mode and ADB). You may also need to install additional drivers, see this tutorial for more info. It is also a good practice to install your specific device for testing virtually before testing physically.

  3. Our app works, but we can make it look prettier. This part is optional, but feel free to use the techniques introduced at the end of the last lab to make our app more visually astonishing! The background photo used can be found here.


Stop

Below is some starter code to give you hints about how to get moving. Do not use this code until after you have tried to do these tasks on your own.


Model starter code:

package com.example.tempconverterapp

class TempConverter() {
    var inputTemp: Int = 0
    var convertedTemp: Int? = 0
    var convertedTempDisplay: String = "0"
    private var tempUnits: String = "°F"
    private var newUnits: String = "°C"

    private fun tempBelowAbsoluteZero(temp: Int): Boolean {
        return (temp < -454 && tempUnits == "°F") || (temp < -270 && tempUnits == "°C")
    }
    
    // Separated functions for temperature conversion, by unit
    private fun convertCtoF(temp: Int):Int?{
    }
    
    private fun convertFtoC(temp: Int):Int?{
    }

    // Call the appropriate conversion function based on unit and update display variables
    fun convert(){
    }
    
  }