Get user input in an app: Part 1

Tobey Yeh
Tobeylearns
Published in
8 min readMay 17, 2021

Create XML layouts for Android

https://developer.android.com/codelabs/basic-android-kotlin-training-xml-layouts?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-kotlin-unit-2-pathway-1%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-training-xml-layouts#3

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.

  1. 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, and okay_service to make the names more descriptive.

Now verify the string resources you just added.

  1. If the Project window isn’t showing, click the Project tab on the left side of the window.
  2. 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.

  1. In activity_main.xml, choose Edit > Select All.
  2. 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.

https://developer.android.com/codelabs/basic-android-kotlin-training-xml-layouts?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-kotlin-unit-2-pathway-1%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-training-xml-layouts#7

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

  1. Open the app’s build.gradle file ( Gradle Scripts > build.gradle (Module: Tip_Time.app) )
  2. In the android section, add the following lines:
buildFeatures {
viewBinding = true
}
  1. Note the message Gradle files have changed since last project sync.
  2. 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.

  1. Open MainActivity.kt (app > java > com.example.tiptime > MainActivity).
  2. Replace all of the existing code for MainActivity class with this code to setup the MainActivity to use view binding:
  3. Open MainActivity.kt (app > java > com.example.tiptime > MainActivity).
  4. Replace all of the existing code for MainActivity class with this code to setup the MainActivity 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)
}
}
  1. 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.

  1. This line initializes the binding object which you'll use to access Views in the activity_main.xml layout.
binding = ActivityMainBinding.inflate(layoutInflater)
  1. 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.

  1. Add an if statement that assigns the ceiling of the tip to the tip variable if roundUp 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.

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

This gives you a number formatter you can use to format numbers as currency.

  1. Using the number formatter, chain a call to the format() method with the tip, and assign the result to a variable called formattedTip.
val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
  1. 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.

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

The %s is where the formatted currency will be inserted.

  1. Now set the text of the tipResult. Back in the calculateTip() method in MainActivity.kt, call getString(R.string.tip_amount, formattedTip) and assign that to the text attribute of the tip result TextView.
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.

  1. In calculateTip(), change the line that declares the cost variable to call toDoubleOrNull() instead of calling toDouble().
val cost = stringInTextField.toDoubleOrNull()
  1. After that line, add a statement to check if cost is null, and if so to return from the method. The return 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 a return 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.

  1. With MainActivity.kt still open, choose Analyze > Inspect Code... in the menus. A dialog box called Specify Inspection Scope appears.
  1. Choose the option that starts with File and press OK. This will limit the inspection to just MainActivity.kt.
  2. A window with Inspection Results appears at the bottom.
  3. 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.
  1. 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 the binding variable.

https://developer.android.com/codelabs/basic-android-kotlin-training-tip-calculator?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-kotlin-unit-2-pathway-1%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-training-tip-calculator#6

--

--