티스토리 뷰

1. 안드로이드 어플 만들기 : 비밀 다이어리

이번에 해본 것은 안드로이드 어플만들기 3번째 예제로 비밀 다이어리 어플을 만들어보는 것 이었습니다. 기능에 대한 특징은 아래와 같습니다.

특징

  • 사용자가 설정한 비밀번호를 입력해야만 다이어리 페이지로 전환된다. (초기 비밀번호는 000)
  • 사용자가 비밀번호를 원하는 대로 다시 바꿀 수 있다.
  • 다이어리 페이지는 자동으로 저장된다. (종료 후 재실행 시 유지)

위에 있는 간단한 기능을 가진 어플리케이션 만들기 실습을 진행해보았고, 사용했던 기술을 이 글에 적어보려합니다. 단, 사소한 것 까지 모두 기록하는 것 보다는 코틀린으로 개발하며 신기하고 처음보는 기능 위주로 기록하도록 하겠습니다.

이 글에서 문단 제목에 붙은 숫자의 경우 SEO를 준수하기 위해 넣어논 것이며 연관된 큰 의미는 없습니다.

좀 더 세부적인 코드와 개발 과정을 확인하고 싶으시다면, 저의 깃허브 저장소에 있는 커밋들을 살펴보시면 도움이 될 것 입니다.

1.2. 해당 어플에서 사용하는 기술 및 자원

  • 레이아웃
    • ConstraintLayout
    • Custom Font
  • Handler 써보기
  • SharedPreference 의 속성들 및 사용방법
  • Theme 사용
  • AlertDialog 사용
  • Kotlin
    • android-ktx 로 SharedPreference 써보기 (Kotlin Android Extension)

2. 안드로이드 스튜디오 커스텀 폰트 사용하기

먼저 살펴볼 것은 커스텀 폰트를 사용하는 방법입니다. 간단히 말하면 자신이 원하는 .ttf 나 .otf 등 폰트파일을 텍스트 뷰 등에서 사용해보는 방법을 말합니다.

먼저 사용할 폰트를 다운로드 받아주시고 (여기서는 무료 폰트 배민 폰트를 사용했습니다.) 자원 폴더인 res 폴더 아래 font라는 안드로이드 리소스 폴더를 하나 추가하고 그 안에 폰트파일 (.ttf 등)을 넣어줍니다.

이후에는 아래와 같이 xml 코드에서 폰트를 적용할 TextView 태그에 속성으로 android:fontFamily="@font/폰트명" 형식으로 추가하시면 원하는 폰트를 적용시킬 수 있습니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#3F51B5"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="50dp"
        android:fontFamily="@font/bm_font"
        android:text="The Screat Garden"
        android:textSize="30sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toTopOf="@id/passwordLayout"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

2.1. 액션바가 없는 엑티비티 사용하기

메인 잠금 화면에서 액션바가 없는 noActionBar 테마를 사용하기위해서 res 폴더 내부 themes 폴더안에 있는 themes.xml 파일에 아래와 같은 코드를 추가해서 사용하였습니다.

<resources xmlns:tools="http://schemas.android.com/tools">
    ...생략...

    <style name="Theme.Aopp2c03secretdiary.NoActionBar" parent='Theme.MaterialComponents.DayNight.NoActionBar'/>

</resources>

이를 적용시키기 위해서는 매니패스트파일에 android:theme="@style/Theme.Aopp2c03secretdiary.NoActionBar" 속성을 추가로 적용시켜 테마를 적용해주면 되겠습니다. 실제로 실행해보면 액션바가 제거된 엑티비티를 확인할 수 있습니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="lilcode.aop.p2c02.aop_p2c03_secret_diary">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Aopp2c03secretdiary">
        <activity android:name=".MainActivity"
            android:theme="@style/Theme.Aopp2c03secretdiary.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".DiaryActivity"
            android:theme="@style/Theme.Aopp2c03secretdiary.NoActionBar">

        </activity>
    </application>

</manifest>

2.2. SharedPreFerences 사용 해서 영구저장 기능 구현

우리는 앱이 종료되더라도 유저가 셋팅해 놓은 비밀번호가 유지되기를 원하고 그렇게 하기위해서는 따로 DB에 비밀번호를 저장하거나 특정 파일을 생성해 비밀번호를 저장해둘 필요가 있습니다.

여기서는 후자의 방법을 사용하는데 그중에서도 안드로이드에서 제공하고있는 SharedPreferences 를 사용해서 비밀번호 값을 저장하고 앱이 종료되고 다시 재실행 하더라도 비밀번호가 유지되도록 해보겠습니다.

코드를 복붙하기 보다는 UI와 강조표시를 함께 보는 것이 이해하는데 도움이 될 것 같아 위 이미지로 대체하였습니다. 눌러보면 크게 원본으로 학인이 가능합니다.

다이어리 열기 버튼 클릭 이벤트 처리 하는 함수 부분 중에서도 SharedPreferences 부분 코드를 강조해놓았으니 해당 부분위주로 봐주시면 되겠습니다.

71번 라인에서 getSharedPreferences 함수로 부터 password 이름을 가진 SharedPreferences를 얻고 모드는 Private로 설정하여 현재 앱애서만 사용하도록 해주도록 합니다.

78번 라인에서 얻어온 shared preference를 통해서 password 이름을 가진 값을 불러오며 이때 getString으로 불러오는 이유는 우리가 저장할 때 String 형식으로 사용했기 때문에 String 값을 불러오도록 getString을 사용합니다.

만약에 값이 없다면 기본 값으로 "000"을 불러오고 그렇기 때문에 앱 초기 실행 시에는 초기 비밀번호인 "000" 값이 비밀번호가 되는 것이죠. 이후에 불러왔다면 equals 를 통해 String 비교하여 현재 입력된 비번과 맞는지 확인합니다.

이후에 비밀번호가 맞다면 다이어리 액티비티로 전환되도록 하면되겠습니다.

3. 핸들러(Handler) 사용 방법 (+kotlin-ktx)

다음으로 핸들러를 사용하는 방법입니다. 다이어리에 텍스트를 작성하게 될 때 수정 이벤트가 생길 때 마다 저장을 해주어 강제 종료되더라도 다시 켰을 때 내용이 자동 저장되어 유지되도록 하는 기능을 구현하기 위해 핸들러를 사용합니다.

그리고 텍스트가 수정될 때마다 무조건 저장하는 것이 아닌 효율성을 위해서 0.5초 이후에 실행하도록 구현을 해보도록 하겠습니다.

class DiaryActivity : AppCompatActivity() {

    private val handler = Handler(Looper.getMainLooper()) // 메인 루퍼를 넣어주면 메인 스레드와 연결

    private val _diaryEditText: EditText by lazy {
        findViewById<EditText>(R.id.diaryEditText)
    }

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

        val diaryEditText = findViewById<EditText>(R.id.diaryEditText)
        val detailPreferences = getSharedPreferences("diary", Context.MODE_PRIVATE)

        diaryEditText.setText(detailPreferences.getString("detail", ""))


        val runnable = Runnable {
            getSharedPreferences("diary", Context.MODE_PRIVATE).edit {
                putString("detail", diaryEditText.text.toString())
                apply()
            }

            Log.d("DiaryActivity", "SAVE!!! ${diaryEditText.text.toString()}")
        }

        // 수정 할때 마다 저장
        diaryEditText.addTextChangedListener {
            Log.d("DiaryActivity", "TextChanged :: $it")

            handler.removeCallbacks(runnable) // 이전에 팬딩되어있는 runnable이 있다면 제거
            //몇 초 이후에 runnable을 실행
            handler.postDelayed(runnable, 500) // 0.5 초 이후에 실행
        }
    }
}

16번 라인과 같이 핸들러 객체 생성 시 메인 루퍼를 넣어 연결해주면 메인 스레드와 연결이 되기 때문에 UI 자원을 사용할 수 있게 됩니다.

핸들러의 자동 저장에 사용될 Runnable 객체를 32번 라인에서 정의해줍시다. 위에서 배웠던 SharedPreferences를 사용하여 다이어리 내용을 저장하고 로그를 띄우도록 설정해줍니다. 이때 SharedPreferences 에 값을 저장하기 위해서 2가지 방법이 있는데 바로 commit 과 apply 방법 중 apply 방법을 사용했습니다. commit을 사용하면 변경시 UI가 정지되며 저장을 바로하지만 UI가 정지된다는 치명적인 문제가 있기 때문에 apply()를 통해 send message 를 날리는 식으로 저장하도록 했습니다.

여기서 이미 Kotlin-ktx 가 사용되었습니다. 원래는 SharedPreferences 의 edit()를 통해 에디터를 따로 불러온 뒤에 위의 작업을 해주어야 했지만(JAVA 에서 말이죠.) 코틀린에서는 바로 람다를 통해서 적용이 가능하게 된 것이죠.

42번 라인 EditText의 addTextChangedListener 설정에서 핸들러를 사용해서 자동저장을 구현해줍니다. 이때 위에서 작성한 runnable 을 사용하며 postDelayed 를 주어 500밀리초 이후에 실행되도록 해줍니다.

물론 계속해서 Text가 수정되어진다면 해당 팬딩건이 계속 쌓일 수 있기 때문에 removeCallbacks를 통해서 여태까지 쌓여있는 모든 runnable을 제거하도록 해줍니다.

3.1. Alert Dialog 사용 방법

비밀번호가 틀렸을 때 경고 박스를 띄우는 부분은 자주 사용되므로 중복을 피하기 위해 함수로 따로 빼서 정의 하였고 사용 방법은 아래와 같습니다.

    private fun showErrorAlertDialog() {
        AlertDialog.Builder(this)
            .setTitle("실패!!")
            .setMessage("비밀번호가 잘못되었습니다.")
            .setPositiveButton("확인") { _, _ -> }
            .create()
            .show()
    }

set 함수를 사용하여 타이틀 제목을 설정하거나 확인 버튼 혹은 아니오 버튼 등을 알맞게 추가해서 Alert Dialog를 사용할 수 있습니다. 이때 확인 버튼을 눌렀을 때 실행을 따로 정해주지 않을 것 이기 때문에 { _, _ -> } 람다로 표시했습니다.

4. 기타 사항

lazy 를 이용한 늦장 초기화

숫자 선택기를 소스코드에 연결할 때 lazy 키워드를 사용해서 게으른 초기화를 맡겨두었는데 이는 메인 엑티비티가 생성될 때 뷰가 다 생성되는 것을 보장할 수 없기 때문에 (실제로 생성 안됨) 뷰가 모두 생성되고나서 onCreate 내부에서 초기화를 할 수 있도록 lazy 키워드를 사용했습니다.

때문에 초기화를 호출하려면 아래와 같이 onCreate 내부에서 수동으로 코드를 추가해서 초기화 되도록 해야하는 점 잊으면 안되더군요. 이 점을 보면서 참 코틀린이 왜 안드로이드 개발의 정식 언어로 지정되어있는지 확실히 알겠더군요.

제약 레이아웃을 사용한 배경 추가 방식

금고 처럼 되어있는 비밀번호 입력기의 배경은 사실 버튼과 숫자 선택기들을 ConstraintLayout으로 한번 더 포함 시켜 회색 배경으로 추가한 것인데요? 이로써 메인 뷰에서 가운데 위치하게하고 좀더 높게 설정을 할 수 있었던 것입니다.

코드를 보시면 layout_constraintStart_toStartOf 를 부모로 설정하고 End 도 마찬가지로 부모로 설정함으로써 너비 부분을 가운데 정렬할 수 있고 마찬가지로 상하 위치도 그렇게 설정하지만 Vertical bias를 추가하여 약간 더 위로 가게 만들었던 것입니다.

이처럼 constraint layout을 사용하면 제약을 주어 UI를 좀 더 규칙적으로 배치할 수 있어서 보기에도 정렬이 잘 된 편안함을 주는 것이 마음에 들었습니다.

댓글
최근에 올라온 글
최근에 달린 댓글
네이버 이웃추가
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함