Get user input in an app: Part 1
Create XML layouts for Android
7. Adopt good coding practices
Extract the strings
You may have noticed the warnings about hard-coded strings. Recall from the earlier codelabs that extracting strings to a resource file makes it easier to translate your app to other languages and to reuse strings. Go through activity_main.xml
and extract all the string resources.
- Click on a string; hover over on the yellow light bulb icon that appears, then click on the triangle next to it; choose Extract String Resource. The default names for the string resources are fine. If you want, for the tip options you can use
amazing_service
,good_service
, andokay_service
to make the names more descriptive.
Now verify the string resources you just added.
- If the Project window isn’t showing, click the Project tab on the left side of the window.
- Open app > res > values > strings.xml to see all the UI string resources.
<resources>
<string name="app_name">Tip Time</string>
<string name="cost_of_service">Cost of Service</string>
<string name="how_was_the_service">How was the service?</string>
<string name="amazing_service">Amazing (20%)</string>
<string name="good_service">Good (18%)</string>
<string name="ok_service">Okay (15%)</string>
<string name="round_up_tip">Round up tip?</string>
<string name="calculate">Calculate</string>
<string name="tip_amount">Tip Amount</string>
</resources>
Reformat the XML
Android Studio provides various tools to tidy up your code and make sure it follows recommended coding conventions.
- In
activity_main.xml
, choose Edit > Select All. - Choose Code > Reformat Code.
This will make sure the indenting is consistent, and it may reorder some of the XML of UI elements to group things, for example, putting all the android:
attributes of one element together.
3. View binding
For convenience, Android also provides a feature called view binding. With a little more work up front, view binding makes it much easier and faster to call methods on the views in your UI. You’ll 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 the
android
section, add the following lines:
buildFeatures {
viewBinding = true
}
- Note the message Gradle files have changed since last project sync.
- Press Sync Now.
After a few moments, you should see a message at the bottom of the Android Studio window, Gradle sync finished. You can close the build.gradle
file if you want.
Initialize the binding object
In earlier codelabs, you saw the onCreate()
method in the MainActivity
class. It's one of the first things called when your app starts and the MainActivity
is initialized. Instead of calling findViewById()
for each View
in your app, you'll create and initialize a binding object once.
- Open
MainActivity.kt
(app > java > com.example.tiptime > MainActivity). - Replace all of the existing code for
MainActivity
class with this code to setup theMainActivity
to use view binding: - Open
MainActivity.kt
(app > java > com.example.tiptime > MainActivity). - Replace all of the existing code for
MainActivity
class with this code to setup theMainActivity
to use view binding:
class MainActivity : AppCompatActivity() { lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
- This line declares a top-level variable in the class for the binding object. It’s defined at this level because it will be used across multiple methods in
MainActivity
class.
lateint 介紹, 如下
https://www.itread01.com/content/1549465050.html
lateinit var binding: ActivityMainBinding
The lateinit
keyword is something new. It's a promise that your code will initialize the variable before using it. If you don't, your app will crash.
- This line initializes the
binding
object which you'll use to accessViews
in theactivity_main.xml
layout.
binding = ActivityMainBinding.inflate(layoutInflater)
- Set the content view of the activity. Instead of passing the resource ID of the layout,
R.layout.activity_main
, this specifies the root of the hierarchy of views in your app,binding.root
.
setContentView(binding.root)
You may recall the idea of parent views and child views; the root connects to all of them.
binding 視圖,替代findViewByid
Now when you need a reference to a View
in your app, you can get it from the binding
object instead of calling findViewById()
. The binding
object automatically defines references for every View
in your app that has an ID. Using view binding is so much more concise that often you won't even need to create a variable to hold the reference for a View
, 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"
How cool is that?
binding 可去除id 有底線直接變成駝峰式使用
ex: my_button => myButton
Note: The name of the binding class is generated by converting the name of the XML file to camel case and adding the word “Binding” to the end. Similarly, the reference for each view is generated by removing underscores and converting the view name to camel case. For example, activity_main.xml
becomes ActivityMainBinding
, and you can access @id/text_view
as binding.textView
.
The term rounding means adjusting a decimal number up or down to the closest integer value, but in this case, you only want to round up, or find the ceiling. You can use the ceil()
function to do that. There are several functions with that name, but the one you want is defined in kotlin.math
. You could add an import
statement, but in this case it's simpler to just tell Android Studio which you mean by using kotlin.math.ceil()
.
If there were several math functions you wanted to use, it would be easier to add an import
statement.
- Add an
if
statement that assigns the ceiling of the tip to thetip
variable ifroundUp
is true.
if (roundUp) {
tip = kotlin.math.ceil(tip)
}
Format the tip
Your app is almost working. You’ve calculated the tip, now you just need to format it and display it.
As you might expect, Kotlin provides methods for formatting different types of numbers. But the tip amount is a little different — it represents a currency value. Different countries use different currencies, and have different rules for formatting decimal numbers. For example, in U.S. dollars, 1234.56 would be formatted as $1,234.56, but in Euros, it would be formatted €1.234,56. Fortunately, the Android framework provides methods for formatting numbers as currency, so you don’t need to know all the possibilities. The system automatically formats currency based on the language and other settings that the user has chosen on their phone. Read more about NumberFormat in the Android developer documentation.
- In
calculateTip()
after your other code, callNumberFormat.getCurrencyInstance()
NumberFormat.getCurrencyInstance()
This gives you a number formatter you can use to format numbers as currency.
- Using the number formatter, chain a call to the
format()
method with thetip
, and assign the result to a variable calledformattedTip
.
val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
getCurrencyInstance() 貨幣格式化 參考如下
Display the tip
Now it’s time to display the tip in the tip amount TextView
element of your app. You could just assign formattedTip
to the text
attribute, but it would be nice to label what the amount represents. In the U.S. with English, you might have it display Tip Amount: $12.34, but in other languages the number might need to appear at the beginning or even the middle of the string. The Android framework provides a mechanism for this called string parameters, so someone translating your app can change where the number appears if needed.
- Open
strings.xml
(app > res > values > strings.xml) - Change the
tip_amount
string fromTip Amount
toTip Amount: %s
.
<string name="tip_amount">Tip Amount: %s</string>
The %s
is where the formatted currency will be inserted.
- Now set the text of the
tipResult
. Back in thecalculateTip()
method inMainActivity.kt
, callgetString(R.string.tip_amount, formattedTip)
and assign that to thetext
attribute of the tip resultTextView
.
binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
Learn about null
Calling toDouble()
on a string that is empty or a string that doesn't represent a valid decimal number doesn't work. Fortunately Kotlin also provides a method called toDoubleOrNull()
which handles these problems. It returns a decimal number if it can, or it returns null
if there's a problem.
Null is a special value that means “no value”. It’s different from a Double
having a value of 0.0
or an empty String
with zero characters, ""
. Null
means there is no value, no Double
or no String
. Many methods expect a value and may not know how to handle null
and will stop, which means the app crashes, so Kotlin tries to limit where null
is used. You'll learn more about this in future lessons.
Your app can check for null
being returned from toDoubleOrNull()
and do things differently in that case so the app doesn't crash.
- In
calculateTip()
, change the line that declares thecost
variable to calltoDoubleOrNull()
instead of callingtoDouble()
.
val cost = stringInTextField.toDoubleOrNull()
- After that line, add a statement to check if
cost
isnull
, and if so to return from the method. Thereturn
instruction means exit the method without executing the rest of the instructions. If the method needed to return a value, you would specify it with areturn
instruction with an expression.
if (cost == null) {
return
}
Inspect the code (檢查code 安全性)
The grey line is very subtle and easy to overlook. You could look through the whole file for more grey lines, but there’s a simpler way to make sure you find all of the suggestions.
- With
MainActivity.kt
still open, choose Analyze > Inspect Code... in the menus. A dialog box called Specify Inspection Scope appears.
- Choose the option that starts with File and press OK. This will limit the inspection to just
MainActivity.kt
. - A window with Inspection Results appears at the bottom.
- Click on the grey triangles next to Kotlin and then next to Style issues until you see two messages. The first says Class member can have ‘private’ visibility.
- Click on the grey triangles until you see the message Property ‘binding’ could be private and click on the message. Android Studio displays some of the code in
MainActivity
and highlights thebinding
variable.