Arvutiteaduse instituut
  1. Kursused
  2. 2022/23 sügis
  3. Mobiilirakenduste arendus (LTAT.06.021)
EN
Logi sisse

Mobiilirakenduste arendus 2022/23 sügis

  • Main
  • Lectures
  • Labs
  • Homeworks & Assignments
  • Task submission
  • Extra Materials
  • Best Practices
  • Results
  • Quizes
  • Projects
    • Presentations & Report
    • Teams & Topics
    • Grading & Submission

Lab 2

Introduction

In this lab we will try our first steps with the Kotlin language and add basic interactive behaviour, such as click handling, to our UI.

  • Make sure you have an understanding of these Kotlin concepts, discussed during the lecture:
    • Mutable and immutable variable declaration (var vs val)
    • Null-safety, including safe calls and the !! operator
    • Inferred types

Exercise 1: In-code GUI creation

Let's try to define a basic UI Layout programmatically, in Kotlin, without XML.

In MainActivity.kt onCreate():

  • Create an instance of the LinearLayout class. The primary constructor expects a Context type argument, you can use the this keyword (which points to the current Activity instance).
    • Set it to be vertical by updating the value of its' orientation property to LinearLayout.VERTICAL
      • The expected value is a constant static property of the LinearLayout class. Use Android Studio's autocomplete (ctrl + space) or study the docs to see other constants of LinearLayout.
      • In this case, we need to set the orientation property to the value LinearLayout.
  • Let's add 10 buttons to the LinearLayout using a loop.( Feel free to try out different ways to achieve this with Kotlin, e.g. with for loops or using repeat )
    • In the loop, create a new Button instance, and use LinearLayout-s addView() function to add it to the layout.
  • Make the Activity display the LinearLayout object instead of the default XML resource (using setContentView(..) ).
    • Test to see if the 10 buttons appear.
  • Update the loop so that the attributes of each button are modified:
    • Set the text of each one to "Button X", where X is an integer representing current iteration number within the loop.
      • If you use Kotlin's repeat, you can get the iteration number using the it keyword within the loop.
    • Set the background of the buttons to different colours.
      • You can update the color with setBackgroundColor( .. ), where the argument is a static constant of Color class, e.g. Color.BLUE
      • Use Kotlin's "when" statement and the iteration number to switch between colours. For example, if you used "repeat" for the loop:
            repeat (10) {
    
                // ... code to create button
    
                // "it" refers to the iteration number of the "repeat" statement
                    when (it % 3){
                        0 -> // set color 1
                        1 -> // set color 2
                        2 -> // set color 3
                    }
    
  • Try to refactor your code and use Kotlin scope functions (let or apply )
    • E.g. if you use apply:
 
        myLinearLayout.addView(
            TextView(this).apply { 
                // inside apply, "this" refers to the TextView
                text = "My button's text"
            }
            // apply returns the object it was called on
        )
  • Optional task: Try to use colour from your project resources (from colors.xml) instead of the Color class constants. You can do this with getColor( R.color.some_color_id ) for example.

Exercise 2: ConstraintLayout practice

Change back your MainActivity so that it uses the XML resource activity definition. Create the following ConstraintLayout configuration:

  1. Divide the UI vertically into 2 (2/3 and 1/3, as shown on picture), using a Guideline in percentage mode
    • Note: When you drop a Guideline from the Palette, by default it's not in percentage mode, toggle the mode by clicking on the circle next to the GuideLine, or change the attribute layout_constraintGuide_XXXX, where XXXX is "begin"/"end" or "percent".
  2. Top should have 1 TextView text
    • Vertically centered w.r.t. guideline
    • Horizontally constrained to edges of layout
  3. Add 2 Buttons to the bottom
    • Vertically centered
    • Horizontally constrained to parent edges

Exercise 3: Click behaviour

  • Set up a click handler for both buttons (see below for more details)
    • For the 1st button, use the programmatic approach and make the click update the button to show the number of times it has been clicked. Each time it gets clicked, it should also add a word to the TextView (hint: use TextView.append(..) or Kotlin repeat).
    • For the 2nd button, use XML onClick and make it restart the counter. (Also try clearing the TextView)

You can achieve this based on the below tips:

Getting a reference to a UI element defined in XML

  • you can access UI View Objects directly using its ID: e.g. if your XML object has an ID of myButton, use the findViewById() method, to get View objects that have been declared in XML resources or created in code.
    • val b:Button = findViewById(R.id.myButton)

Setting click listeners

  • To set a click listener from the code, you can use Button.setOnClickListener( .. )
    • button1.setOnClickListener { view: View? -> Log.i(TAG, "View: ${view?.id} was clicked!") }
  • Alternatively, you can specify android:onClick attribute of the XML object definition, setting its value to the name of the method to be called
    • The method has to be defined in the Activities' .kt file and accept a single argument of type View:
      • fun myHandler(view: View) { /** react to click */ }

Exercise 4: Touch listener

Let's add more fine-grained control of button presses using a touch listener. With a touch listener, we can differentiate the moment when the button is pressed down and when it is released.

The basic code for attaching a touch listener looks like this:

  my_button.setOnTouchListener({ view, motionEvent ->
            // do something useful
            true // return value - whether this listener consumed the event
        })

In the above, the return value indicates whether or not this listener consumed the event (if you set it false and also have a click listener for the same button, the click listener also gets triggered, whereas if the touch listener consumes the event, the click listener won't get triggered).

the motionEvent can be used to determine which kind of touch event it is. Let's handle two kinds of events: DOWN and UP using Kotlin's when construct:

    when (motionEvent.action){
                MotionEvent.ACTION_UP -> { /** TODO */ }
                MotionEvent.ACTION_DOWN -> { /** TODO */  }
                else -> { /** something else happened */}
            }
  • You may see a warning about not calling performClick(), you can ignore it for this lab, but here is a good explanation of the warning.
  • Replace the TextView in your ConstraintLayout with a vertical LinearLayout (in the XML layout file).
  • Using the onTouchListener, record how long the button was held down in milliseconds (use System.currentTimeMillis() )
  • Whenever the button is released and you have the press duration, add a new TextView to the LinearLayout
    • Make the TextView display the duration
  • To empty the LinearLayout (for the reset button), you can use my_layout.removeAllViews()

Exercise 5: Enabling View Binding

It can become bothersome and inefficient to manage UI manipulation using findViewById(..). For this purpose, View Binding is a technique which automatically generates classes representing XML-defined layouts, and provides references to the objects therein.

https://developer.android.com/topic/libraries/view-binding

By default, View Binding isn't enabled. Let's add it to our project:

Update your module level build.gradle file, by adding the below buildFeatures part:

android {
    ...
    buildFeatures {
        viewBinding true
    }
}

In MainActivity.kt, let's initialize the Binding object. Since we enabled View Binding in the Gradle file, a class is automatically created for each Activity. In our case, the class ActivityMainBinding has been generated . Let's use it in the code:

// Declare variable for storing the binding
private lateinit var binding: ActivityMainBinding

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

    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)  // binding.root refers to the root Layout

    ... // we can access UI elements from the binding, e.g. binding.myButton.text = "Button"

}

Note: the lateinit is a special Kotlin keyword which alllows us to have a non-nullable type variable, whose initial value is undefined. We define it later, in onCreate, but thanks to lateinit, the type is : ActivityMainBinding and not : ActivityMainBinding?

See here for more about lateinit

  • Arvutiteaduse instituut
  • Loodus- ja täppisteaduste valdkond
  • Tartu Ülikool
Tehniliste probleemide või küsimuste korral kirjuta:

Kursuse sisu ja korralduslike küsimustega pöörduge kursuse korraldajate poole.
Õppematerjalide varalised autoriõigused kuuluvad Tartu Ülikoolile. Õppematerjalide kasutamine on lubatud autoriõiguse seaduses ettenähtud teose vaba kasutamise eesmärkidel ja tingimustel. Õppematerjalide kasutamisel on kasutaja kohustatud viitama õppematerjalide autorile.
Õppematerjalide kasutamine muudel eesmärkidel on lubatud ainult Tartu Ülikooli eelneval kirjalikul nõusolekul.
Courses’i keskkonna kasutustingimused