saveData() 는 onCreate() 함수 외부이자, InputActivity 클래스의 내부에 위치시킨다.
간단히 말하면 코드 간결성을 위해, onCreate() 에서 기능을 빼내서 따로 구현한 것이다.
saveData() 함수 내부에서는 아래와 같은 5가지 정보를 저장한다.
이름 2) 생년월일 3) 혈액형 4) 비상연락처 5) 주의사항
이런 정보를 저장하는 방법을 알아보기 위해
SharedPreferences API 에 대해서 공부하자.
1-3) SharedPrereferences ( saveData() 작성 )
SharedPreferences 에 대해 알아보기 위해
안드로이드 공식 문서를 살펴보자.
저장하려는 키-값 컬렉션이 비교적 작은 경우
SharedPreferences API를 사용해야 한다. 키-값 데이터 저장.
SharedPreferences 는 하나의 파일로 저장된다.
어떤 파일로 저장이 될 지 지정할 수 있다.
SharedPreferences( “파일이름”, Context.모드 )
Context.MODE_PRIVATE
이 파일을 생성한 앱에서만 접근할 수 있다.
chapter4 에서만 해당 파일에 접근할 수 있다.
InputActivity.kt
// 저장을 위해 sharedPreference 를 사용하자.
private fun saveData() {
val editor = getSharedPreferences("userInformation", Context.MODE_PRIVATE).edit()
editor.putString("name", binding.nameEditText.text.toString())
editor.putString("birthDate", binding.birthdateTextView.text.toString())
editor.putString("bloodType",)
editor.putString("emergencyContact", binding.emergencyContactEditText.text.toString())
editor.putString("warning", )
editor.apply()
// 저장되었습니다. 알림 띄우기
// 마찬가지로 show()를 붙여주어야 화면에 나타남
Toast.makeText(this, "저장을 완료했습니다.", Toast.LENGTH_SHORT).show()
}
1-3-1) commit 과 apply
editor 에서 apply() 함수를 호출할 수도 있고
commit() 함수를 호출할 수도 있다.
Consider using apply() instead, commit writes its data to persistent storate immediately, whereas apply will handle it in the background.
commit 같은 경우는
이 commit 을 가지고 있는 스레드에서
현재 실행 중인 스레드의 동작을 막은 후 저장을 하고
저장을 완료한 뒤에 다시 이전 스레드가 동작할 수 있다.
스레드는 작업이 이루어지는 하나의 공간
이 데이터를 저장하는 동안, 사용자는 아무 동작도 할 수 없다.
데이터를 저장하는 동안, 사용자의 동작을 막지 않으려면
현재 사용하고 있는 메인스레드가 아닌
다른 스레드를 열어서 그 스레드에서 처리를 해야한다.
다른 스레드를 열어주고 commit을 하거나
apply 함수를 호출한다.
apply 는 commit 과 다르게
해당 스레드에서 동작이 이루어지는 게 아니고
Async 하게 (비동기적으로) 동작이 이루어진다.
저장을 할 때, 다른 스레드를 열어서
그 스레드에서 저장을 하기 때문에
지금 사용하고 있는 스레드의 동작을 막지 않으므로
가능한 apply 를 사용하는 것이 좋다.
1-3-2) scope function 을 통한 반복 줄이기
InputActivity.kt
// 저장을 위해 sharedPreference 를 사용하자.
private fun saveData() {
val editor = getSharedPreferences("userInformation", Context.MODE_PRIVATE).edit()
editor.putString("name", binding.nameEditText.text.toString())
editor.putString("birthDate", binding.birthdateTextView.text.toString())
editor.putString("bloodType",)
editor.putString("emergencyContact", binding.emergencyContactEditText.text.toString())
editor.putString("warning", )
editor.apply()
// 저장되었습니다. 알림 띄우기
// 마찬가지로 show()를 붙여주어야 화면에 나타남
Toast.makeText(this, "저장을 완료했습니다.", Toast.LENGTH_SHORT).show()
}
editor 가 계속 반복이 된다.
scope function 을 이용해서 반복을 줄여보자.
apply, run 등 많은 scope function 중에서 with 를 사용할 것이다.
InputActivity.kt
// 저장을 위해 sharedPreference 를 사용하자.
private fun saveData() {
with(getSharedPreferences("userInformation", Context.MODE_PRIVATE).edit()) {
putString("name", binding.nameEditText.text.toString())
putString("birthDate", binding.birthdateTextView.text.toString())
putString("bloodType",getBloodType())
putString("emergencyContact", binding.emergencyContactEditText.text.toString())
putString("warning", getWarning())
apply()
}
// 저장되었습니다. 알림 띄우기
// 마찬가지로 show()를 붙여주어야 화면에 나타남
Toast.makeText(this, "저장을 완료했습니다.", Toast.LENGTH_SHORT).show()
}
1-3-3) 하드코딩 대신 상수 선언
getSharedPreferences(“userInformation”,… )
key 값인 “userInformation” 을 하드코딩하다보면 실수할 수 있다.
자바에서는 이 key 값들을 하나의 static 변수로 만드는 것이 흔하다.
코틀린에서는 이러한 static 변수를 파일에 선언할 수 있다.
Kotlin Class 로 static 변수들을 담는 Constant.kt 를 만들 수 있다.
자바에서 상수를 선언할 때
snake rule 을 따르고 capital letter 을 사용하는 것처럼
코틀린에서도 동일하게 작성하면 된다.
const val 키워드로 static 변수를 선언하자.
Constant.kt
package com.part1.chapter4
const val USER_INFORMATION = "userInformation"
const val NAME = "name"
const val BIRTHDATE = "birthdate"
const val BLOOD_TYPE = "bloodType"
const val EMERGENCY_CONTACT = "emergencyContact"
const val WARNING = "warning"
이제 하드코딩한 문자열 대신 상수값을 인자로 변경하자.
InputActivity.kt
// 저장을 위해 sharedPreference 를 사용하자.
private fun saveData() {
with(getSharedPreferences("userInformation", Context.MODE_PRIVATE).edit()) {
putString(NAME, binding.nameEditText.text.toString())
putString(BIRTHDATE, binding.birthdateTextView.text.toString())
putString(BLOOD_TYPE,getBloodType())
putString(EMERGENCY_CONTACT, binding.emergencyContactEditText.text.toString())
putString(WARNING, getWarning())
apply()
}
// 저장되었습니다. 알림 띄우기
// 마찬가지로 show()를 붙여주어야 화면에 나타남
Toast.makeText(this, "저장을 완료했습니다.", Toast.LENGTH_SHORT).show()
}
package com.part1.chapter4
import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.core.view.isVisible
import com.part1.chapter4.databinding.ActivityInputBinding
import com.part1.chapter4.databinding.ActivityMainBinding
import java.util.zip.Inflater
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Intent
// 의도: MainActivity 에서 편집 버튼을 누르면
// InputActivity 로 이동한다.
binding.goInputActivityButton.setOnClickListener {
// Intent 가 시작하는 곳의 context 를 알려주어야 함 ( MainActivity 가 가진 context)
// InputActivity 를 call 하겠다는 의도
val intent = Intent(this, InputActivity::class.java)
// 다른 Activity 실행
startActivity(intent)
}
}
override fun onResume() {
super.onResume()
getDataUiUpdate() // onCreate() 가 아닌, onResume() 에서 호출돼야 함.
// onCreate() 는 액티비티가 최초실행될 때
// 액티비티 종류된 후 다른 액티비티가 실행될 때는
// onCreate() 가 아닌 onResume() 이 호출되므로
}
// 데이터를 가져와서 UI 업데이트를 함께 했다.
// 두 가지 기능을 나누어서 하는 것이 좋다.
// 함수 하나에는 하나의 기능이 있는 것이 좋다.
private fun getDataUiUpdate() {
with(getSharedPreferences(USER_INFORMATION, Context.MODE_PRIVATE)) {
binding.nameValueTextView.text = getString(NAME, "미정")
binding.birthdateValueTextView.text = getString(BIRTHDATE, "미정")
binding.bloodtypeValueTextView.text = getString(BLOOD_TYPE, "미정")
binding.emergencyContactValueTextView.text = getString(EMERGENCY_CONTACT, "미정")
val warning = getString(WARNING, "")
binding.warningTextView.isVisible = warning.isNullOrEmpty().not()
binding.warningValueTextView.isVisible = warning.isNullOrEmpty().not()
if(!warning.isNullOrEmpty()){
binding.warningValueTextView.text = warning
}
}
}
}
InputActivity.kt 에서 setContentView() 에서 뷰를 셋팅해아하므로
res > layout 에서 activity_input.xml 파일도 추가하자.
setContentView(R.layout.activity_input)
2-3) gradle : app 에 viewBinding 설정
app 의 하위 수준에 아래 코드 삽입 후, Sync Now
viewBinding {
enabled = true
}
2-4) Activity.kt
binding 변수 선언
뷰를 생성하는 inflater 담기
setContentView
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
3) Intent
Intent
개별 구성요소 (예: 2개의 Activity) 사이의 런타임 바인딩을 제공하는 객체
Activity 와 Activity 사이의 화면전환을 할 수 있도록 하는 역할
그 안에 데이터도 넣을 수 있음
각 구성요소들이 메시지나 의도를 주고받을 때,
인텐트를 통해서 다른 앱을 실행할 수 있음
Intent 는 영어 뜻대로
우리가 하고자 하는 “의도” 를 담고 있는 객체
사용 예시
intent 를 만들고
그 내부에 정보를 넣고
startActivity로 액티비티를 시작한다.
3-1) MainActivity.kt
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Intent
// 의도: MainActivity 에서 편집 버튼을 누르면
// InputActivity 로 이동한다.
binding.goInputActivityButton.setOnClickListener {
// 1) this
// Intent 가 시작하는 곳의 context 를 알려주어야 함
// ( MainActivity 가 가진 context)
// 2) InputActivity::class.java
// InputActivity 를 call 하겠다는 의도
// [1] intent 만들기
val intent = Intent(this, InputActivity::class.java)
// [2] intent 에 데이터 넣기
intent.putExtra("intentMessage", "응급의료정보")
// [3] 다른 Activity 실행
startActivity(intent)
}
}
}
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Intent
// 의도: MainActivity 에서 편집 버튼을 누르면
// InputActivity 로 이동한다.
binding.goInputActivityButton.setOnClickListener {
// Intent 가 시작하는 곳의 context 를 알려주어야 함 ( MainActivity 가 가진 context)
// InputActivity 를 call 하겠다는 의도
val intent = Intent(this, InputActivity::class.java)
// intent 에 데이터 넣기
intent.putExtra("intentMessage", "응급의료정보")
// 다른 Activity 실행
startActivity(intent)
}
}
}
InputActivity.kt
class InputActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
// 데이터를 잘 받아오는지 확인하려면
val message = intent.getStringExtra("intentMessage") ?: "없음"
Log.d("intentMessage", message)
setContentView(R.layout.activity_input)
}
}
<!-- value 는 사용자의 입력값에 따라 길이가 변경됨 -->
<androidx.constraintlayout.widget.Guideline
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.4" />
<TextView
....
app:layout_constraintStart_toStartOf="@id/guideLine" />
3)maxLines 와 ellipsize
만약 최대 줄 개수를 지정해주고 싶다면,
maxLines를 지정해주자.
android:maxLines="1"
그리고 내용이 정해진 영역 내에 모두 표시될 수 없다면
‘ … ‘ 으로 표시되도록 하고 싶다면,
ellipsize 을 end로 설정하자.
android:ellipsize="end"
<TextView
...
android:text="김주철 이름을 길게 넣으면 어떻게 될까요?"
android:maxLines="1"
android:ellipsize="end"