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
- Code from Creating XML
Layouts for Android article. - Learn how to run Android apps from Android Studio in the emulator or on a device.
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
Double
the 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
- A tip calculator app with a functioning Calculate button.
required conditions - A computer with the latest stable version of Android Studio installed .
- The starter code for the Tip Time app, which contains the layout for the tip calculator.
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 Calculate
button , but it doesn't work yet. Cost of Service EditText
Allows the user to enter a service fee. A list of RadioButtons that lets the user choose a tip percentage and Switch
lets the user choose whether the tip should be rounded up. The tip amount is displayed TextView
in , and eventually Calculate Button
the will tell the application to fetch data from other fields and calculate the tip amount. This article is to continue from 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).
- 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 applicationsstrings.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 Button
or TextView
) before View
calling or accessing its properties. The Android framework provides findViewById()
a method that does exactly what you need, which is, given View
the 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
- Open the app's
build.gradle
file ( Gradle Scripts > build.gradle (Module: Tip_Time.app) ). - In
android
the section , add the following lines of code:
buildFeatures {
viewBinding = true
}
- Note the following message: Gradle files have changed since last project sync .
- Press Sync Now .
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.gradle
the file .
Initialize the binding object
In the previous article, you MainActivity
saw onCreate()
the method in the class. It's one of the first things called MainActivity
when . You'll create and initialize the binding object once, rather than for every View
call findViewById()
.
- Open
MainActivity.kt
(app > java > com.example.tiptime > MainActivity ). - Set up to use view binding by replacing all existing code in
MainActivity
the 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)
}
}
- 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
MainActivity
the class .
lateinit var binding: ActivityMainBinding
lateinit
Keywords are new content. Make sure your code initializes variables before using them. If you don't, the app will crash.
- The following line of code initializes
binding
the object that you will use to accessactivity_main.xml
the layout in theViews
.
binding = ActivityMainBinding.inflate(layoutInflater)
- Sets the activity's content view. The following line of code specifies the root of the view hierarchy in the app
binding.root
instead of passing the layoutR.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, View
when , you can binding
get it from the object instead of calling findViewById()
. binding
The object automatically defines a View
reference . Using view binding is much cleaner, and often you don't even need to create a variable to View
hold 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.xml
becomesActivityMainBinding
and youbinding.textView
can 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.
- In
MainActivity.kt
the ofonCreate()
,setContentView()
after ,Calculate
set a click listener on the button and have it callcalculateTip()
.
binding.calculateButton.setOnClickListener{
calculateTip() }
- Still in
MainActivity
the class , butonCreate()
outside of , add a helper methodcalculateTip()
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 EditText
in , but you need it as a number so you can use it in calculations. You may recall Int
the type , but Int
can only hold integers. To use decimal numbers in your app, use a data type Double
named instead of Int
. You can read more about numeric data types in Kotlin in the corresponding documentation . Kotlin provides String
a Double
method (named toDouble()
) for converting to .
- First, get the text of the service charge. In
calculateTip()
the method , get the text property of Cost of ServiceEditText
and assign it to a variablestringInTextField
called . Remember that you can access UI elements usingbinding
the 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.costOfService
references the interface elements for service charges. Adding at the end .text
means to get that result ( EditText
object), and get text
the property . This is called a "chain" and is a very common pattern in Kotlin.
- Next, convert the text to decimal numbers.
stringInTextField
Calls ontoDouble()
and stores it in a variablecost
named .
val cost = stringInTextField.toDouble()
However, that doesn't work and requires String
a call to toDouble()
. The EditText
original text
attribute is Editable
because it represents text that can be changed. Fortunately, you can convert this to by Editable
calling ) on .toString(
String
binding.costOfService.text
calltoString(
) to convert it toString
:
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 RadioButtons
need RadioGroup
the tip percentage the user chooses from the of .
- In
calculateTip()
the , get the propertytipOptions
RadioGroup
of the and assign it to a variable named .checkedRadioButtonId
selectedId
val selectedId = binding.tipOptions.checkedRadioButtonId
RadioButton
Now, 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_percent
You could write a series if/else
of statements , but when
it's much simpler to use expressions.
- 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.
calculateTip()
In , after the other code you've added,tipPercentage
multiply bycost
and assign it to a variabletip
called .
var tip = tipPercentage * cost
Note that is used here var
instead 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 Switch
elements , you can check isChecked
the attribute to see if the switch is "on".
- Assign the
isChecked
property to aroundUp
variable 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.math
in . You could add import
a statement , but in this case it's easier to use kotlin.math.ceil(
) to tell Android Studio which function you're referring to.
If you want to use several math functions, it is easier to add import
statements .
- Add
if
the statement , ifroundUp
true, assign the upper limit of the tip totip
the 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 .
calculateTip()
In , after other code, callNumberFormat.getCurrencyInstance()
.
NumberFormat.getCurrencyInstance()
This gives you a number formatting tool that you can use to format numbers as currency.
- Using the number formatter
format()
,tip
chain calls to the method with and assign the result to a variableformattedTip
named .
val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
-
Note that
NumberFormat
it is shown in red. This is because Android Studio cannot automatically figure out which version you are referring toNumberFormat
. -
Hover
NumberFormat
over and choose Import in the popup that appears .
-
In the list of possible imports, select NumberFormat (java.text) . Android
MainActivity
Studio addsimport
a statement at the top of the file, and isNumberFormat
no longer red.
show tip
Now it's time to display the tip in the app's tipAmount TextView
element . You could just formattedTip
assign to the text
attribute, 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.
- Open
strings.xml
( app > res > values > strings.xml ). tip_amount
Change the string fromTip Amount
toTip Amount: %s
.
<string name="tip_amount">Tip Amount: %s</string>
The formatted currency will be inserted %s
at .
- Now, set the text
tipResult
of the .MainActivity.kt
BackcalculateTip()
in the method of the , callgetString(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).
- Open
activity_main.xml
( app > res > layout > activity_main.xml ). - find
tip_result
TextView
. - Remove the line of code that contains
android:text
the attribute .
android:text="@string/tip_amount"
- Add a line of code for the attribute
Tip Amount: $10
set .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.
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 stringInTextField
does not represent a number? What happens if the user doesn't enter any text and stringInTextField
is empty?
- Run the app in the simulator, but use Run > Debug 'app' instead of Run > Run 'app' .
- 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.
- Now, try deleting all the text in
Cost of Service
the field , and tapCalculate
. 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.
- Press the Logcat button at the bottom of Android Studio, or select View > Tool Windows > Logcat from the menu.
- The Logcat window will appear at the bottom of Android Studio filled with some weird looking text.
The text is a stack trace listing the methods being called when the crash occurred. - 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)
- Read down until you find the line
NumberFormatException
containing .
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 String
when it should have been one with a value String
.
- Read on and you'll see some calls
parseDouble()
to . - Below these calls, find the line
calculateTip
containing . Note that it also containsMainActivity
the class .
at com.example.tiptime.MainActivity.calculateTip(MainActivity.kt:22)
- Looking closely at that line, you can see the exact place in the code where the call is made, which is line 22
MainActivity.kt
in the . (This may be a different number here if your code is different.) This lineString
converts toDouble
and assigns the result tocost
the variable .
val cost = stringInTextField.toDouble()
- Find methods
String
fortoDouble()
. The method is calledString.toDouble()
. - "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 String
converted to Double
. Although EditText
has inputType=numberDecimal
a 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
.
null
is a special value that means "no value". It is not the same as a value 0.0
of Double
or an empty "" that contains zero strings String
. Null
Indicates no value, None Double
or None String
. Many methods expect a value, but they may not know what to do with null
and will stall, which means the app will crash, so Kotlin tries to limit null
where the is used. You will learn more about this in future lessons.
Apps can check to toDoubleOrNull()
return null
and handle it differently null
when so that the app doesn't crash.
calculateTip()
In , changecost
the line of code that declares the variable to calltoDoubleOrNull()
instead of callingtoDouble()
.
val cost = stringInTextField.toDoubleOrNull()
- After that line, add a statement to check
cost
if isnull
, and if so, return from the method.return
instruction means to exit the method without executing the rest of the instructions. If the method needs to return a value, you can specify it usingreturn
the directive .
if (cost == null) {
return
}
- Run the application again.
- 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:
- Enter a valid amount for the service charge
- Tap Calculate to calculate tip
- Delete Service Fee
- 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.
- 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.
if
Inside the just added ,return
before the statement, add a linetipResult
totext
set the 's property to an empty string.
if (cost == null) {
binding.tipResult.text = ""
return
}
This will clear the tip amount before calculateTip()
returning .
- 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.
- Open
MainActivity.kt
( app > java > com.example.tiptime > MainActivity ). - Take a look at the beginning of
calculateTip()
the method , and you'll probably see it has a squiggly gray underline.
- Hover
calculateTip()
over and you'll see a message: Function 'calculateTip' could be private , followed by a suggestion: Make 'calculateTip' 'private' .
As you may recall from an earlier article,private
a representation method or variable is only visible to code within that class (in this case,MainActivity
the class ).MainActivity
Code outside of has no reason to call itcalculateTip()
, so you can safely leave it asprivate
. - 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.
- With
MainActivity.kt
still open, select Analyze > Inspect Code… from the menu . A dialog box called Specify Inspection Scope will be displayed .
- Select the option starting with File and press OK . This will limit the scope of the inspection to only
MainActivity.kt
. - A window containing Inspection Results will appear at the bottom .
- 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 .
- 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
MainActivity
in and highlightsbinding
the variable.
- 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).
- If you look in the code
binding
, you'll see that Android Studio addsprivate
the keyword .
private lateinit var binding: ActivityMainBinding
- 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
selectedId
the variable .
- If you look at the code, you'll see that is used
selectedId
only twice: first in the highlighted line, where it's given a valuetipOptions.checkedRadioButtonId
, and a second timewhen
in . - Press the Inline variable button. Android Studio
when
willselectedId
replace 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 roundUp
variable is assigned a value on one line, used on the next line, and not used anywhere else.
- Copy the expression to the right of from the line
roundUp
where is assigned=
.
val roundUp = binding.roundUpSwitch.isChecked
roundUp
Replace the on the next line with the expression you just copiedbinding.roundUpSwitch.isChecked
.
if (binding.roundUpSwitch.isChecked) {
tip = kotlin.math.ceil(tip)
}
- Delete the line
roundUp
containing , as this line is no longer needed.
You do the same thing that Android Studio helps you do with selectedId
the 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 tipResult
updates with an empty string ""
. When a value is present, you can use NumberFormat
to format it. This functionality can be used elsewhere in the app, for example, to display a 0.0
tip 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 Double
the form , formats it, and updates the on-screen tipResult
TextView
.
- Identify duplicate code
MainActivity.kt
in . These lines of code can be used multiple times incalculateTip()
the function , once for0.0
the case of and once for the general case.
val formattedTip = NumberFormat.getCurrencyInstance().format(0.0)
binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
- 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)
}
- Update
calculateTip()
function to usedisplayTip()
helper function and check0.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.
MainActivity.kt
(note, on the first line: if your package name is com.example.tiptime
different 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.
RadioGroup
UsecheckedRadioButtonId
the attribute of the to find out which is selectedRadioButton
.- Use to
NumberFormat.getCurrencyInstance()
get formatting tools for formatting numbers as currency. - You can use string parameters
%s
such as to create dynamic strings that can still be easily translated into other languages. - Testing is very important!
- You can use in Android Studio
Logcat
to 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.
Null
means "no value".- Not all code can handle
null
the 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
Double
Data types in Kotlin- Numeric data types in Kotlin
- Null Safety in Kotlin
- application list
- View binding
NumberFormat.getCurrencyInstance()
- string parameter
- test
- Logcat
- Analyzing stack traces
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.