*비밀 다이어리
-비밀번호가 설정되어 있는 다이어리
-비밀번호는 변경 가능함
*구조
-앱 실행
-0~9 범위를 가지고 있는 3개 <NumberPicker/> 비밀번호 선택
--초기 비밀번호 기본 값은 000
-OPEN 버튼 클릭
--저장되어있는 비밀번호 일치 시 : 텍스트를 쓸 수 있는 액티비티로 이동, 넘버피커 초기화(000)
--저장되어있는 비밀번호 불일치 시 : 알림창 생성(AlertDialog)
-비밀번호 변경 버튼 클릭(OPEN버튼 하단 작은 버튼)
--저장되어있는 비밀번호 일치 시 : 알림창 생성(Toast), 버튼의 색상이 빨강으로 변경
--저장되어있는 비밀번호 불일치 시 : 알림창 생성(AlertDialog)
-변경할 비밀번호 설정 후 다시 변경버튼 클릭
--알림창 생성(AlertDialog), 버튼의 색상이 원래 색상으로 변경, 넘버피커 초기화(000)
-어플을 종료해도 비밀번호와 다이어리 내용은 내부 저장소에 저장되며, 다시 어플 실행 시 데이터는 계속 존재함
-MainActivity.kt
/** 비밀 다이어리 만들기 **/
//1
//레이아웃 설정 : activity_main.xml
//넘버피커, 앱컴팻 버튼, 커스텀 폰트 적용
class MainActivity : AppCompatActivity() {
//2
//레이아웃 넘버피커를 가지고 있는 변수 선언하고
//최대, 최소값 설정 .apply{}
//뷰바인딩 사용없이 작성하기...
val numPicker1: NumberPicker by lazy {
findViewById<NumberPicker>(R.id.numPicker1)
.apply {
minValue = 0
maxValue = 9
}
}
val numPicker2: NumberPicker by lazy {
findViewById<NumberPicker>(R.id.numPicker2)
.apply {
minValue = 0
maxValue = 9
}
}
val numPicker3: NumberPicker by lazy {
findViewById<NumberPicker>(R.id.numPicker3)
.apply {
minValue = 0
maxValue = 9
}
}
//3
//오픈버튼과 패스워드 변경버튼 선언
val btnOpen: AppCompatButton by lazy {
findViewById<AppCompatButton>(R.id.btnOpen)
}
val btnChangePW: AppCompatButton by lazy {
findViewById<AppCompatButton>(R.id.btnChangePW)
}
//6-1
//비밀번호 변경 시 다른 위젯이 동작하지 못 하도록
//전역변수 생성
var changePWMode = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//4
//넘버피커 초기화하여 메모리에 할당
numPicker1
numPicker2
numPicker3
//5
//오픈버튼 동작 정의
//버튼을 눌렀을 때 저장되어있는 패스워드 값을 가져와 넘버피커 1,2,3의 숫자들과 비교
//SharedPreference를 사용하여 기기에 저장되어있는 패스워드를 가져오기
btnOpen.setOnClickListener {
//6-1
//비밀번호 변경 시 다른 위젯이 동작하지 못 하도록 메시지 띄우고
//onCreate()에서 나가는 것인지 클릭리스너 람다에서 나가는 것인지 리턴 함수를 통해 명시
//다시 리스너로 돌아가기...
if (changePWMode){
Toast.makeText(this, "비밀번호 변경 중입니다...", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
//5-1
//SharedPreference를 사용해 password라는 파일에 저장되어 있는 값 접근하기
//모드는 공유하지 않고 이 앱에서만 사용할 수 있는 Context.MODE_PRIVATE 모드 정의
val passwordPreferences = getSharedPreferences("password", Context.MODE_PRIVATE)
//5-2
//현재 넘버피커에 설정되어있는 값 받아오기
//문자열 타입으로 값 가져오기
//val passwordFromUser = numPicker1.value.toString() + numPicker2.value.toString() + numPicker2.value.toString()
val passwordFromUser = "${numPicker1.value}${numPicker2.value}${numPicker3.value}"
//5-3
//현재 넘버피커에 설정된 값(passwordFromUser)과
//SharedPreference를 사용해 특정 파일에 접근한 값(passwordPreferences)을 비교
//get메서드 입력값의 파라미터는 기본값을 지정할 수 있음
//기본값(defaultValue)를 지정하면 해당 키의 데이터가 없을 때 지정한 기본값 반환
//앱 처음 실행하면 데이터가 없으므로 000이 비밀번호의 기본값으로 설정된다고 보면 됨...
if (passwordPreferences.getString("password", "000").equals(passwordFromUser)){
//5-4
//만약 저장되어있는 패스워드가 현재 패스워드와 같을 때
//패스워드 성공
//다음 페이지인 다이어리 페이지로 이동
//인텐트를 통해 넘기기...
startActivity(Intent(this,DiaryActivity::class.java))
//넘버피커 값 초기화
numPicker1.value = 0
numPicker2.value = 0
numPicker3.value = 0
}else{
//5-5
//패스워드 실패
//실패 다이얼로그 생성
showErrorDialog()
}
}//btnOpen
//6
//비밀번호 변경버튼 정의
//비밀번호 변경할 때 다른 동작을 하지 못 하도록 예외처리하기...
//false를 가지고 있는 전역변수를 생성하여 사용함
btnChangePW.setOnClickListener {
//6-2
//SharedPreference를 사용해 password라는 파일에 저장되어 있는 값 접근하는 변수생성
val passwordPreferences = getSharedPreferences("password", Context.MODE_PRIVATE)
//6-2-1
//현재 피커에 설정한 번호를 변수생성하여 문자열로 저장
val passwordFromUser = "${numPicker1.value}${numPicker2.value}${numPicker3.value}"
//6-3
//만약 비밀번호 변경모드라면 현재 넘버피커에 설정한 번호를
//SharedPreference를 사용해 password라는 파일을 읽어와 .edit하여 값 변경
if (changePWMode){
//6-3-1
//파일에 저장되어 있는 값을 읽어와 변경하기
//자바에서는 .edit()함수를 이용해 .putString()형식으로 변경했으나
//코틀린 에서는 edit함수가 이미 정의 되어 있어 쉽게 사용할 수 있음...
//정의되어있는 edit함수를 람다 형식으로 사용하기...
passwordPreferences.edit{
//6-3-2
//저장파일에 문자열로 저장된 번호 덮어쓰기
putString("password", passwordFromUser)
//6-3-3
//저장하는 값이 큰 경우에
//모든 작업이 끝난 후 저장하고 UI스레드를 사용하는 commit()사용하면 화면이 멈출 수 있으므로
//apply()를 하여 비동기적으로 바로 처리함
//현재 이 앱은 저장하는 값이 크지 않으므로 commit()처리를 하였음...
commit()
}
//6-3-4
//파일의 비밀번호 변경 후 비밀번호 변경모드 다시 false로 하고
//빨강색으로 활성화 되어있던 변경 버튼의 색상을 되돌려 놓기
changePWMode = false
btnChangePW.setBackgroundColor(getColor(R.color.custom_black))
AlertDialog.Builder(this)
.setMessage("비밀번호가 변경되었습니다.")
.setNegativeButton("확인"){_,_ -> }
.create().show()
//넘버피커 값 초기화
numPicker1.value = 0
numPicker2.value = 0
numPicker3.value = 0
//6-3-1 ***
//위 passwordPreferences.edit{}코드를 더 편리하게 사용하는 방법
//commit()의 실행 명령을 할 때... 꼭 명령을 해줘야 수정이 되는데
//작성하다보면 이 부분을 놓치는 경우가 있음... 그래서
//코틀린 에서는 실행 명령어를 까먹어도 이미 적용되어 있는
//명령어를 사용할 수 있게 메서드를 정의해 두었음...
// passwordPreferences.edit(true){
// val passwordFromUser = "${numPicker1.value}${numPicker2.value}${numPicker3.value}"
// putString("password", passwordFromUser)
// }
}else{
//6-4
//만약 비밀번호 변경모드가 활성화 되어 있지 않다면
//활성화 될 때(::) 비밀번호가 저장되어있는 비밀번호와 맞는지 체크하여 활성화 하기...
//6-4-1
//저장되어있는 값과 현재 값 비교하여 실행
if (passwordPreferences.getString("password", "000").equals(passwordFromUser)){
//6-4-2
//비밀번호가 일치 한다면 비밀번호 모드 true로 변경하고
//패스워드 변경모드가 활성화 되었음을 알려주는 토스트 생성
changePWMode = true
Toast.makeText(this, "변경할 비밀번호를 입력하세요.", Toast.LENGTH_SHORT).show()
//6-4-3
//변경버튼이 활성화되었으므로 버튼 백그라운드 색상을 빨간색으로 변경
btnChangePW.setBackgroundColor(Color.RED)
}else{
//6-4-4
//비밀번호가 일치하지 않다면
//실패 다이얼로그 생성
showErrorDialog()
}
}
}//btnChangePW
}//onCreate
//7
//비밀번호가 일치하지 않을 때 생성되는 다이얼로그
//오픈버튼과 비밀번호 변경 버튼을 눌렀을 때
//저장되어있는 번호와 현재 넘버피커에 설정한 번호를 비교하여
//일치하지 않았을 경우 같은 메시지를 보여줌
//중복되는 코드를 최소화 하기 위해 메서드를 생성하여
//각 버튼에 적용!
fun showErrorDialog(){
//7-1
//Builder()를 하여 값을 세팅할 수 있음
//실패창의 제목과 메시지 설정
//닫기(PositiveButton)버튼 설정,
//포지티브버튼은 두 개의 파라미터를 받기 때문에
//람다로 두개의 파라미터와 내용 생략하기... (코드 Cmd + 클릭하여 상세보기 가능)
//항상 .create().show()를 하여 다이얼로그 생성하기...
AlertDialog.Builder(this)
.setTitle("⚠️ Error")
.setMessage("비밀번호가 일치하지 않습니다.")
.setPositiveButton("확인"){ _, _ ->}
.create().show()
}//showErrorDialog()
}//MainActivity
-DiaryActivity.kt
//1
//액티비티 생성 후 매니페스트에 등록필수
class DiaryActivity : AppCompatActivity() {
//4-3
//메인 스레드에 연결되어 있는 핸들러 생성
val handler = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_diary)
//2
//다이어리 액티비티의 뷰가 실행되었을 때
//내부 저장소에서 저장되어있는 내용을 가져와 화면 에디트텍스트에 보여주기
//SharedPreferences사용하여 내부 저장소 파일에 데이터 저장...
//앞에서 파일이름을 password로 하여 같이 저장해도 상관 없지만
//다이어리 내용을 가지고 있어야 할 파일명과 맞지 않이 헷갈릴 수 있기때문에
//새 파일명을 지정해 데이터 저장하여 사용할 것임...
//2-1
//레이아웃의 에디트텍스트 연결
val etContent = findViewById<EditText>(R.id.etContent)
//2-2
//getSharedPreferences를 하여 컨텐츠파일의 내용 가져와 변수에 저장
val contentPreferences = getSharedPreferences("content", Context.MODE_PRIVATE)
//2-3
//에디트텍스트에 가져온 내용을 문자열로 저장하기
//getString(파일명, 기본값)
etContent.setText(contentPreferences.getString("content", ""))
//4
//다이어리 텍스트가 한 글자씩 변경될 때마다
//내용을 저장을 위해 계속 실행되기 때문에 실행 낭비가 됨
//효율성을 높이기 위해 글 작성 중 지정 시간동안 멈출 때마다 저장되도록
//백그라운드에서 작동하는 스레드 기능 이용하기
//4-1
//백그라운드 스레드에 넣을 때 Runnable 인터페이스 사용***
val runnable = Runnable {
//4-2
//저장소에 내용을 비동기(apply)로 저장하기
contentPreferences.edit {
putString("content", etContent.text.toString())
}
//4-3
//새로 생성한 백그라운드 스레드는 UI에 접근할 수 없기 때문에
//Handler를 사용하여 메인스레드와 연결하여 UI에 접근하여 수정할 수 있음
//전역변수로 핸들러 생성...
//로그로 텍스트 저장이 잘 되는지 확인
Log.d("DiaryActivity", "TextSaved ${etContent.text.toString()}")
}//runnable
//3
//다이어리 컨텐츠의 텍스트 내용이 변경될 때마다
//변경된 내용을 저장하기 위해 호출하는 기능 사용
//.addTextChangedListener{}
etContent.addTextChangedListener {
//5
//백그라운드 스레드에서 일어나는 기능 구현하고
//비동기로 저장한 내용을 핸들러를 이용해 메인과 백그라운드 연결 후
//Hadler기능 중 postDelay기능을 사용하여
//지정된 시간 후에 백그라운드 스레드의 기능이 구현되도록 설정
//0.5초 이내에 텍스트 체인지가 계속 일어나고 있다면
//runnable의 동작 실행이 되지 않도록 .removeCallbacks()
//만약 0.5초 이상 텍스트가 변경되지 않았다면
//핸들러를 이용해 runnable의 동작이 수행되면서 내부저장소에 저장됨
//로그로 텍스트 변경을 잘 읽을 수 있는지 확인
Log.d("DiaryActivity", "TextChanged :: $it")
//5-1
//이전에 백그라운드스레드(runnable)이 진행되고 있다면
//지우고 다시 실행되도록...
handler.removeCallbacks(runnable)
//5-2
//0.5초(500밀리초) 후에 핸들러를 이용하여 백그라운드 스레드(변수 runnable)기능 동작
handler.postDelayed(runnable, 500)
}
}//onCreate
}//DiaryActivity
-결과
*사용코드 다시보기...
-NumberPicker 사용
--넘버피커 가로로 붙어있게 설정 : app:layout_constraintHorizontal_chainStyle="packed"
-CustomFont 사용
--res - New - Android Resource Directory - name : font - OK
-Theme 툴바 삭제(NoActionBar)
--material테마 사용중... 새로운 테마 생성하여 AndroidManifest.xml에 적용
-AppCompatButton 사용
-android-ktx(Kotlin Android Extension)의 SharedPreference 사용
--SharedPreferences : 내부 저장소 이용(권한 설정 필요없음), 간단한 코드로 사용가능함
--참고 : https://heeyjinny.tistory.com/84
--참고사항 중 원래 SharedPreferences의 edit()함수 사용할 때 putString()을하여 변경했지만
--코틀린에서는 android-ktx기능을 이용해 미리 정의되어 있는 edit함수를 쉽게 사용할 수 있음...
-AlertDialog 사용 : .Builder로 타이틀, 메시지, 네거티브버튼 설정, .create().show()필수
-백그라운드 스레드 Runnable인터페이스로 생성
-Handler 사용
--Handler생성 : val handler = Handler(Looper.getMainLooper())
--Handler기능 사용 : .removecallBacks(), .postDelayed()
'Android App > Project(Kotlin)' 카테고리의 다른 글
[Code] 간단한 계산기 - Layout (0) | 2023.01.17 |
---|---|
[App] 로또 번호 추첨 (2) | 2022.12.31 |
[Code] 로또 번호 추첨 - Random(), list, set 사용 방법 (0) | 2022.12.30 |
[App] BMI(체질량지수)계산기 (0) | 2022.12.28 |