Android Kotlin Fundamentals Course 코드랩 하면서 노트.

Useful Shortcuts

Vector drawables

A part of Android Jetpack.

As default, android app will contain bitmap version of the drawables in lower than API 21. Vector drawables support in oldver vrsions of Android, back to API 7.

Add this line at defaultConfig in build.gradle (Module:app).

vectorDrawables.useSupportLibrary = true

Click Sync Now button.

Open activity_main.xml layout file and add this namespace at <LinearLayout> tag.

xmlns:app="http://schemas.android.com/apk/res-auto"

Change android:src in <ImageView> to app:srcCompat.

app:srcCompat="@drawable/hello_world"

Note: app namespace is for attributes that come from custom code or libraries.

Use findViewById once

Define member with lateint keyword.

lateinit var diceImage : ImageView

Init the member in onCreate().

diceImage = findViewById(R.id.dice_image)

API Levels

Architecture of the basic activity template

Resources

Docs

Android team

Extract the style

  1. Right-click the component from the component tree and select Refactor > Extract Style.
  2. Fill in the form and save it.
  3. Use named style in activity xml file via style attribute
style="@style/NameStyle"

The style is defined in styles.xml in res.

Changing android:id

When android:id of the component need to be changed, right-click and select Refector > Rename.

Density-independent pixels

Support different pixel densities

Unit conversion: px = dp * (dpi / 160)

Threshold must be needed for dp conversion to pixel. Scale factor is in DisplayMetrics.density.

Autofill

Optimize your app for autofill

Show/Hide the keyboard

// Hide
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)

// Set focus
editText.requestFocus()
// Show
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(editText, 0)

Attach onClick events

Constraints

In the activity page, a magnet icon is for Autoconnect feature.

Use Constraint Widget in Attributes section of the activity design page.

the type of the constraint

Chains

A chain is a group of views that are linked to each other with bidirectional constraints.

Chain styles

set the layout_constraintHorizontal_chainStyle or the layout_constraintVertical_chainStyle.

Baseline constraint

The baseline constraint aligns the baseline of a view's text with the baseline of another view's text. Right click on the component and select show baseline.

<Button
   android:id="@+id/buttonB"
   ...   
   android:text="B"
   app:layout_constraintBaseline_toBaselineOf="@+id/buttonA" />

Design-Time attributes

Only for the layout design. Design-time attributes are prefixed with the tools namespace. e.g. tools:layout_editor_absoluteY, tools:text

Data binding

Eliminate findViewById() using binding objects. Benefits:

Setup

// build.gradle (Module: app)
android {
  // ...
  buildFeatures {
    dataBinding true
  }
  // ...
}
// Then sync

Add <layout> at the outermost tag around the layout xml file. Then, move all xml namespaces to the <layout>. e.g.

<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">

Usage

Add the type of binding in the activity class. For activity_main, the class will be ActivityMainBinding.

private lateinit var binding: ActivityMainBinding

It need to import something like import <your.project>.databinding.ActivityMainBinding.

Then, use DataBindingUtil.setContentView() and remove the previous setContentView().

// import androidx.databinding.DataBindingUtil

binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

Now, the binding object holds all views in the layout.

binding.doneButton.setOnClickListener {}
binding.apply { // kotlinize the function
  nicknameText.text = nicknameEdit.text.toString()
  nicknameEdit.visibility = View.GONE
}

Data binding to display data

Create the data class in the package.

// MyName.kt
data class MyName(var name: String = "", var nickname: String = "")

Add data to the layout. Open activity xml file and add <data> right under the <layout>. Then, add <variable> tag with name and package path. (package name + variable name)

<layout ...>
  <data>
    <variable
      name="myName"
      type="com.example.android.aboutme.MyName" />
  </data>
  <!-- ... -->
</layout>

@={} is a directive to get the data that is referenced inside the curly braces. Replace the string like below:

<TextView android:text="@string/name" />
<TextView android:text="@={myName.name}" />

Create the data in the activity file.

// MainActivity.kt
private val myName: MyName = MyName("Edward Kim")

Then, binding the object with the view using binding.

// in onCreate()
binding.myName = myName

Manipulate the binding object so that the updated data will reflect to the view.

android:text="@{myName.nickname}"
binding.apply {
  myName?.nickname = nicknameEdit.text.toString()
  invalidateAll() // UI is refreshed with the new value
  // ...
}

Read more

Fragments

A Fragment represents a behavior or a protion of user interface in an activity. Modular section of an activity like a "sub-activity".

"Inflate the Fragment's view" is equivalant to using setContentView() for an Activity.

class TitleFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        // create a binding object and inflate the Fragment's view
        val binding = DataBindingUtil.inflate<FragmentTitleBinding>(inflater,
                R.layout.fragment_title, container, false)
        return binding.root
    }
}

Add the new fragment to the main layout file.

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <fragment
      android:id="@+id/titleFragment"
      android:name="com.example.android.navigation.TitleFragment"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      />
  </LinearLayout>
</layout>

Navigation

Preparation

Add dependencies for the navigation library.

// build.gradle (project-level)
ext {
  // ...
  navigationVersion = "2.3.0"
  // ...
}
// build.gradle (module-level)
dependencies {
  // ...
  implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
  implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
  // ...
}

Sync and rebuild.

Add a navigation graph to the project. right-click the res folder and select New > Android Resource File.

Set navigation as a file name and Navigation as a resource type, then okay.

Open res/navigation/navigation.xml with the navigation editor.

Create the NavHostFragment

NavHostFragment acts as a host for the fragments in a navigation graph.

Open layout xml file and add NavHostFragment like below.

<fragment
  android:id="@+id/myNavHostFragment"
  android:name="androidx.navigation.fragment.NavHostFragment"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  app:navGraph="@navigation/navigation"
  app:defaultNavHost="true" />

Add fragments to the navigation graph

Open navigation.xml and add new destinations. If the preview is not showing, check tools:layout.

Then drag the dot (circular connection point) on the preview and drop to the destination. Check the ID of the action. (e.g. action_titleFragment_to_gameFragment)

Add the button click event in the Fragment inside the onCreateView() method.

binding.playButton.setOnClickListener { view: View ->
    view.findNavController().navigate(R.id.action_titleFragment_to_gameFragment)
}

Conditional navigation

Add two fragments to the navigation graph.

Drag the circular connection point to the conditional fragments.

Then, add the code like:

// at onCreateView() in GameFragment.kt
// find the appropriate position...

view.findNavController().navigate(R.id.action_gameFragment_to_gameWonFragment)

Change the back button's destination

Control the back stack by setting the "pop" behavior in actions on the navigation editor.

Add up button in the App bar

(= action bar)

Designing Back and Up navigation

Back button is for the back stack, up button is for the screen hierachy.

Use NavigationUI.

// onCreate() in MainActivity.kt
val navController = this.findNavController(R.id.myNavHostFragment)
NavigationUI.setupActionBarWithNavController(this, navController)
// in MainActivity.kt
override fun onSupportNavigateUp(): Boolean {
  val navController = this.findNavController(R.id.myNavHostFragment)
  return navController.navigateUp()
}

Add an options menu

Add new destination to the navigation graph.

Add a options-menu to the project. right-click the res folder and select New > Android Resource File.

Set options_menu as a file name and Menu as a resource type, then okay.

Open options_menu.xml.

Add Menu Item from Plaette to the component tree. Set the id of item to the same name as the target fragment e.g. aboutFragment.

Add onClick handler.

// TitleFragment.kt
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                         savedInstanceState: Bundle?): View? {
  // ...
  setHasOptionsMenu(true) // Add this line
  return binding.root
}

// Add the options menu and inflate the menu resource file
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
  super.onCreateOptionsMenu(menu, inflater)
  inflater.inflate(R.menu.options_menu, menu)
}

// Add onOptionsItemSelected() for the navigation
// It uses the id of the selected item
override fun onOptionsItemSelected(item: MenuItem): Boolean {
     return NavigationUI.
            onNavDestinationSelected(item,requireView().findNavController())
            || super.onOptionsItemSelected(item)
}

Add the navigation drawer

Drawer appears when the user swipes edge to edge or tap the nav drawer button/hamburger icon. It is a part of the material components for Android.

// build.gradle (app-level)
dependencies {
  // ...
  implementation "com.google.android.material:material:$supportlibVersion"
  // ...
}

// then sync!

Add a drawer to the project. right-click the res folder and select New > Android Resource File.

Set navdrawer_menu as a file name and Menu as a resource type, then okay.

Open navdarwer_menu and add menu items. Note: Match the ID for the menu item as for the destination Fragment.

Add <DrawerLayout> and wrap the entire layout in activity xml file. Also, add NavigationView before closing it.

<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
  <androidx.drawerlayout.widget.DrawerLayout
       android:id="@+id/drawerLayout"
       android:layout_width="match_parent"
       android:layout_height="match_parent">

    <LinearLayout>
      <!-- ... -->
    </LinearLayout>
    <com.google.android.material.navigation.NavigationView
      android:id="@+id/navView"
      android:layout_width="wrap_content"
      android:layout_height="match_parent"
      android:layout_gravity="start"
      app:headerLayout="@layout/nav_header"
      app:menu="@menu/navdrawer_menu" />
  </androidx.drawerlayout.widget.DrawerLayout>
</layout>

For swaping, connect the drawer to the navigation controller:

// onCreate() in MainActivity.kt
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
       this, R.layout.activity_main)

NavigationUI.setupWithNavController(binding.navView, navController)

For drawer button, add the code bleow:

// in MainActivity.kt
private lateinit var drawerLayout: DrawerLayout

// onCreate()
drawerLayout = binding.drawerLayout
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
  // add it as a third param

To make the Up button work with the drawer button:

// onSupportnavigateUp() in MainActivity.kt
override fun onSupportNavigateUp(): Boolean {
  val navController = this.findNavController(R.id.myNavHostFragment)
  return NavigationUI.navigateUp(navController, drawerLayout)
}

다음 챕터: Android Kotlin Fundamentals: 03.3 Start an external Activity

색상을 바꿔요

눈에 편한 색상을 골라보세요 :)

Darkreader 플러그인으로 선택한 색상이 제대로 표시되지 않을 수 있습니다.