【Calculation of tip】

Previous: 【Create XML layout for Android application】

1. Preparations

In this article, you'll write code for a tip calculator to be used with the interface created in the previous article, "Creating an XML Layout for Android."

prerequisite

Learning Content

  • The basic structure of an Android application.
  • How to read values ​​from the interface into code and manipulate them.
  • How to more easily write code that interacts with views using view binding instead findViewById()of .
  • How to use decimal numbers in Kotlin with Doublethe data type.
  • How to format a number as currency.
  • How to dynamically create a string using a string parameter.
  • How to use Logcat in Android Studio to find problems in your app.

build content

2. Overview of the starter application

The Tip Time app from the previous article has all the interface a tip calculator needs, but no code to calculate tips. There is a Calculatebutton , but it doesn't work yet. Cost of Service EditTextAllows the user to enter a service fee. A list of RadioButtons that lets the user choose a tip percentage and Switchlets the user choose whether the tip should be rounded up. The tip amount is displayed TextViewin , and eventually Calculate Buttonthe will tell the application to fetch data from other fields and calculate the tip amount. This article is to continue from here.

insert image description here

Application Project Structure
An application project in the IDE consists of many parts, including Kotlin code, XML layouts, and other resources such as strings and images. Before making changes to your application, it's a good idea to familiarize yourself with the environment.

  • Open the Tip Time project in Android Studio.
  • If the Project window is not displayed, select the Project tab on the left side of Android Studio.
  • Select the Android view from the drop-down list (if it is not already selected).
    insert image description here
  • java folder for Kotlin files (or Java files)
  • MainActivity- The class where all the Kotlin code for the tip calculator logic resides
  • res folder of application resources
  • activity_main.xml- Layout files for Android applications
  • strings.xml- Contains string resources for Android applications
  • Gradle Scripts folder

Gradle is the automated build system used by Android Studio. Whenever you change code, add resources, or make other changes to your app, Gradle figures out what changed and performs the necessary steps to rebuild your app. It also installs the app in an emulator or on a physical device and controls its execution.

There are other folders and files involved in building the application, but these are the main folders and files that you will be working with in this codelab and the ones that will follow.

Note: You can also write Android applications using the Java programming language.

3. View Binding

In order to calculate the tip, the code needs to access all interface elements to read the user's input. As you may recall from previous articles, code needs to find a reference to View(such as Buttonor TextView) before Viewcalling or accessing its properties. The Android framework provides findViewById()a method that does exactly what you need, which is, given Viewthe ID of a , returns a reference to it. This works, but as you add more views to your app and the interface becomes more complex, using findViewById()can become cumbersome.

For convenience, Android also provides a feature called view binding . With a little more work up front, view binding can make calling methods on views in your interface much easier and faster. You need to enable view binding for your app in Gradle and make some code changes.

Enable view binding

  1. Open the app's build.gradlefile ( Gradle Scripts > build.gradle (Module: Tip_Time.app) ).
  2. In androidthe section , add the following lines of code:
buildFeatures {
    
    
    viewBinding = true
}
  1. Note the following message: Gradle files have changed since last project sync .
  2. Press Sync Now .
    insert image description here

After a few moments, you should see the following message at the bottom of the Android Studio window: Gradle sync finished . You can close build.gradlethe file .

Initialize the binding object
In the previous article, you MainActivitysaw onCreate()the method in the class. It's one of the first things called MainActivitywhen . You'll create and initialize the binding object once, rather than for every Viewcall findViewById().
insert image description here

  1. Open MainActivity.kt (app > java > com.example.tiptime > MainActivity ).
  2. Set up to use view binding by replacing all existing code in MainActivitythe class with the following:MainActivity
class MainActivity : AppCompatActivity() {
    
    

    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }
}
  1. The following line of code declares a top-level variable in the class for the bound object. The variable is defined at this level because it will be used in several methods of MainActivitythe class .
lateinit var binding: ActivityMainBinding

lateinitKeywords are new content. Make sure your code initializes variables before using them. If you don't, the app will crash.

  1. The following line of code initializes bindingthe object that you will use to access activity_main.xmlthe layout in the Views.
binding = ActivityMainBinding.inflate(layoutInflater)
  1. Sets the activity's content view. The following line of code specifies the root of the view hierarchy in the app binding.rootinstead of passing the layout R.layout.activity_main's resource ID.
setContentView(binding.root)

You may remember the concept of parent and child views; the root connects to all of these views.

Now, Viewwhen , you can bindingget it from the object instead of calling findViewById(). bindingThe object automatically defines a Viewreference . Using view binding is much cleaner, and often you don't even need to create a variable to Viewhold reference to the , just use it directly from the binding object.

// Old way with findViewById()
val myButton: Button = findViewById(R.id.my_button)
myButton.text = "A button"

// Better way with view binding
val myButton: Button = binding.myButton
myButton.text = "A button"

// Best way with view binding and no extra variable
binding.myButton.text = "A button"

Isn't that great!

Note: The name of the binding class is generated by converting the name of the XML file to Pascal case and adding the word "Binding" at the end. Likewise, references to each view are generated by removing the underscore and converting the view name to camel lowercase. For example, for Pascal case, activity_main.xmlbecomes ActivityMainBindingand you binding.textViewcan access as @id/text_view.

4. Calculate the tip

Calculating a tip begins when the user taps the Calculate button. This involves checking the interface to see how much the service costs and what percentage the user wants to tip. Using this information, you can calculate the total service charge and display the tip amount.

Adding a click listener to the button
The first step is to add a click listener to specify what the Calculate button should do when the user taps it.

  1. In MainActivity.ktthe of onCreate(), setContentView()after , Calculateset a click listener on the button and have it call calculateTip().
binding.calculateButton.setOnClickListener{
    
     calculateTip() }
  1. Still in MainActivitythe class , but onCreate()outside of , add a helper method calculateTip()named .
fun calculateTip() {
    
    

}

Here you will add the appropriate code to check the interface and calculate the tip.
MainActivity.kt

class MainActivity : AppCompatActivity() {
    
    

    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.calculateButton.setOnClickListener{
    
     calculateTip() }
    }

    fun calculateTip() {
    
    

    }
}

Obtaining a Service Charge
In order to calculate a tip, the first thing you need is a service charge. Text is stored EditTextin , but you need it as a number so you can use it in calculations. You may recall Intthe type , but Intcan only hold integers. To use decimal numbers in your app, use a data type Doublenamed instead of Int. You can read more about numeric data types in Kotlin in the corresponding documentation . Kotlin provides Stringa Doublemethod (named toDouble()) for converting to .

  1. First, get the text of the service charge. In calculateTip()the method , get the text property of Cost of Service EditText and assign it to a variable stringInTextFieldcalled . Remember that you can access UI elements using bindingthe object , and you can refer to UI elements by their resource ID names (in camel case).
val stringInTextField = binding.costOfService.text

Note the .text. The first part binding.costOfServicereferences the interface elements for service charges. Adding at the end .textmeans to get that result ( EditTextobject), and get textthe property . This is called a "chain" and is a very common pattern in Kotlin.

  1. Next, convert the text to decimal numbers. stringInTextFieldCalls on toDouble()and stores it in a variable costnamed .
val cost = stringInTextField.toDouble()

However, that doesn't work and requires Stringa call to toDouble(). The EditTextoriginal textattribute is Editablebecause it represents text that can be changed. Fortunately, you can convert this to by Editablecalling ) on .toString(String

  1. binding.costOfService.textcall toString() to convert it to String:
val stringInTextField = binding.costOfService.text.toString()

Now, stringInTextField.toDouble()will work.
At this point, calculateTip()the method should look like this:

fun calculateTip() {
    
    
    val stringInTextField = binding.costOfService.text.toString()
    val cost = stringInTextField.toDouble()
}

Getting a Tip Percent
By now, you've got your service fee. Now you RadioButtonsneed RadioGroupthe tip percentage the user chooses from the of .

  1. In calculateTip()the , get the propertytipOptions RadioGroup of the and assign it to a variable named .checkedRadioButtonIdselectedId
val selectedId = binding.tipOptions.checkedRadioButtonId

RadioButtonNow, you know which one ( R.id.option_twenty_percent, , andR.id.option_eighteen_percent ) was chosen , but you also need the corresponding percentage. R.id.fifteen_percentYou could write a series if/elseof statements , but whenit's much simpler to use expressions.

  1. Add the following line of code to get the tip percentage.
val tipPercentage = when (selectedId) {
    
    
    R.id.option_twenty_percent -> 0.20
    R.id.option_eighteen_percent -> 0.18
    else -> 0.15
}

At this point, calculateTip()the method should look like this:

fun calculateTip() {
    
    
    val stringInTextField = binding.costOfService.text.toString()
    val cost = stringInTextField.toDouble()
    val selectedId = binding.tipOptions.checkedRadioButtonId
    val tipPercentage = when (selectedId) {
    
    
        R.id.option_twenty_percent -> 0.20
        R.id.option_eighteen_percent -> 0.18
        else -> 0.15
    }
}

Calculate tip and round it up

Now that you have the service charge and the tip percentage, calculating the tip is simple: the tip is the service charge times the tip percentage, or tip = service charge * tip percentage. (Optional) The value can be rounded up.

  1. calculateTip()In , after the other code you've added, tipPercentagemultiply by costand assign it to a variable tipcalled .
var tip = tipPercentage * cost

Note that is used here varinstead of val. This is because you may need to round the value up if the user selects the corresponding option, so the value may change.

For Switchelements , you can check isCheckedthe attribute to see if the switch is "on".

  1. Assign the isCheckedproperty to a roundUpvariable named .
val roundUp = binding.roundUpSwitch.isChecked

The term "rounding" means rounding a decimal number up or down to the nearest integer value, but in this case you just want to round up or find an upper bound. You can use ceil()the function . There are several functions with that name, but what you want is the one defined kotlin.mathin . You could add importa statement , but in this case it's easier to use kotlin.math.ceil() to tell Android Studio which function you're referring to.
insert image description here
If you want to use several math functions, it is easier to add importstatements .

  1. Add ifthe statement , if roundUptrue, assign the upper limit of the tip to tipthe variable .
if (roundUp) {
    
    
    tip = kotlin.math.ceil(tip)
}

At this point, calculateTip()the method should look like this:

fun calculateTip() {
    
    
    val stringInTextField = binding.costOfService.text.toString()
    val cost = stringInTextField.toDouble()
    val selectedId = binding.tipOptions.checkedRadioButtonId
    val tipPercentage = when (selectedId) {
    
    
        R.id.option_twenty_percent -> 0.20
        R.id.option_eighteen_percent -> 0.18
        else -> 0.15
    }
    var tip = tipPercentage * cost
    val roundUp = binding.roundUpSwitch.isChecked
    if (roundUp) {
    
    
        tip = kotlin.math.ceil(tip)
    }
}

Format the tip

Your app is almost ready to use. You've calculated the tip, now you just need to format and display the tip.

As you might expect, Kotlin provides several methods for formatting different types of numbers. But the tip amount is slightly different - it represents a monetary value. Different countries use different currencies and have different rules for formatting decimal numbers. For example, 1234.56 would be formatted as $1,234.56 when expressed in US dollars, but would be formatted as €1.234,56 when expressed in Euros. Fortunately, the Android framework provides methods for formatting numbers as currency, so you don't need to know all the possibilities. Currency is automatically formatted based on language and other settings selected by the user on the phone. You can read more about NumberFormat in the Android developer documentation .

  1. calculateTip()In , after other code, call NumberFormat.getCurrencyInstance().
NumberFormat.getCurrencyInstance()

This gives you a number formatting tool that you can use to format numbers as currency.

  1. Using the number formatter format(), tipchain calls to the method with and assign the result to a variable formattedTipnamed .
val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
  1. Note that NumberFormatit is shown in red. This is because Android Studio cannot automatically figure out which version you are referring to NumberFormat.

  2. Hover NumberFormatover and choose Import in the popup that appears .
    insert image description here

  3. In the list of possible imports, select NumberFormat (java.text) . Android MainActivityStudio adds importa statement at the top of the file, and is NumberFormatno longer red.
    show tip

Now it's time to display the tip in the app's tipAmount TextViewelement . You could just formattedTipassign to the textattribute, but it's better to mark what the amount represents. In China where Chinese is used, you can display it as Tip Amount: ¥12.34 , (because my ability is limited, I can only set it to US dollars according to the official document, and it will be changed in time after mastering it later) But in other languages, the number may need to appear at the beginning or even in the middle of the string. The Android framework provides a mechanism for this called "string parameters", so that the person translating the application can change where the numbers appear if they want.

  1. Open strings.xml( app > res > values ​​> strings.xml ).
  2. tip_amountChange the string from Tip Amountto Tip Amount: %s.
<string name="tip_amount">Tip Amount: %s</string>

The formatted currency will be inserted %sat .

  1. Now, set the text tipResultof the . MainActivity.ktBack calculateTip()in the method of the , call getString(R.string.tip_amount, formattedTip)and it to the propertyTextView of the tip result .text
binding.tipResult.text = getString(R.string.tip_amount, formattedTip)

At this point, calculateTip()the method should look like this:

fun calculateTip() {
    
    
    val stringInTextField = binding.costOfService.text.toString()
    val cost = stringInTextField.toDouble()
    val selectedId = binding.tipOptions.checkedRadioButtonId
    val tipPercentage = when (selectedId) {
    
    
        R.id.option_twenty_percent -> 0.20
        R.id.option_eighteen_percent -> 0.18
        else -> 0.15
    }
    var tip = tipPercentage * cost
    val roundUp = binding.roundUpSwitch.isChecked
    if (roundUp) {
    
    
        tip = kotlin.math.ceil(tip)
    }
    val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
    binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
}

You're almost done. Setting a placeholder for this TextView is useful when developing an app (and viewing a preview).

  1. Open activity_main.xml( app > res > layout > activity_main.xml ).
  2. find tip_result TextView.
  3. Remove the line of code that contains android:textthe attribute .
android:text="@string/tip_amount"
  1. Add a line of code for the attribute Tip Amount: $10set .tools:text
tools:text="Tip Amount: $10"

Since this is just a placeholder, you don't need to extract the string into a resource. It doesn't show up when running the app.

Note that the tool text is displayed in the layout editor .
Run the application. Enter the amount for the service charge and select some options, then press the Calculate button.
insert image description here
Congratulations, it's working! If you didn't get the correct tip amount, go back to step 1 of this section and make sure you've made any necessary code changes.

5. Testing and Debugging

You've run the app through the various steps to make sure it works as expected, but now it's time for some additional testing.

Now, consider how information moves through the application in calculateTip()the method , and what problems might arise at each step.

For example, in the following line of code:

val cost = stringInTextField.toDouble()

What happens if stringInTextFielddoes not represent a number? What happens if the user doesn't enter any text and stringInTextFieldis empty?

  1. Run the app in the simulator, but use Run > Debug 'app' instead of Run > Run 'app' .
  2. Experiment with different combinations of service charge, tip amount, and whether or not the tip is rounded up to verify that you get the expected results when you tap Calculate in each situation.
  3. Now, try deleting all the text in Cost of Servicethe field , and tap Calculate. Oops, the program crashed.

Debug crash issues

The first step in dealing with errors is figuring out what happened. Android Studio keeps a log of what's going on in the system, which you can use to figure out what went wrong.

  1. Press the Logcat button at the bottom of Android Studio, or select View > Tool Windows > Logcat from the menu.
    insert image description here
  2. The Logcat window will appear at the bottom of Android Studio filled with some weird looking text.
    insert image description here
    The text is a stack trace listing the methods being called when the crash occurred.
  3. Scroll up in the Logcat text until you find the line containing the text .FATAL EXCEPTION
2020-06-24 10:09:41.564 24423-24423/com.example.tiptime E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.tiptime, PID: 24423
    java.lang.NumberFormatException: empty String
        at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
        at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
        at java.lang.Double.parseDouble(Double.java:538)
        at com.example.tiptime.MainActivity.calculateTip(MainActivity.kt:22)
        at com.example.tiptime.MainActivity$onCreate$1.onClick(MainActivity.kt:17)
  1. Read down until you find the line NumberFormatExceptioncontaining .
java.lang.NumberFormatException: empty String

Shown on the right is empty String. The type of the exception tells you that it has something to do with the number format, and the rest tells you the basics of the problem: a null was found Stringwhen it should have been one with a value String.

  1. Read on and you'll see some calls parseDouble()to .
  2. Below these calls, find the line calculateTipcontaining . Note that it also contains MainActivitythe class .
at com.example.tiptime.MainActivity.calculateTip(MainActivity.kt:22)
  1. Looking closely at that line, you can see the exact place in the code where the call is made, which is line 22 MainActivity.ktin the . (This may be a different number here if your code is different.) This line Stringconverts to Doubleand assigns the result to costthe variable .
val cost = stringInTextField.toDouble()
  1. Find methods Stringfor toDouble(). The method is called String.toDouble().
  2. "Exceptions: NumberFormatException- if the string is not a valid representation of a number" is displayed on the page.

Exceptions are a way for the system to signal that something is wrong. In this case, the problem is that an empty toDouble()cannot be Stringconverted to Double. Although EditTexthas inputType=numberDecimala setting, it is still possible to enter some value toDouble()that cannot handled by , such as an empty string.
understand null

Calling on an empty string or a string that does not represent a valid decimal number toDouble()has no effect . Fortunately, Kotlin also provides a method toDoubleOrNull()called to handle these problems. It returns a decimal number if it can, and returns it if there is a problem null.

nullis a special value that means "no value". It is not the same as a value 0.0of Doubleor an empty "" that contains zero strings String. NullIndicates no value, None Doubleor None String. Many methods expect a value, but they may not know what to do with nulland will stall, which means the app will crash, so Kotlin tries to limit nullwhere the is used. You will learn more about this in future lessons.

Apps can check to toDoubleOrNull()return nulland handle it differently nullwhen so that the app doesn't crash.

  1. calculateTip()In , change costthe line of code that declares the variable to call toDoubleOrNull()instead of calling toDouble().
val cost = stringInTextField.toDoubleOrNull()
  1. After that line, add a statement to check costif is null, and if so, return from the method. returninstruction means to exit the method without executing the rest of the instructions. If the method needs to return a value, you can specify it using returnthe directive .
if (cost == null) {
    
    
    return
}
  1. Run the application again.
  2. With no text in the Cost of Service field, tap Calculate . This time the app didn't crash! Great - you found and fixed the bug!

handle other situations

Not all errors cause the app to crash, and sometimes the results can confuse users.

The following are other situations to consider. What happens if the user:

  1. Enter a valid amount for the service charge
  2. Tap Calculate to calculate tip
  3. Delete Service Fee
  4. Tap Calculate again

For the first time, the tip will be calculated and displayed as expected. calculateTip()The second time, the method returns early due to the check you just added , but the app still shows the previous tip amount. This can confuse the user, so some code should be added to clear the tip amount if something goes wrong.

  1. Confirm that this is what happened by entering a valid service charge and tapping Calculate , then deleting the text and tapping Calculate again . The first tip value should still be displayed.
  2. ifInside the just added , returnbefore the statement, add a line tipResultto textset the 's property to an empty string.
if (cost == null) {
    
    
    binding.tipResult.text = ""
    return
}

This will clear the tip amount before calculateTip()returning .

  1. Run the app again and try the above scenarios. When you tap Calculate a second time , the first tip amount should disappear.

Congratulations! You've created a working Android tip calculator app that handles some edge cases!

6. Adopt good coding practices

The Tip Calculator works fine for now, but you can make the code a little better to make it easier to use in the future by adopting good coding practices.

  1. Open MainActivity.kt( app > java > com.example.tiptime > MainActivity ).
  2. Take a look at the beginning of calculateTip()the method , and you'll probably see it has a squiggly gray underline.
    insert image description here
  3. Hover calculateTip()over and you'll see a message: Function 'calculateTip' could be private , followed by a suggestion: Make 'calculateTip' 'private' .
    insert image description here
    As you may recall from an earlier article, privatea representation method or variable is only visible to code within that class (in this case, MainActivitythe class ). MainActivityCode outside of has no reason to call it calculateTip(), so you can safely leave it as private.
  4. Select Make 'calculateTip' 'private' , or add the private keyword before fun calculateTip() . The gray line below calculateTip() disappears.

check code

Gray lines are very thin and easily overlooked. You can look for more gray lines throughout the file, but there's an easier way to make sure you find all the suggestions.

  1. With MainActivity.ktstill open, select Analyze > Inspect Code… from the menu . A dialog box called Specify Inspection Scope will be displayed .
    insert image description here
  2. Select the option starting with File and press OK . This will limit the scope of the inspection to only MainActivity.kt.
  3. A window containing Inspection Results will appear at the bottom .
  4. Click the gray triangle next to Kotlin , then click the gray triangle next to Style issues until you see two messages. The first message reads Class member can have 'private' visibility .
    insert image description here
  5. Click the gray triangle until you see the following message: Property 'binding' could be private , then click the message. Android Studio displays some of the code MainActivityin and highlights bindingthe variable.
    insert image description here
  6. Press the Make 'binding' 'private' button. Android Studio will remove the problem from the Inspection Results (if the problem is still displayed, then click the double green triangle in the upper left corner of the fourth picture above to check again).
  7. If you look in the code binding, you'll see that Android Studio adds privatethe keyword .
private lateinit var binding: ActivityMainBinding

  1. Click the gray triangle in the results until you see the following message: Variable declaration could be inlined . Android Studio displays some code again, but this time with selectedIdthe variable .
    insert image description here
  2. If you look at the code, you'll see that is used selectedIdonly twice: first in the highlighted line, where it's given a value tipOptions.checkedRadioButtonId, and a second time whenin .
  3. Press the Inline variable button. Android Studio whenwill selectedIdreplace the in the expression with the value assigned on the previous line. Then, it removes the previous line entirely, since this line is no longer needed!
val tipPercentage = when (binding.tipOptions.checkedRadioButtonId) {
    
    
    R.id.option_twenty_percent -> 0.20
    R.id.option_eighteen_percent -> 0.18
    else -> 0.15
}

This is really great! One less line of code, one less variable.
Remove unnecessary variables

Android Studio no longer has any inspection results. However, if you take a closer look at the code, you'll see a pattern similar to the one you just changed: the roundUpvariable is assigned a value on one line, used on the next line, and not used anywhere else.

  1. Copy the expression to the right of from the line roundUpwhere is assigned =.
val roundUp = binding.roundUpSwitch.isChecked

  1. roundUpReplace the on the next line with the expression you just copied binding.roundUpSwitch.isChecked.
if (binding.roundUpSwitch.isChecked) {
    
    
    tip = kotlin.math.ceil(tip)
}
  1. Delete the line roundUpcontaining , as this line is no longer needed.

You do the same thing that Android Studio helps you do with selectedIdthe variable . Again, one less line of code and one less variable. These are minor changes, but help to improve the conciseness and readability of the code.
(Optional) Eliminate duplicate code

Once your app is running correctly, you can look for other opportunities to clean up your code and make it cleaner. For example, when you don't enter a value in Service Charge, the app tipResultupdates with an empty string "". When a value is present, you can use NumberFormatto format it. This functionality can be used elsewhere in the app, for example, to display a 0.0tip instead of an empty string.

To reduce duplication of very similar code, you can extract these two lines of code into their own function. This helper function takes as input a tip amount of Doublethe form , formats it, and updates the on-screen tipResult TextView.

  1. Identify duplicate code MainActivity.ktin . These lines of code can be used multiple times in calculateTip()the function , once for 0.0the case of and once for the general case.
val formattedTip = NumberFormat.getCurrencyInstance().format(0.0)
binding.tipResult.text = getString(R.string.tip_amount, formattedTip)

  1. Move duplicate code to its own function. One change to the code is to adopt a tip parameter so that the code can be used in multiple places.
private fun displayTip(tip : Double) {
    
    
   val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
   binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
}
  1. Update calculateTip()function to use displayTip()helper function and check 0.0.

MainActivity.kt

private fun calculateTip() {
    
    
    ...

        // If the cost is null or 0, then display 0 tip and exit this function early.
        if (cost == null || cost == 0.0) {
    
    
            displayTip(0.0)
            return
        }

    ...
    if (binding.roundUpSwitch.isChecked) {
    
    
        tip = kotlin.math.ceil(tip)
    }

    // Display the formatted tip value on screen
    displayTip(tip)
}

Note
Although the app is functional, it is not ready for use. You need to do more testing. Also, you need to polish it up a bit visually and follow Material Design guidelines. You'll also learn how to change your app theme and app icon in upcoming articles.

7. Solution Code

The solution code for this article is shown below.
insert image description here
MainActivity.kt

(note, on the first line: if your package name is com.example.tiptimedifferent from , replace it)

package com.example.tiptime

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.tiptime.databinding.ActivityMainBinding
import java.text.NumberFormat

class MainActivity : AppCompatActivity() {
    
    

   private lateinit var binding: ActivityMainBinding

   override fun onCreate(savedInstanceState: Bundle?) {
    
    
       super.onCreate(savedInstanceState)

       binding = ActivityMainBinding.inflate(layoutInflater)
       setContentView(binding.root)

       binding.calculateButton.setOnClickListener {
    
     calculateTip() }
   }

   private fun calculateTip() {
    
    
       val stringInTextField = binding.costOfService.text.toString()
       val cost = stringInTextField.toDoubleOrNull()
       if (cost == null) {
    
    
           binding.tipResult.text = ""
           return
       }

       val tipPercentage = when (binding.tipOptions.checkedRadioButtonId) {
    
    
           R.id.option_twenty_percent -> 0.20
           R.id.option_eighteen_percent -> 0.18
           else -> 0.15
       }

       var tip = tipPercentage * cost
       if (binding.roundUpSwitch.isChecked) {
    
    
           tip = kotlin.math.ceil(tip)
       }

       val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
       binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
   }
}

Revisestrings.xml



<string name="tip_amount">Tip Amount: %s</string>

Reviseactivity_main.xml

...

<TextView
   android:id="@+id/tip_result"
   ...
   tools:text="Tip Amount: $10" />

...

Modify the application module'sbuild.gradle

android {
    
    
    ...

    buildFeatures {
    
    
        viewBinding = true
    }
    ...
}

8. Summary

  • View binding makes it easier for you to write code that interacts with UI elements in your app.
  • The Double data type in Kotlin can store decimal numbers.
  • RadioGroupUse checkedRadioButtonIdthe attribute of the to find out which is selected RadioButton.
  • Use to NumberFormat.getCurrencyInstance()get formatting tools for formatting numbers as currency.
  • You can use string parameters %ssuch as to create dynamic strings that can still be easily translated into other languages.
  • Testing is very important!
  • You can use in Android Studio Logcatto troubleshoot issues such as app crashes.
  • The stack trace shows the list of methods called. This can be useful if the code generates exceptions.
  • Exceptions indicate problems that the code did not anticipate.
  • Nullmeans "no value".
  • Not all code can handle nullthe value , so it should be used with care.
  • Choose Analyze > Inspect Code to get suggestions for improving your code.

9. More codelabs for interface improvement

You've got the tip calculator working, great! You'll notice that there are still ways to improve the interface and make the app look better. If you're interested, check out these additional articles below to learn more about how to change your app's theme and app icon, as well as follow best practices from the Material Design guidelines for the Tip Time app!

10. Learn more

11. Practice on your own

Note: Practice as needed. This is your chance to practice what you've learned in this codelab.

  • Using the cooking unit of measure converter app from the previous exercise, add code for the logic and calculations for converting between milliliters and fluid ounces, and so on.

Guess you like

Origin blog.csdn.net/Jasonhso/article/details/126923147