Android/TOYTOY

비밀 다이어리 ( Handler, SharedPreference, AlertDialog)

sh1mj1 2022. 9. 5. 21:47

지난 시간에 배운 Thread, Handler, RunnableSharedPreference 을 실제 앱에서 사용하여 간단한 비밀 다이어리를 만들어볼 것이다.

아래는 지난 포스팅 링크: Android Thread, Handler, Runnable

[Android Thread, Handler, Runnable

이번에는 Android 에서의 Thread, Handler, Runnable 에 대해서 알아봅니다. 사실 작년에 프로젝트를 할 때도 자주 사용되는 기능이고, 또 클론 코딩 등 공부를 하면서도 자주 사용했지만 이번에도 누군가

sh1mj1-log.tistory.com](https://sh1mj1-log.tistory.com/10)

Android SharedPreference

[Android SharedPreference

SharedPrefernce Context.gethsharedPreference(String, int) 로 리턴된 preference을 접근하고 수정하는 인터페이스. 특정 집합에 대해 모든 클라이언트가 공유하는 이 클래스의 단일 인스턴스가 있다. 즉, 이것..

sh1mj1-log.tistory.com](https://sh1mj1-log.tistory.com/11)

이 포스팅은 https://fastcampus.co.kr/dev_online_iosappfinal 을 참고하여 만들어졌습니다.

사용한 주요 기능

  • Handler, Runnable
  • SharedPreference
  • background , backgroundTintMode
  • AlertDialog
    • Dialog 의 서브 클래스이다. dialog 창 안에 문자열, 버튼의 문자열과 클릭 이벤트 등을 설정할 수 있다.
    • Dialog 클래스를 상속받고, DialogInterface 을 구현한다.
    • 아래 사이트에서 다양한 속성을 확인할 수 있다.
    • https://developer.android.com/reference/android/app/AlertDialog

비밀 다이어리

앱 실행 시 TimePicker 을 사용하여 비밀 번호를 입력해야 다이어리 액티비티로 접근할 수 있도록 설정해준다
이 때 사용자가 비밀번호를 알고 있다면 비밀번호를 재설정할 수 있도록 하는 기능을 제공한다.

  • NumberPicker 3개를 나란히 배치, 그 외 Button, ConstraintLayout, TextView 배치
  • Theme 에서 새 Style 을 만들어서 상태바와 타이틀 바 없애기

MainActivity - 비밀번호 화면

그리고 DiaryActivity 에는 단순히 EditText 만 배치하였다. 추가로 더 많은 기능과 UI 을 사용자 친화적으로 만들 수 있지만 여러 기능을 테스트해보는 목적이기 때문에 많은 것은 넣지 않았다.

DiaryActivity - 입력하는 부분

xml 파일에서 UI 작업을 하던 중 헷갈리는 부분이 Background, Tint, TintMode 였다. 간단히 정리해보면 아래와 같다.

android:background

배경으로 사용하는 drawable. 완전히 drawable 소스를 참조할 수도 있고, 혹은 단순 색상(#ff0000)도 사용할 수 있다.

또 다른 리소스를 “@[package:]type/name” 형태로 참조하거나 theme 속성을 “?[package:]type/name” 형태로 참조할 수 있다.

android:backgroundTint

공식 문서 : Tint to apply to the background. 여기서 “tint” 은 “염색하다. 약간의 색깔을 넣다” 라는 뜻이다.

즉, 기존 background에 색상을 넣었다면 그 색에 색을 더 더하는 것이다.

android:backgroundTintMode

공식 문서: Blending mode used to apply the background tint.

background tint을 적용하는데 사용되는 혼합 모드.

아래 중 하나의 값을 가진다.

이 때 알파 채널은 16진수 색상 코드 8자리 중에서 가장 앞 두자리를 말한다.

만약 색깔이 #ff2344f2 라면 맨 앞 ff 이 알파 채널, 23 은 Red, 44 는 Green, f2 는 blue 이다.

#00000000 라면 투명색이 된다.

 

Constant Value Description
add 10 Combines the tint and drawable color and alpha channels, clamping the result to valid color values. Saturate(S + D).
tint 와 drawable 색상과 알파 채널을 결합하여 결과를 유효한 색 값으로 고정
multiply e Multiplies the color and alpha channels of the drawable with those of the tint. [Sa * Da, Sc * Dc].
색상과 tint의 drawable의 알파채널을 곱한다.
screen f [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc]
src_atop 9 The tint is drawn above the drawable, but with the drawable’s alpha channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc].
tint는 drawable 위에 그려지지만, drawable의 알파 채널은 결과를 마스킹.
src_in 5 The tint is masked by the alpha channel of the drawable. The drawable’s color channels are thrown out. [Sa * Da, Sc * Da].
tint는 drawable의 알파 채널에 의해 마스킹된다. drawable 색 채널이 튀어나와 있다.
src_over 3 The tint is drawn on top of the drawable. [Sa + (1 - Sa)Da, Rc = Sc + (1 - Sa)Dc].tint은 drawable 위에 그려진다.

비밀번호 저장, 변경 기능

NumberPicker 3개를 포함한 각 UI 들을 lazy 로 선언해준다.
그리고 changePwMode 라는 Boolean 변수를 생성해준다.

Properties

// Mark -Propeties/////////////////////////////////////////
    private val numberPicker0: NumberPicker by lazy {
        findViewById<NumberPicker?>(R.id.numberPicker0_Np).apply {
            minValue = 0
            maxValue = 9
        }
    }
    private val numberPicker1: NumberPicker by lazy { ... }
    private val numberPicker2: NumberPicker by lazy { ... }
    private val openBtn: AppCompatButton by lazy {
        findViewById(R.id.openBtn)
    }
    private val changePwBtn: AppCompatButton by lazy {
        findViewById(R.id.changePwBtn)
    }

    private var changePwMode = false

initOpenBtn

private fun initOpenBtn() {
        openBtn.setOnClickListener {
            if (changePwMode) {
                Toast.makeText(this, "비밀번호 변경 중입니다.", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }

            val pwPreference = getSharedPreferences("password", MODE_PRIVATE)
            val pwFromUser = "${numberPicker0.value}${numberPicker1.value}${numberPicker2.value}"

            if (pwPreference.getString("password", "000").equals(pwFromUser)) {
                // pw Succeed!
                Log.d(
                    "MainActivity",
                    "PW Succeed! , password: ${pwPreference.getString("password", "000")}"
                )
                startActivity(Intent(this, DiaryActivity::class.java))
            } else {
                showErrorDialog("실패!!", "비밀번호가 잘못되었습니다.")
            }
        }
    }

잠금화면에서 비밀번호를 입력 후 열기 버튼을 눌렀을 때. 만약 이 버튼을 누를 때 pw변경 모드이면, return.

getSharedPreference 를 통해 key 가 “password” 인 값을 가져온다. 이 때 MODE_PRIVATE 이기 때문에 앱 내에서만 파일에 엑세스 가능.

그래서 앱이 저장한 pwPreference 와 현재 화면에 표시되고 있는 번호 pwFromUser 을 선언.

조건문으로 앱이 저장한 비밀번호와 pwFromUser 비교. 만약 같다면 DiaryActivity 을 열고, 다르다면 미리 구현한 함수를 통해AlertDialog 을 연다.

showErrorDialog(title: String, message: String)

private fun showErrorDialog(title: String, message: String) {
        AlertDialog.Builder(this)
            .setTitle(title)
            .setMessage(message)
            // Event: Ramda Function
            .setPositiveButton("확인") { _, _ -> }
            .create()
            .show()
    }

https://developer.android.com/reference/android/app/AlertDialog
추가로 속성이 많음. 코드가 중복되며 길어서 함수로 따로 빼냈다.

initChangePwBtn

private fun initChangePwBtn() {
        changePwBtn.setOnClickListener {
            val pwPreference = getSharedPreferences("password", MODE_PRIVATE)
            val pwFromUser = "${numberPicker0.value}${numberPicker1.value}${numberPicker2.value}"
            if (changePwMode) {
                pwPreference.edit(true) {
                    putString("password", pwFromUser)
                }
                changePwMode = false
                changePwBtn.setBackgroundColor(Color.BLACK)
                Log.d("MainActivity", "ChangeDone!!! new PW : ${pwFromUser}")

            } else {
                // changePwMode 로 활성화
                // 비밀번호가 맞으면 모드 변경.
                if (pwPreference.getString("password", "000").equals(pwFromUser)) {

                    changePwMode = true
                    Toast.makeText(this, "변경할 패스워드 입력", Toast.LENGTH_SHORT).show()
                    changePwBtn.setBackgroundColor(Color.RED)
                    // 비밀번호가 맞지 않으면 모드 변경 실패
                } else {
                    showErrorDialog("실패!!", "비밀번호가 잘못되었습니다.")
                }
            }
        }
    }

changePwBtn 을 눌렀을 시.

만약 changePwMode 이면 앱이 저장하는 비밀번호를 현재 화면에 표시된 (바꾼) 비밀번호로 저장, changePwMode에서 빠져나옴.. UI 업데이트

만약 changePwMode가 아니면

현재 비밀번호가 틀리면 changePwMode로 들어가지 못함.

현재 비밀번호가 맞으면 changePwMode 로 들어감.

다이어리 내용 저장

class DiaryActivity : AppCompatActivity() {

    // Mark -Properties/////////////////////////////////////////
    private val handler = Handler(Looper.getMainLooper()) // MainThread 에 들어이있는 Handler.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_diary)

        // Mark -Properties/////////////////////////////////////////
        val diaryEt = findViewById<EditText>(R.id.diary_Et)
        val detailPreferences = getSharedPreferences("diary", Context.MODE_PRIVATE)
        diaryEt.setText(detailPreferences.getString("detail", ""))

        val runnable = Runnable{
            getSharedPreferences("diary", Context.MODE_PRIVATE).edit{
                putString("detail", diaryEt.text.toString())
            }
            Log.d("DiaryActivity","Save!")
        }

        diaryEt.addTextChangedListener {
            // 메인 스레드와 연결을 해주는 기능을 Handler을 이용하여 구현할 것.
            handler.removeCallbacks(runnable) // Remove any pending posts of Runnable r that are in the message queue.
            handler.postDelayed(runnable, 1000)

        }
    }

}

diaryActivity 가 실행되었을 때 preference에 저장되어 있는 내용으로 표시되게 하기.

앱 구현 화면

아래는 해당 앱을 올린 github 링크입니다.

https://github.com/sh1mj1/SecretDiary_03