<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ars.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Fri, 02 Feb 2024 15:06:52 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>ars.log</title>
            <url>https://velog.velcdn.com/images/ars_yeon/profile/6e1c9aca-4bd6-4a0c-ba68-ce3263d2e0be/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. ars.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ars_yeon" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Android] Keystore 분실 시 재설정하기]]></title>
            <link>https://velog.io/@ars_yeon/Android-Keystore-%EB%B6%84%EC%8B%A4-%EC%8B%9C-%EC%9E%AC%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ars_yeon/Android-Keystore-%EB%B6%84%EC%8B%A4-%EC%8B%9C-%EC%9E%AC%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 02 Feb 2024 15:06:52 GMT</pubDate>
            <description><![CDATA[<h2 id="사건의-전말🫥">사건의 전말🫥</h2>
<p>최근 Firebase 관련 라이브러리 업데이트를 하라는 메시지를 받고 수정 후 릴리즈 하는데 아래와 같은 에러가 발생했다....🫥
<img src="https://velog.velcdn.com/images/ars_yeon/post/94598efb-ddbd-4086-9518-1d9d81b9e41e/image.png" alt="Keystore-에러"></p>
<blockquote>
<p>빌드 중에 &#39;:app:validateSigningRelease&#39; 작업이 실패했습니다.
&#39;externalOverride&#39; 서명 구성에 대한 keystore 파일을 찾을 수 없습니다.</p>
</blockquote>
<p>Keystore는 안드로이드 앱을 서명하고 릴리스할 때 사용되고, Play Console에도 등록해야 된다. 근데 아무리 찾아봐도 안보인다.....
생각해보니 어제 안드로이드 스튜디오를 Giraffe에서 Hedgehog로 업데이트를 했는데, 그때 삭제됐나보다😢
과거의 나... 왜 복제를 안했을까😂</p>
<p>구글링을 해보니 Keystore를 새로 만들고 Play Console 지원팀에 요청을 하라고 되어있다.
<br/></p>
<hr>
<br/>

<h2 id="새로운-keystore-생성">새로운 Keystore 생성</h2>
<p>Build &gt; Generate Signed Bundle or APK &gt; Android App Bundle 또는 APK &gt; Create new</p>
<p><img src="https://velog.velcdn.com/images/ars_yeon/post/93b767c8-4a11-4d89-b103-068966137ba5/image.png" alt="Keystore-생성1"></p>
<p><img src="https://velog.velcdn.com/images/ars_yeon/post/f01096e6-36d3-4f70-873a-17fc8881865c/image.png" alt="Keystore-생성2"></p>
<ul>
<li>Key store path: Key store가 생성되는 위치</li>
<li>Password: Key store 비밀번호</li>
<li>Key<ul>
<li>Alias: Key 별명</li>
<li>Password: Key store path의 비밀번호와 동일해야 함</li>
<li>Validity (years): 키의 유효기간 (최소 25)</li>
</ul>
</li>
<li>Certificate: 인증서에 사용할 관련 정보<ul>
<li>First and Last Name: 서명자 이름</li>
<li>Organization Unit: 부서</li>
<li>Organization: 회사</li>
<li>City or Locality: 도시/지역</li>
<li>State or Province: 도/광역시<br/>

</li>
</ul>
</li>
</ul>
<p>🚨 아주 중요한 건 <strong>Key store path</strong>와 <strong>Key</strong>의 <strong>Password</strong>가 동일해야 된다는 것!!
그리고 Key store path와 Alias를 잘 기억을 잘 해야 한다!
<br/></p>
<hr>
<br/>

<h2 id="jks-→-pem-파일-변환">.jks → .pem 파일 변환</h2>
<p>터미널에서 Key store 파일인 jks를 PEM 파일로 변환해서 Play Console 지원팀에 제출해야 된다.
아래 명령어는 Key store 파일이 존재하는 경로에서 진행해야 한다.</p>
<pre><code>keytool -export -rfc -keystore &lt;Key store 파일명&gt;.jks -alias &lt;Alias&gt; -file upload_certificate.pem</code></pre><p><img src="https://velog.velcdn.com/images/ars_yeon/post/40a86729-4cc3-4e53-8b20-dedfc154f381/image.png" alt="jks-pem-파일변환"></p>
<p>난 AndroidStuidoProjects 폴더에 저장해서 <code>cd ..</code>를 입력하여 상위 폴더로 이동했다.</p>
<p>입력하고 나면 <code>Enter keystore password</code>라는 문구와 함께 커서가 깜빡거린다.
비밀번호를 입력할 때 커서는 움직이지 않으나 입력이 되고 있는 상태니까 차근차근 써야 된다!</p>
<p>비밀번호를 다 입력하면 <code>Certificate stored in file &lt;upload_certificate.pem&gt;</code>이라고 뜬다.
인증서가 <code>upload_verficate.pem</code>이라는 이름으로 저장이 되었다는 것이다.
<br/></p>
<hr>
<br/>

<h2 id="업로드-키-재설정-요청">업로드 키 재설정 요청</h2>
<p>Google Play Console을 열고 Play 앱 서명 페이지로 이동한다!
⭐ 로그인은 개발자 아이디로!</p>
<p><a href="https://play.google.com/console/developers/app/keymanagement">출시 &gt; 설정 &gt; 앱 서명 &gt; 업로드 키 재설정 요청</a></p>
<p><img src="https://velog.velcdn.com/images/ars_yeon/post/bf0daa50-16da-4ffe-a885-20f28d40bd64/image.png" alt="업로드_키_재설정_요청_1"></p>
<p><img src="https://velog.velcdn.com/images/ars_yeon/post/1e5e9f25-0b8f-42b6-823a-846abae323e4/image.png" alt="업로드_키_재설정_요청_2"></p>
<p>업로드 키 재설정 요청을 위한 폼을 작성한다.
아까 생성했던 <code>pem</code> 파일도 잊지 않고 첨부하면 완료!</p>
<p><img src="https://velog.velcdn.com/images/ars_yeon/post/effa4035-7d54-462b-abd3-5691a5075b21/image.png" alt="업로드_키_재설정_요청_3"></p>
<p>그러고나면 이렇게 재설정 요청이 대기중이라는 문구가 떠있다!
<br/></p>
<hr>
<br/>

<h2 id="답변-대기중📪">답변 대기중...📪</h2>
<p><img src="https://velog.velcdn.com/images/ars_yeon/post/c658f815-8128-4e33-9d13-dddc0ea40b30/image.png" alt="업로드_키_재설정_요청_접수_메일"></p>
<p>바로 메일이 왔는데, 새 업로드 키는 이틀 후부터 유효하다고 한다🫠
이틀 후에 Google Play Store에 업데이트를 해야지!
그리고 Keystore는 복제해서 따로 보관해놔야지...ㅠㅠㅜㅠㅜ
<br/></p>
<hr>
<br/>

<p>[참고 사이트]
<a href="https://developer.android.com/studio/publish/app-signing?hl=ko">&#39;앱 서명&#39;, Developers</a>
<a href="https://support.google.com/googleplay/android-developer/answer/9842756?sjid=11860546720553815875-AP#create&amp;zippy=%2C%EC%97%85%EB%A1%9C%EB%93%9C-%ED%82%A4-%EC%9A%94%EA%B5%AC%EC%82%AC%ED%95%AD%2C%ED%82%A4-%EC%A0%80%EC%9E%A5%EC%86%8C-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8">&#39;Play 앱 서명 사용하기&#39;, Play Console 고객센터</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Programmers] k진수에서 소수 개수 구하기 (Kotlin)]]></title>
            <link>https://velog.io/@ars_yeon/Programmers-k%EC%A7%84%EC%88%98%EC%97%90%EC%84%9C-%EC%86%8C%EC%88%98-%EA%B0%9C%EC%88%98-%EA%B5%AC%ED%95%98%EA%B8%B0-Kotlin</link>
            <guid>https://velog.io/@ars_yeon/Programmers-k%EC%A7%84%EC%88%98%EC%97%90%EC%84%9C-%EC%86%8C%EC%88%98-%EA%B0%9C%EC%88%98-%EA%B5%AC%ED%95%98%EA%B8%B0-Kotlin</guid>
            <pubDate>Mon, 15 Jan 2024 06:23:29 GMT</pubDate>
            <description><![CDATA[<h2 id="k진수에서-소수-개수-구하기">[k진수에서 소수 개수 구하기]</h2>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/92335">Programmers k진수에서 소수 개수 구하기</a></p>
<h3 id="문제-설명">문제 설명</h3>
<p>양의 정수 <code>n</code>이 주어집니다. 이 숫자를 <code>k</code>진수로 바꿨을 때, 변환된 수 안에 아래 조건에 맞는 소수(Prime number)가 몇 개인지 알아보려 합니다.</p>
<ul>
<li><code>0P0</code>처럼 소수 양쪽에 0이 있는 경우</li>
<li><code>P0</code>처럼 소수 오른쪽에만 0이 있고 왼쪽에는 아무것도 없는 경우</li>
<li><code>0P</code>처럼 소수 왼쪽에만 0이 있고 오른쪽에는 아무것도 없는 경우</li>
<li><code>P</code>처럼 소수 양쪽에 아무것도 없는 경우</li>
<li>단, <code>P</code>는 각 자릿수에 0을 포함하지 않는 소수입니다.<ul>
<li>예를 들어, 101은 <code>P</code>가 될 수 없습니다.</li>
</ul>
</li>
</ul>
<p>예를 들어, 437674을 3진수로 바꾸면 <code>211020101011</code>입니다. 여기서 찾을 수 있는 조건에 맞는 소수는 왼쪽부터 순서대로 211, 2, 11이 있으며, 총 3개입니다. (211, 2, 11을 <code>k</code>진법으로 보았을 때가 아닌, 10진법으로 보았을 때 소수여야 한다는 점에 주의합니다.) 211은 <code>P0</code> 형태에서 찾을 수 있으며, 2는 <code>0P0</code>에서, 11은 <code>0P</code>에서 찾을 수 있습니다.</p>
<p>정수 <code>n</code>과 <code>k</code>가 매개변수로 주어집니다. <code>n</code>을 <code>k</code>진수로 바꿨을 때, 변환된 수 안에서 찾을 수 있는 위 조건에 맞는 소수의 개수를 return 하도록 solution 함수를 완성해 주세요.
<br/></p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>1 ≤ n ≤ 1,000,000</li>
<li>3 ≤ k ≤ 10<br/>

</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>n</th>
<th>k</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>437674</td>
<td>3</td>
<td>3</td>
</tr>
<tr>
<td>110011</td>
<td>10</td>
<td>2</td>
</tr>
<tr>
<td><br/></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<h4 id="입출력-예-1">입출력 예 #1</h4>
<p>문제 예시와 같습니다.</p>
<h4 id="입출력-예-2">입출력 예 #2</h4>
<p>110011을 10진수로 바꾸면 110011입니다. 여기서 찾을 수 있는 조건에 맞는 소수는 11, 11 2개입니다. 이와 같이, 중복되는 소수를 발견하더라도 모두 따로 세어야 합니다.</p>
<br/>

<h2 id="풀이">풀이</h2>
<p>문제를 읽고 어떻게 코드를 작성할까 생각해보았다.</p>
<ol>
<li>n을 k진수로 바꾼다.</li>
<li>k진수로 바꾼 숫자를 0을 기준으로 나눈다.</li>
<li>나뉜 숫자들이 소수인지 확인하여 카운트에 +1을 한다.<br/>

</li>
</ol>
<h2 id="코드">코드</h2>
<h3 id="1-코드-1-런타임-에러">1. 코드 1 (런타임 에러)</h3>
<pre><code>class Solution {
    fun toKBase(n: Int, k: Int): String {
        // n을 k진수로 변환
        return n.toString(k)
    }

    fun isPrime(n: Int): Boolean {
        // 2 미만의 수는 소수가 아님
        if (n &lt; 2) return false

        // 2부터 제곱근까지의 수로 나누어 소수 여부 확인
        for (i in 2..Math.sqrt(n.toDouble()).toInt()) {
            if (n % i == 0) return false
        }
        return true
    }

    fun countPrimes(n: Int, k: Int): Int {
        val kBaseNum = toKBase(n, k)
        val segments = kBaseNum.split(&quot;0&quot;)

        return segments.count { it.isNotEmpty() &amp;&amp; isPrime(it.toInt()) }
    }

    fun solution(n: Int, k: Int): String {
        return countPrimes(n, k)
    }
}</code></pre><br/>

<p>위와 같이 코드를 작성했는데 테스트 케이스 1과 11에 런타임 에러가 발생했다.
<img src="https://velog.velcdn.com/images/ars_yeon/post/69f203ae-603f-4332-9dd1-00183450cb26/image.png" alt="런타임 에러">
코드를 살펴보았을 때 왠지 <code>isPrime</code>함수에서 런타임 에러가 발생하는 것 같은데 이유를 잘 모르겠어서 <a href="https://school.programmers.co.kr/learn/courses/14743/lessons/118834">프로그래머스 코딩테스트 연습 힌트 모음집</a>을 한 번 보았다.
<img src="https://velog.velcdn.com/images/ars_yeon/post/15e257f7-b37d-4c20-8db9-863e72c8cbc0/image.png" alt="힌트"></p>
<p><code>isPrime</code>은 k진수로 바꾼 수가 소수인지 판별하기 위한 함수이다.
2부터 제곱근까지만 나누기 때문에 이 코드는 잘 작성했다고 생각했는데 <code>int</code>만 사용해서 문제가 생긴 것 같다.</p>
<h3 id="2-코드-2-isprime-함수-매개변수-변경">2. 코드 2 (isPrime 함수 매개변수 변경)</h3>
<p><code>isPrime</code> 함수의 매개변수를 <code>Int</code>가 아닌 <code>Long</code>으로 바꿔서 코드를 작성해보았다.</p>
<pre><code>class Solution {
    fun toKBase(n: Int, k: Int): String {
        // n을 k진수로 변환
        return n.toString(k)
    }

    fun isPrime(n: Long): Boolean {
        // 2 미만의 수는 소수가 아님
        if (n &lt; 2) return false

        // 2부터 제곱근까지의 수로 나누어 소수 여부 확인
        for (i in 2..Math.sqrt(n.toDouble()).toLong()) {
            if (n % i == 0L) return false
        }
        return true
    }

    fun countPrimes(n: Int, k: Int): Int {
        val kBaseNum = toKBase(n, k)
        val segments = kBaseNum.split(&quot;0&quot;)
        return segments.count { it.isNotEmpty() &amp;&amp; isPrime(it.toLong()) }
    }

    fun solution(n: Int, k: Int): Int {
        return countPrimes(n, k)
    }
}</code></pre><p>소수를 확인하는 동안 <code>Int</code>타입을 사용하면 큰 수의 경우 <code>NumberFormatException</code>이 발생할 수 있기 때문에 <code>isPrime</code>함수의 매개변수를 <code>Long</code>타입으로 변경하였다.
실행 결과는 아래와 같이 성공이다!
<img src="https://velog.velcdn.com/images/ars_yeon/post/830ecbe5-2383-4e73-9cb1-264958743cb3/image.png" alt="테스트 통과"></p>
<hr>
<p>이번 문제를 풀면서 10진법인 수를 여러 진법으로 변환하면 숫자의 크기가 커질 수 있다는 것을 미처 생각지 못했었다. 다른 문제를 풀 때에도 데이터 타입을 주의해야겠다는 생각을 했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Programmers] 가장 가까운 같은 글자 (kotlin)]]></title>
            <link>https://velog.io/@ars_yeon/Programmers-%EA%B0%80%EC%9E%A5-%EA%B0%80%EA%B9%8C%EC%9A%B4-%EA%B0%99%EC%9D%80-%EA%B8%80%EC%9E%90-kotlin</link>
            <guid>https://velog.io/@ars_yeon/Programmers-%EA%B0%80%EC%9E%A5-%EA%B0%80%EA%B9%8C%EC%9A%B4-%EA%B0%99%EC%9D%80-%EA%B8%80%EC%9E%90-kotlin</guid>
            <pubDate>Thu, 11 Jan 2024 08:36:54 GMT</pubDate>
            <description><![CDATA[<h2 id="가장-가까운-같은-글자">[가장 가까운 같은 글자]</h2>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/142086">Programmers 가장 가까운 같은 글자</a></p>
<h3 id="문제-설명">문제 설명</h3>
<p>문자열 s가 주어졌을 때, s의 각 위치마다 자신보다 앞에 나왔으면서, 자신과 가장 가까운 곳에 있는 같은 글자가 어디 있는지 알고 싶습니다.
예를 들어, s=&quot;banana&quot;라고 할 때,  각 글자들을 왼쪽부터 오른쪽으로 읽어 나가면서 다음과 같이 진행할 수 있습니다.</p>
<ul>
<li>b는 처음 나왔기 때문에 자신의 앞에 같은 글자가 없습니다. 이는 -1로 표현합니다.</li>
<li>a는 처음 나왔기 때문에 자신의 앞에 같은 글자가 없습니다. 이는 -1로 표현합니다.</li>
<li>n은 처음 나왔기 때문에 자신의 앞에 같은 글자가 없습니다. 이는 -1로 표현합니다.</li>
<li>a는 자신보다 두 칸 앞에 a가 있습니다. 이는 2로 표현합니다.</li>
<li>n도 자신보다 두 칸 앞에 n이 있습니다. 이는 2로 표현합니다.</li>
<li>a는 자신보다 두 칸, 네 칸 앞에 a가 있습니다. 이 중 가까운 것은 두 칸 앞이고, 이는 2로 표현합니다.</li>
</ul>
<p>따라서 최종 결과물은 [-1, -1, -1, 2, 2, 2]가 됩니다.</p>
<p>문자열 s이 주어질 때, 위와 같이 정의된 연산을 수행하는 함수 solution을 완성해주세요.
<br/></p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>1 ≤ s의 길이 ≤ 10,000<ul>
<li>s은 영어 소문자로만 이루어져 있습니다.<br/>

</li>
</ul>
</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>s</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>&quot;banana&quot;</td>
<td>[-1, -1, -1, 2, 2, 2]</td>
</tr>
<tr>
<td>&quot;foobar&quot;</td>
<td>[-1, -1, 1, -1, -1, -1]</td>
</tr>
<tr>
<td><br/></td>
<td></td>
</tr>
</tbody></table>
<h2 id="풀이">풀이</h2>
<ol>
<li>s를 문자 배열로 변환</li>
<li>문자 배열로 변환한 s를 순회<ul>
<li>같은 문자가 없다면 -1을 추가</li>
<li>같은 문자가 있다면 가장 가까운 같은 글자와의 거리를 계산하여 추가</li>
</ul>
</li>
</ol>
<ul>
<li><code>lastIndexOf</code>로 특정 요소의 인덱스를 알아내면 될 듯!<ul>
<li>배열 내 특정 요소의 인덱스를 찾아주는 메소드로, 해당 요소가 없다면 <code>-1</code>을 반환함<br/>

</li>
</ul>
</li>
</ul>
<h2 id="코드">코드</h2>
<pre><code>class Solution {
    fun solution(s: String): IntArray {
        val answer = mutableListOf&lt;Int&gt;()

        val words = s.toCharArray()
        for (i in words.indices) {
            val temp = words.sliceArray(0 until i)
            val last = temp.lastIndexOf(word[i])

            if (last == -1) {
                answer.add(-1)
            } else {
                answer.add(i - last)
            }
        }
        return answer.toIntArray()
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Programmers] 멀리뛰기 (Kotlin)]]></title>
            <link>https://velog.io/@ars_yeon/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%A9%80%EB%A6%AC%EB%9B%B0%EA%B8%B0-Kotlin</link>
            <guid>https://velog.io/@ars_yeon/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%A9%80%EB%A6%AC%EB%9B%B0%EA%B8%B0-Kotlin</guid>
            <pubDate>Thu, 11 Jan 2024 07:10:08 GMT</pubDate>
            <description><![CDATA[<h2 id="멀리-뛰기">[멀리 뛰기]</h2>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/12914">Programmers 멀리 뛰기</a></p>
<h3 id="문제-설명">문제 설명</h3>
<p>효진이는 멀리 뛰기를 연습하고 있습니다. 효진이는 한번에 1칸, 또는 2칸을 뛸 수 있습니다. 칸이 총 4개 있을 때, 효진이는
(1칸, 1칸, 1칸, 1칸)
(1칸, 2칸, 1칸)
(1칸, 1칸, 2칸)
(2칸, 1칸, 1칸)
(2칸, 2칸)
의 5가지 방법으로 맨 끝 칸에 도달할 수 있습니다. 멀리뛰기에 사용될 칸의 수 n이 주어질 때, 효진이가 끝에 도달하는 방법이 몇 가지인지 알아내, 여기에 1234567를 나눈 나머지를 리턴하는 함수, solution을 완성하세요. 예를 들어 4가 입력된다면, 5를 return하면 됩니다.
<br/></p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>n은 1 이상, 2000 이하인 정수입니다.<br/>

</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>n</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>4</td>
<td>5</td>
</tr>
<tr>
<td>3</td>
<td>3</td>
</tr>
<tr>
<td><br/></td>
<td></td>
</tr>
</tbody></table>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<h4 id="입출력-예-1">입출력 예 #1</h4>
<p>위에서 설명한 내용과 같습니다.</p>
<h4 id="입출력-예-2">입출력 예 #2</h4>
<p>(2칸, 1칸)
(1칸, 2칸)
(1칸, 1칸, 1칸)
총 3가지 방법으로 멀리 뛸 수 있습니다.</p>
<br/>

<h2 id="풀이">풀이</h2>
<p>규칙을 찾기 위해서 1부터 5까지 직접 구해보았다.</p>
<table>
<thead>
<tr>
<th>n</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>2</td>
<td>2</td>
</tr>
<tr>
<td>3</td>
<td>3</td>
</tr>
<tr>
<td>4</td>
<td>5</td>
</tr>
<tr>
<td>5</td>
<td>8</td>
</tr>
</tbody></table>
<p>중고등학생 때 배웠던 피보나치 수열이다.
피보나치 수의 점화식은 아래와 같다.</p>
<blockquote>
<p>$$F_0 = 0$$
$$F_1 = 1$$</p>
<p>$$n ∈ { 2,3,4, ... }$$
$$F_n = F_{n-1} + F_{n-2}$$</p>
</blockquote>
<p>이것을 활용하여 코드를 작성하면 된다!</p>
<br/>

<h2 id="코드">코드</h2>
<pre><code>class Solution {
    fun solution(n: Int): Long {
        if (n == 1) return 1

        var (prev, current) = 1L to 1L

        for (i in 2..n) {
            val temp = current
            current = (prev + current) % 1234567
            prev = temp
        }
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] #45 Fragment Lifecycle]]></title>
            <link>https://velog.io/@ars_yeon/TIL-45-Fragment-Lifecycle</link>
            <guid>https://velog.io/@ars_yeon/TIL-45-Fragment-Lifecycle</guid>
            <pubDate>Fri, 22 Sep 2023 13:05:28 GMT</pubDate>
            <description><![CDATA[<h2 id="🌀-fragment-lifecycle">🌀 Fragment Lifecycle</h2>
<h3 id="1-onattach">1. onAttach()</h3>
<ul>
<li>프래그먼트가 액티비티에 붙을 때 호출</li>
<li>인자로 Context가 주어짐</li>
</ul>
<pre><code>override fun onAttach(context: Context) {
    super.onAttach(context)
}</code></pre><br>

<h3 id="2-oncreate">2. onCreate()</h3>
<ul>
<li>프래그먼트가 액티비티의 호출을 받아 생성</li>
<li>Bunddle로 액티비티로부터 데이터가 넘어옴</li>
<li>UI 초기화는 불가능</li>
</ul>
<pre><code>override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
}</code></pre><br>

<h3 id="3-oncreateview">3. onCreateView()</h3>
<ul>
<li>레이아웃 inflate 담당</li>
<li>savedInstanceState로 이전 상태에 대한 데이터 제공</li>
</ul>
<pre><code>override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    return super.onCreateView(inflater, container, savedInstanceState)
}</code></pre><br>

<h3 id="4-onviewcreated">4. onViewCreated()</h3>
<ul>
<li>onCreagteView()를 통해 반환된 View 객체는 onViewCreated()의 파라미터로 전달됨<ul>
<li>Lifecycle이 INITIALIZED 상태로 업데이트가 됨</li>
<li>View의 초기값 설정, LiveData 옵저빙, RecyclerView, ViewPager2에 사용될 Adapter를 입력해주기 좋은 위치!</li>
</ul>
</li>
</ul>
<pre><code>override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
}</code></pre><br>

<h3 id="5-onviewstaterestored">5. onViewStateRestored()</h3>
<ul>
<li>저장해둔 모든 state 값이 프래그먼트의 View의 계층 구조에 복원되었을 때 호출<ul>
<li>ex. 체크박스 위젯이 현재 체크되어있는가</li>
</ul>
</li>
<li>View lifecycle owner : INITIALIZED → CREATED 변경</li>
</ul>
<pre><code>override fun onViewStateRestored(savedInstanceState: Bundle?) {
    super.onViewStateRestored(savedInstanceState)
}</code></pre><br>

<h3 id="6-onstart">6. onStart()</h3>
<ul>
<li>사용자에게 보여질 수 있을 때 호출</li>
<li>액티비티의 onStart() 시점과 유사</li>
<li>프래그먼트의 childFragmentManager을 통해 FragmentTransaction을 안전하게 수행할 수 있음</li>
<li>View lifecycle owner : CREATED → STARTED 변경</li>
</ul>
<pre><code>override fun onStart() {
    super.onStart()
}</code></pre><br>

<h3 id="7-onresume">7. onResume()</h3>
<ul>
<li>사용자와 프래그먼트가 상호작용 할 수 있는 상태일 때 호출</li>
<li>프래그먼트가 보이는 상태에서 모든 Animator와 Transition 효과가 종료됨 프래그먼트와 사용자가 상호작용 할 수 있을 때 onResume Callback</li>
</ul>
<pre><code>override fun onResume() {
    super.onResume()
}</code></pre><br>

<h3 id="8-onpause">8. onPause()</h3>
<ul>
<li>Fragment가 visible 일 때 onPause()가 호출</li>
<li>Faragment와 View의 Lifecycle이 PAUSED가 아닌 <strong>STARTED</strong>가 됨</li>
</ul>
<pre><code>override fun onPause() {
    super.onPause()
}</code></pre><br>

<h3 id="9-onstop">9. onStop()</h3>
<ul>
<li>Fragment가 더 이상 화면에 보여지지 않게 되면 onStop() 콜백 호출
부모 액티비티, 프래그먼트가 중단될 때, 상태가 저장될 때 호출</li>
<li>View와 Lifecycle : STARTED → CREATED</li>
<li>API 28버전을 기점으로 onSaveInstanceState() 함수와 onStop() 함수 호출 순서가 달라짐<ul>
<li>onStop()이 FragmentTransaction을 안전하게 수행하는 마지막 지점이 됨</li>
</ul>
</li>
</ul>
<pre><code>override fun onStop() {
    super.onStop()
}</code></pre><br>

<h3 id="10-ondestoryview">10. onDestoryView()</h3>
<ul>
<li>모든 exit animation, transaction이 완료되고 Fragment가 화면으로부터 벗어났을 경우 호출</li>
<li>view와 lifecycle : CREATED → DESTROYED</li>
<li>가비지 컬렉터에 의해 수거될 수 있도록 Fragment View에 대한 모든 참조가 제거되어야 함</li>
<li>getViewLifecycleOwnerLiveData()</li>
</ul>
<pre><code>override fun onDestroyView() {
    super.onDestroyView()
}</code></pre><br>

<h3 id="11-ondestroy">11. onDestroy()</h3>
<ul>
<li>Fragment가 제거되거나, FragmentManager가 destroy 됐을 경우, onDestroy() 콜백 함수 호출</li>
<li>Fragment Lifecycle의 끝을 알림</li>
</ul>
<pre><code>override fun onDestroy() {
    super.onDestroy()
}</code></pre><br>

<h3 id="12-ondetach">12. onDetach()</h3>
<ul>
<li>프래그먼트가 액티비티로부터 해제되어질 때 호출</li>
</ul>
<pre><code>override fun onDetach() {
    super.onDetach()
}</code></pre><br>

<hr>
<br>

<p>[참고 사이트]</p>
<p><a href="https://developer.android.com/guide/components/fragments?hl=ko#Lifecycle">&#39;프래그먼트 수명 주기 처리&#39;, Developers</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] #44 Log]]></title>
            <link>https://velog.io/@ars_yeon/TIL-44-Log</link>
            <guid>https://velog.io/@ars_yeon/TIL-44-Log</guid>
            <pubDate>Thu, 21 Sep 2023 13:08:50 GMT</pubDate>
            <description><![CDATA[<h2 id="1-log">1. Log?</h2>
<ul>
<li>코딩을 할 때 코드의 흐름을 파악하기 위해 앱 외부에 출력하는 정보</li>
<li>디버거를 사용할수도 있지만 매번 사용하기엔 번거로움으로 한번 설정으로 해당 코드 흐름을 확인할 수 있음</li>
</ul>
<br>

<h2 id="2-logcat">2. Logcat?</h2>
<ul>
<li>안드로이드 디바이스에서 발생하는 로그 메시지를 표시하는 도구</li>
<li>개발자가 앱 실행 중에 발생하는 로그를 실시간으로 확인하고 디버깅 및 모니터링을 수행할 수 있도록 도와줌</li>
</ul>
<br>

<h2 id="3-log-level">3. Log Level</h2>
<h3 id="1-logv">1) Log.v</h3>
<ul>
<li>Verbose</li>
<li>가장 낮은 로그 레벨</li>
<li>상세한 디버깅 정보 기록</li>
</ul>
<h3 id="2-logd">2) Log.d</h3>
<ul>
<li>Debug</li>
<li>디버깅 목적으로 사용</li>
<li>중간 단계의 디버깅 정보 기록</li>
</ul>
<h3 id="3-logi">3) Log.i</h3>
<ul>
<li>information</li>
<li>일반적인 정보나 앱의 주요 이벤트 기록</li>
</ul>
<h3 id="4-logw">4) Log.w</h3>
<ul>
<li>warning</li>
<li>경고 메시지 기록</li>
<li>잠재적인 문제를 나타냄</li>
<li>앱은 계속 실행됨!</li>
</ul>
<h3 id="5-loge">5) Log.e</h3>
<ul>
<li>error</li>
<li>오류 메시지 기록</li>
<li>앱이 예외 또는 오류 상태에 도달한 경우 사용</li>
</ul>
<br>

<h2 id="4-writelogs">4. WriteLogs</h2>
<ul>
<li><code>TAG</code>는 상수로 선언하는게 좋음!</li>
<li>태그 이름이 23자(영문 기준)를 초과하는 경우 Logcat 출력에서 잘림</li>
</ul>
<pre><code>Log.v(&quot;Tag&quot;, &quot;Verbose 메시지&quot;)
Log.i(&quot;Tag&quot;, &quot;Information 메시지&quot;)
Log.d(&quot;Tag&quot;, &quot;Debug 메시지&quot;)
Log.w(&quot;Tag&quot;, &quot;Warning 메시지&quot;)
Log.e(&quot;Tag&quot;, &quot;Error 메시지&quot;)</code></pre><br>

<h2 id="5-how-to-use-logs">5. How to Use Logs</h2>
<h3 id="1-적절한-로그-레벨-선택">1) 적절한 로그 레벨 선택</h3>
<ul>
<li>각 로그 레벨은 다른 목적을 가지고 있으므로, 상황에 맞게 적절한 레벨을 선택해야 함</li>
<li>ex. 디버깅 메시지는 Log.d를 사용하고, 중요한 이벤트는 Log.i로 기록함</li>
</ul>
<h3 id="2-로그-태그-사용">2) 로그 태그 사용</h3>
<ul>
<li>로그 태그는 로그 메시지를 그룹화하고 식별하는 데 사용됨</li>
<li>태그를 잘 정의하고 유용한 정보를 포함하도록 노력해야 함</li>
</ul>
<h3 id="3-릴리스-버전에서-로그-비활성화">3) 릴리스 버전에서 로그 비활성화</h3>
<ul>
<li>앱을 릴리스할 때 디버깅 목적의 로그를 비활성화해야 함</li>
<li>프로덕션 빌드에서 로그를 제거하거나 비활성화하는 기술을 사용할 수 있음</li>
</ul>
<h3 id="4-필터링-및-검색">4) 필터링 및 검색</h3>
<ul>
<li>Logcat에서 로그를 확인할 때 필터링 옵션을 사용하여 특정 태그 또는 로그 레벨로 로그 메시지를 필터링하고 검색할 수 있음</li>
</ul>
<h3 id="5-예외-처리와-함께-사용">5) 예외 처리와 함께 사용</h3>
<ul>
<li>Log.e를 사용하여 예외 정보를 기록하고, 예외 처리 코드 내에서 로그를 활용하여 문제를 추적하고 디버깅할 수 있음</li>
</ul>
<br>

<hr>
<br>

<p>[참고 사이트]</p>
<p><a href="https://developer.android.com/reference/android/util/Log">&#39;Log&#39;, Developers</a>
<a href="https://developer.android.com/studio/debug/am-logcat?hl=ko">&#39;Logcat&#39;을 이용한 로그 작성 보기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] #43 Debugging]]></title>
            <link>https://velog.io/@ars_yeon/TIL-43-Debugging</link>
            <guid>https://velog.io/@ars_yeon/TIL-43-Debugging</guid>
            <pubDate>Wed, 20 Sep 2023 13:26:38 GMT</pubDate>
            <description><![CDATA[<h2 id="1-debugging">1. Debugging?</h2>
<ul>
<li>프로그램을 실행하면서 발생하는 오류나 버그를 찾고 수정하는 과정<ul>
<li>코드를 분석하고 프로그램의 상태를 모니터링하여 문제를 해결하는 것을 포함함</li>
</ul>
</li>
<li>소프트웨어 개발 과정에서 중요한 부분으로, 오류를 찾고 수정함으로써 안정적이고 정확한 프로그램을 만들 수 있도록 도와줌</li>
<li>디버깅 도구를 사용해 소프트웨어를 제어된 환경에서 실행하고 코드를 단계별로 확인하여 문제를 분석하고 수정함</li>
</ul>
<br>

<h2 id="2-the-importance-of-debugging">2. The Importance of Debugging</h2>
<h3 id="1-버그-식별">1) 버그 식별</h3>
<ul>
<li>프로그램이 원하는 대로 동작하지 않거나 예기치 않은 동작을 할 수 있는 버그를 식별할 수 있음</li>
</ul>
<h3 id="2-코드-품질-향상">2) 코드 품질 향상</h3>
<ul>
<li>디버깅 과정을 통해 코드를 더 깨끗하고 효율적으로 만들 수 있음</li>
<li>또한 코드 작성 시 오류를 방지하는 데 도움을 줄 수 있음</li>
</ul>
<h3 id="3-사용자-만족도-향상">3) 사용자 만족도 향상</h3>
<ul>
<li>버그를 제거하면 사용자 경험이 향상되며 소프트웨어의 신뢰성을 높일 수 있음</li>
</ul>
<h3 id="4-비용-절감">4) 비용 절감</h3>
<ul>
<li>버그로 인해 발생하는 문제를 미리 해결하여 시간과 비용을 절감 할 수 있음</li>
</ul>
<br>

<h2 id="3-debugging-process-steps">3. Debugging Process Steps</h2>
<h3 id="1-오류-식별">1) 오류 식별</h3>
<ul>
<li>개발자, 테스터 및 최종 사용자가 소프트웨어를 테스트하거나 사용하는 동안 발견한 버그를 보고함</li>
<li>개발자가 버그의 원인이 된 정확한 코드 줄 또는 코드 모듈을 찾음</li>
<li>이 프로세스는 번거롭고 시간이 많이 걸릴 수 있음</li>
</ul>
<h3 id="2-오류-분석">2) 오류 분석</h3>
<ul>
<li>코더가 모든 프로그램 상태 변경 및 데이터 값을 기록하여 오류를 분석함</li>
<li>소프트웨어 기능에 미치는 영향을 기준으로 버그 수정의 우선 순위를 정함</li>
<li>소프트웨어 팀이 개발 목표와 요구 사항에 따라 버그 수정 일정을 정함</li>
</ul>
<h3 id="3-수정-및-검증">3) 수정 및 검증</h3>
<ul>
<li>개발자가 버그를 수정하고 테스트를 실행하여 소프트웨어가 계속 정상적으로 작동하는지 확인함</li>
<li>미래에 그 버그가 재발할지 확인하기 위해 새로운 테스트를 작성할 수도 있음</li>
</ul>
<h3 id="4-디버깅-vs-테스트">4) 디버깅 vs 테스트</h3>
<ul>
<li>디버깅과 테스트는 소프트웨어 프로그램이 제대로 실행되도록 보장하는 보완 프로세스!</li>
<li>프로그래머는 코드의 전체 섹션 또는 일부를 작성한 후, 버그와 오류를 식별하기 위해 테스트함</li>
<li>버그가 발견되면, 코더가 디버깅 프로세스를 시작하고 소프트웨어에서 오류를 제거할 수 있음</li>
</ul>
<br>

<hr>
<br>

<p>디버깅을 하는 방법에는 직접 Log를 출력하는 방법과 IDE에 있는 디버거를 사용하는 방법이 있다! 이건 따로 정리할 예정!</p>
<br>

<hr>
<br>

<p>[참고 사이트]</p>
<p><a href="https://developer.android.com/codelabs/basic-android-kotlin-compose-intro-debugger?hl=ko#0">&#39;Android 스튜디오에서 디버거 사용하기&#39;, Developer</a>
<a href="https://www.youtube.com/watch?v=xWeCowzr_cs&amp;ab_channel=hongdroid%ED%99%8D%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C">&#39;안드로이드 코틀린 앱 만들기 #12 Log출력하여 디버깅 - 쉽게 앱 만드는 방법 (현직 개발자 설명)&#39;, 홍드로이드(YouTube)</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] #42 Specify the input method action]]></title>
            <link>https://velog.io/@ars_yeon/TIL-42-Specify-the-input-method-action</link>
            <guid>https://velog.io/@ars_yeon/TIL-42-Specify-the-input-method-action</guid>
            <pubDate>Tue, 19 Sep 2023 13:21:00 GMT</pubDate>
            <description><![CDATA[<p>앱을 사용하면서 지나쳤던 작은 요소!
바로 키보드의 <strong>InputMethodAction</strong>이다.
이미지 검색 앱을 만들어보면서 키보드의 엔터를 눌렀는데 검색이 안돼서 당황했었다. 사용할 때는 당연하다고 생각했던 것인데 따로 설정을 해줘야 한다!
<br></p>
<hr>
<br>

<h2 id="1-imeoptions-in-xml">1. imeOptions in XML</h2>
<h3 id="1-imeoptins">1) imeOptins?</h3>
<ul>
<li><code>EditText</code>나 <code>TextView</code>와 같은 텍스트 입력 위젯에 대한 속성 중 하나로, 키보드와 관련된 동작을 제어하는 데 사용</li>
<li>사용자가 키보드를 통해 텍스트를 입력할 때 어떤 동작을 실행할지 지정할 수 있음</li>
<li>물론 기능적인 부분은 <code>kt</code> 파일에서 설정해줘야 됨!</li>
</ul>
<br>

<h3 id="2-type">2) Type</h3>
<ul>
<li><code>actionSearch</code> 검색 필드에서 Enter 키를 누를 때 검색 동작 실행</li>
<li><code>actionGo</code> 텍스트 필드에서 Enter 키를 누를 때 특정 동작 실행 (ex. 로그인)</li>
<li><code>actionNext</code> 다음 입력 필드로 포커스 이동</li>
<li><code>actionDone</code> 텍스트 입력 완료</li>
<li><code>actionSend</code> 입력된 텍스트 전송</li>
<li><code>actionNone</code> 키보드 동작 지정 X</li>
</ul>
<br>

<h3 id="3-example">3) Example</h3>
<p><img src="https://velog.velcdn.com/images/ars_yeon/post/627f4d19-55b6-4376-8a64-b7f3734a96ef/image.png" alt=""></p>
<ul>
<li>왼쪽은 imeOptions을 따로 설정하지 않았을 때의 키보드!</li>
<li>오른쪽은 imeOptions을 <code>actionSearch</code>로 설정했을 때의 키보드!</li>
</ul>
<br>

<p>이렇게 써서 각각의 형태에 맞게 기능이 실행되면 좋겠지만 따로 설정해줘야 함😢</p>
<br>

<h2 id="2-input-method-action">2. Input Method Action</h2>
<h3 id="1-input-method-action">1) Input Method Action?</h3>
<ul>
<li>사용자가 키보드에서 &quot;Enter&quot; 또는 &quot;다음&quot; 버튼을 눌렀을 때 실행되는 동작을 정의하는 메커니즘</li>
<li>사용자 경험과 앱의 상호작용을 개선하는 데 중요함</li>
</ul>
<h3 id="2-example">2) Example</h3>
<ul>
<li>imeOptions을 <code>actionSearch</code>로 설정했을때 실행될 동작을 정의할 코드<pre><code>searchEv.setOnEditorActionListener { _, actionId, _ -&gt;
  if (actionId == EditorInfo.IME_ACTION_SEARCH) {
      val query = searchEv.text.toString().trim()
      if (query.isNotEmpty()) {
          // 검색 동작 처리 로직을 여기에 추가
          // ex. setSearch(query) 또는 검색 화면으로 이동하는 등의 동작을 수행
      }
      // 검색 버튼을 눌렀을 때 항상 true 반환
      return@setOnEditorActionListener true
  }
  return@setOnEditorActionListener false
}</code></pre></li>
</ul>
<br>

<hr>
<br>

<p>[참고 사이트]</p>
<p><a href="https://developer.android.com/training/keyboard-input/style?hl=ko">&#39;입력 방법 유형 지정&#39;, Developers</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] #41 Retrofit]]></title>
            <link>https://velog.io/@ars_yeon/TIL-41-Retrofit</link>
            <guid>https://velog.io/@ars_yeon/TIL-41-Retrofit</guid>
            <pubDate>Mon, 18 Sep 2023 12:20:29 GMT</pubDate>
            <description><![CDATA[<h2 id="1-retrofit">1. Retrofit?</h2>
<ul>
<li>Square Inc.에서 개발한 HTTP 클라이언트 라이브러리</li>
<li>API 통신을 위해 구현된 OkHTTP의 HTTP 통신을 간편하게 만들어주는 라이브러리를 뜻함</li>
<li>안드로이드 앱 개발에서 주로 활용되지만, 다른 플랫폼에서도 사용할 수 있음</li>
</ul>
<br>

<h2 id="2-features">2. Features</h2>
<h3 id="1-간결한-코드">1) 간결한 코드</h3>
<ul>
<li>Retrofit을 사용하면 RESTful API 통신을 위한 코드를 간결하게 작성할 수 있음</li>
<li>인터페이스를 정의하고, 해당 인터페이스를 통해 HTTP 요청을 만들고 응답을 처리할 수 있음</li>
</ul>
<h3 id="2-동기-및-비동기-지원">2) 동기 및 비동기 지원</h3>
<ul>
<li>Retrofit은 동기 및 비동기 방식으로 HTTP 요청을 처리할 수 있음</li>
<li>이를 통해 UI 스레드에서 블로킹되지 않고 효율적으로 네트워크 요청을 관리할 수 있음</li>
</ul>
<h3 id="3-다양한-서버와의-통신-지원">3) 다양한 서버와의 통신 지원</h3>
<ul>
<li>Retrofit은 JSON, XML, Protocol Buffers와 같은 다양한 데이터 형식을 처리할 수 있음</li>
<li>다양한 서버와 통신하기 위한 다양한 HTTP 클라이언트 구성 옵션을 제공함</li>
</ul>
<h3 id="4-인터셉터">4) 인터셉터</h3>
<ul>
<li>인터셉터를 사용하여 요청 및 응답을 중간에서 가로채고 수정할 수 있음</li>
<li>인증 토큰 추가, 로깅, 캐싱 등의 작업을 수행하기에 용이함</li>
</ul>
<br>

<h2 id="3-elements">3. Elements</h2>
<h3 id="1-dto">1) DTO</h3>
<ul>
<li>Data Transfer Object</li>
<li>서버와 클라이언트 간에 데이터를 주고받을 때 사용하는 객체<ul>
<li>서버 응답의 구조와 동일한 필드를 갖음</li>
</ul>
</li>
<li>Gson 같은 컨버터를 사용하여 JSON 데이터를 DTO 객체로 변환</li>
</ul>
<h3 id="2-interface">2) Interface</h3>
<ul>
<li>API Interface</li>
<li>서버 API의 엔드포인트 및 HTTP 요청 메서드를 정의<ul>
<li>각 엔드포인트의 경로와 요청 메서드(GET, POST, PUT, DELETE 등)를 어노테이션을 사용하여 지정</li>
</ul>
</li>
<li>메서드의 매개변수와 반환 형식을 정의하여 Retrofit이 요청을 처리하고 응답을 파싱할 수 있게 함</li>
</ul>
<h3 id="3-retrofit-클래스">3) Retrofit 클래스</h3>
<ul>
<li>Retrofit을 초기화하고 설정함</li>
<li>서버와 통신할 기본 URL을 설정함</li>
<li>컨버터 팩토리를 추가하여 데이터 변환을 지원함</li>
<li>인터셉터를 추가하여 요청과 응답을 가로채고 수정할 수 있음</li>
<li>API 인터페이스와 실제 HTTP 요청을 수행하는 객체를 생성함</li>
<li>Retrofit 객체를 초기화하는 단계에서 서버의 기본 설정을 지정하므로 앱에서 전반적인 통신 구성을 제어할 수 있음</li>
</ul>
<br>

<h2 id="4-example">4. Example</h2>
<p>Retrofit을 사용하여 GET 요청을 보내고 응답을 처리하는 예제!</p>
<h3 id="1-buildgaldekts">1) build.galde.kts</h3>
<pre><code>implementation(&quot;com.squareup.retrofit2:retrofit:2.9.0&quot;)
implementation(&quot;com.squareup.retrofit2:converter-gson:2.9.0&quot;)</code></pre><h3 id="2-androidmanifestxml">2) AndroidManifest.xml</h3>
<p>인터넷 권한 설정!</p>
<pre><code>    &lt;uses-permission android:name=&quot;android.permission.INTERNET&quot; /&gt;</code></pre><h3 id="3-dto">3) DTO</h3>
<pre><code>// 카카오 이미지 검색 API 사용 예시
data class KakaoImage(
    val collection: String,
    @SerializedName(&quot;thumbnail_url&quot;)
    val thumbnailUrl: String,
    @SerializedName(&quot;image_url&quot;)
    val imageUrl: String,
    @SerializedName(&quot;display_sitename&quot;)
    val siteName: String,
    @SerializedName(&quot;doc_url&quot;)
    val docUrl: String,
    val datetime: String
)</code></pre><ul>
<li>속성명을 수정하고 싶다면 아래 예시처럼 <code>@SerializedName</code>에 원래 속성명을 쓰고 바꾸면 됨!<pre><code>  @SerializedName(&quot;thumbnail_url&quot;)
  val thumbnailUrl: String,\</code></pre>(JSON 데이터만 사용해봐서 XML도 똑같은지는 잘 모르겠음...)</li>
</ul>
<h3 id="4-interface">4) Interface</h3>
<pre><code>interface KakaoApi {
    @GET(&quot;v2/search/image&quot;)
    fun searchImage(
        @Query(&quot;query&quot;) query: String
    ): Call&lt;KakaoImageList&gt;
}</code></pre><ul>
<li>카카오 이미지 API의 엔드포인트 경로를 <code>@GET</code>에 써줌</li>
</ul>
<h3 id="5-retrofit-객체">5) Retrofit 객체</h3>
<pre><code>private val retrofit = Retrofit.Builder()
    .baseUrl(Constants.BASE_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .client(networkClient)
    .build()</code></pre><ul>
<li><code>Constants.BASE_URL</code>은 카카오 API URL을 상수로 설정해놓은거!</li>
</ul>
<h3 id="6-비동기적으로-요청-보내고-응답-처리">6) 비동기적으로 요청 보내고 응답 처리</h3>
<pre><code>try {
    val response = kakaoApi.getPosts()
    if (response.isSuccessful) {
        val posts = response.body()
        // 응답 데이터(posts)를 처리하는 로직 작성
    } else {
        // 오류 처리
    }
} catch (e: Exception) {
    // 네트워크 오류 처리
}</code></pre><br>

<hr>
<br>

<p>[참고 사이트]</p>
<p><a href="https://cocococo.tistory.com/entry/Kotlin-Retrofit-%EC%82%AC%EC%9A%A9%ED%95%9C-API-%ED%86%B5%EC%8B%A0-%EB%B0%A9%EB%B2%95">&#39;[Kotlin] Retrofit 사용한 API 통신 방법&#39;, 나의 개발 해방 일지</a>
<a href="https://minchanyoun.tistory.com/44">&#39;[kotlin][Android] retrofit2 (레트로핏) 사용방법&#39;, 챠니의 코딩일기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] #40 viewPager]]></title>
            <link>https://velog.io/@ars_yeon/TIL-40-ViewPager</link>
            <guid>https://velog.io/@ars_yeon/TIL-40-ViewPager</guid>
            <pubDate>Fri, 15 Sep 2023 13:48:31 GMT</pubDate>
            <description><![CDATA[<h2 id="1-viewpager">1. viewPager?</h2>
<ul>
<li>안드로이드 앱의 화면을 여러 개의 페이지로 나누고, 사용자가 이 페이지들을 좌우로 스와이프하면서 볼 수 있게 해주는 위젯</li>
<li>주로 이미지 갤러리, 화면 슬라이드쇼, 워크스룸, 탭 레이아웃, 메뉴 및 탭 간의 전환 등 다양한 용도로 사용</li>
<li>기존에 있던 viewPager는 사용 방법이 복잡하고 까다로웠다고 들었는데, 2019년 구글이 viewPager2를 발표하면서 조금 쉬워졌다고 한다!</li>
</ul>
<br>

<h2 id="2-features">2. Features</h2>
<h3 id="1-화면-슬라이딩">1) 화면 슬라이딩</h3>
<ul>
<li>ViewPager는 여러 페이지 사이를 스와이프하여 전환할 수 있도록 제공!</li>
</ul>
<h3 id="2-탭-표시">2) 탭 표시</h3>
<ul>
<li>ViewPager는 각 페이지에 대한 탭을 표시하고, 사용자가 탭을 터치하면 해당 페이지로 이동함</li>
</ul>
<h3 id="3-다양한-어댑터-사용">3) 다양한 어댑터 사용</h3>
<ul>
<li>ViewPager를 사용하면 어댑터를 통해 페이지의 데이터 및 뷰를 동적으로 로드하고 표시할 수 있음</li>
</ul>
<br>

<h2 id="3-advantages">3. Advantages</h2>
<h3 id="1-사용자-경험-향상">1) 사용자 경험 향상</h3>
<ul>
<li>ViewPager는 사용자에게 편리한 슬라이드 기능을 제공하여 앱의 사용자 경험을 향상시킴</li>
</ul>
<h3 id="2-콘텐츠-관리-용이성">2) 콘텐츠 관리 용이성</h3>
<ul>
<li>다양한 종류의 콘텐츠를 페이지로 표시할 수 있으므로 앱에서 다양한 정보를 구조적으로 관리하기 용이함</li>
</ul>
<h3 id="3-탭과의-통합">3) 탭과의 통합</h3>
<ul>
<li>ViewPager와 함께 TabLayout을 사용하면 탭 간의 전환을 더욱 편리하게 관리할 수 있음</li>
</ul>
<br>


<h2 id="4-example">4. Example</h2>
<p>간단하게 이미지 슬라이드를 만들어보려고 함!</p>
<h3 id="1-buildgradlekts">1) build.gradle.kts</h3>
<p>ViewPager를 사용하기 위해서는 dependencies에 아래 코드를 추가해줘야 함</p>
<pre><code>implementation (&quot;androidx.viewpager2:viewpager2:1.0.0&quot;)</code></pre><h3 id="2-activity_mainxml">2) activity_main.xml</h3>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;RelativeLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;&gt;

    &lt;androidx.viewpager2.widget.ViewPager2
        android:id=&quot;@+id/viewPager&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot; /&gt;
&lt;/RelativeLayout&gt;</code></pre><h3 id="3-item_imagexml">3) item_image.xml</h3>
<p>이미지 표시를 위한 레이아웃</p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;&gt;

    &lt;ImageView
        android:id=&quot;@+id/imageView&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:adjustViewBounds=&quot;true&quot;
        android:scaleType=&quot;centerCrop&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;</code></pre><h3 id="4-mainactivitykt">4) MainActivity.kt</h3>
<pre><code>class MainActivity : AppCompatActivity() {
    private lateinit var viewPager: ViewPager2
    private val images = listOf(
        R.drawable.image1,
        R.drawable.image2,
        R.drawable.image3
    )

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

        viewPager = findViewById(R.id.viewPager)
        val adapter = ImageAdapter(images)
        viewPager.adapter = adapter
    }
}</code></pre><h3 id="5-imageadapterkt">5) ImageAdapter.kt</h3>
<p>ViewPager에서 이미지를 표시하는 데 사용할 어댑터</p>
<pre><code>class ImageAdapter(private val images: List&lt;Int&gt;) : RecyclerView.Adapter&lt;ImageAdapter.ImageViewHolder&gt;() {

    inner class ImageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val imageView: ImageView = itemView.findViewById(R.id.imageView)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
        val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_image, parent, false)
        return ImageViewHolder(itemView)
    }

    override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
        val imageResource = images[position]
        holder.imageView.setImageResource(imageResource)
    }

    override fun getItemCount(): Int {
        return images.size
    }
}</code></pre><h3 id="6-result">6) Result</h3>
<p><img src="https://velog.velcdn.com/images/ars_yeon/post/e86c824d-383d-42be-a7b7-768b8f702dc9/image.gif" alt="sample"></p>
<br>

<hr>
<br>

<p>[참고 사이트]</p>
<p><a href="https://developer.android.com/jetpack/androidx/releases/viewpager2?hl=ko">&#39;ViewPager2&#39;, Develpers</a>
<a href="https://github.com/android/views-widgets-samples/tree/main/ViewPager2">&#39;ViewPager2 samples&#39;, Android(GitHub)</a>
<a href="https://developer.android.com/guide/navigation/navigation-swipe-view-2?hl=ko">&#39;ViewPager2를 사용하여 탭으로 스와이프 뷰 만들기&#39;, Developers</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] #39 How to hide API key 2]]></title>
            <link>https://velog.io/@ars_yeon/TIL-39-How-to-hide-API-key-2</link>
            <guid>https://velog.io/@ars_yeon/TIL-39-How-to-hide-API-key-2</guid>
            <pubDate>Thu, 14 Sep 2023 10:59:35 GMT</pubDate>
            <description><![CDATA[<p>어제 API Key 숨기는 방법에 대해 올렸는데 알고 보니 그건 구글 맵만 가능한 것이었다....😂</p>
<p>오늘 미세먼지 앱을 만들어보면서 공공 데이터 포털에 있는 API를 사용했는데 어제 그 방법으로 적용이 안됐다....그래서 또 구글링을 엄청 해보면서 내 안스 버전에 맞는 방법을 알아냈다!
그런데 이 방법이 더 간단한 듯...?
이 방법도 블로그에 기록해야지!!🔥</p>
<hr>
<h2 id="hide-api-key-ver2">Hide API Key (ver.2)</h2>
<ul>
<li><span style="background-color: #F5F0FF; color: #462679; font-weight: bold">&nbsp;안드로이드 스튜디오 버전: Giraffe&nbsp;</span></li>
<li><span style="background-color: #F5F0FF; color: #462679; font-weight: bold">&nbsp;빌드 구성(Gradle) 언어: DLS&nbsp;</span></li>
<li><span style="background-color: #F5F0FF; color: #462679; font-weight: bold">&nbsp;사용할 API: 공공데이터포털&nbsp;</span> - <a href="https://www.data.go.kr/data/15073861/openapi.do">(한국환경공단 에어코리아 대기오염정보)</a></li>
</ul>
<ul>
<li>사용할 파일들!  <img src="https://velog.velcdn.com/images/ars_yeon/post/9a070e0f-10bd-44b7-8754-ddaff52ae3ec/image.png" width="60%" height="60%">

</li>
</ul>
<br>

<h3 id="1-localproperties">1) local.properties</h3>
<ul>
<li><code>local.properties</code> 파일에 아래 코드를 추가!</li>
</ul>
<pre><code>DUST_API_KEY=&quot;YOUR_API_KEY&quot;</code></pre><ul>
<li><code>DUST_API_KEY</code>는 사용할 API Key 구분을 위한 이름<ul>
<li>미세먼지 앱을 만들고 있어서 <code>DUST_API_KEY</code>라고 이름 설정함</li>
</ul>
</li>
<li><code>YOUR_API_KEY</code>는 사용할 API 키로 변경!</li>
</ul>
<br>

<h3 id="2-buildgradlekts-app">2) build.gradle.kts (:app)</h3>
<ul>
<li><code>build.gradle (Module :app)</code> 파일에 아래 코드를 추가!</li>
</ul>
<pre><code>import java.util.Properties

val properties = Properties()
val localPropertiesFile = rootProject.file(&quot;local.properties&quot;)
if (localPropertiesFile.exists()) {
    localPropertiesFile.inputStream().use { input -&gt;
        properties.load(input)
    }
}

android {

    // ...

    defaultConfig {

        // ...

        buildConfigField(&quot;String&quot;, &quot;DUST_API_KEY&quot;, &quot;${properties[&quot;DUST_API_KEY&quot;]}&quot;)
    }

    // ...

    buildFeatures {
        // ...
        buildConfig = true
    }
}</code></pre><ul>
<li>위 코드는 <code>local.properties</code> 파일에 저장된 API 키를 불러와 앱의 빌드 구성에 설정하여, 앱의 다른 부분에서 이 API 키를 사용할 수 있도록 해주는 역할을 함!</li>
</ul>
<br>

<h3 id="3-api를-사용하는-activity-또는-fragment">3) API를 사용하는 Activity 또는 Fragment...</h3>
<ul>
<li>난 MainActivity에 썼음!</li>
<li>API를 불러오고자 하는 파일에 아래 코드를 추가!</li>
</ul>
<pre><code>val authKey = BuildConfig.DUST_API_KEY</code></pre><br>

<hr>
<br>

<p>[참고 사이트]</p>
<p><a href="https://yline.tistory.com/9">&#39;[ 안드로이드 - Kotlin ] API KEY 관리&#39;, Y_LINE&#39;s_Repository</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] #38 How to hide API key - Giraffe, DLS]]></title>
            <link>https://velog.io/@ars_yeon/TIL-38-How-to-hide-API-key-Giraffe-DLS</link>
            <guid>https://velog.io/@ars_yeon/TIL-38-How-to-hide-API-key-Giraffe-DLS</guid>
            <pubDate>Wed, 13 Sep 2023 06:11:16 GMT</pubDate>
            <description><![CDATA[<p>지난번에 날씨앱 만들기 클론 코딩을 하면서 API를 사용한 적이 있다. 그런데 GitHub에 올리려면 API를 숨겨야 한다는데 구글링하면 버전이나 Gradle 스크립트 언어가 달라서 이해하기가 어려워서 포기했었다...
그러다 오늘 미친듯이 구글링하고 Chat GPT랑 싸우면서 알아냈다.😂
API는 계속 사용할 것 같으니까 블로그에 기록해야지!!🔥</p>
<hr>
<h2 id="1-api">1. API?</h2>
<ul>
<li>Application Programming Interface</li>
<li>프로그램 간에 상호 작용할 수 있도록 만들어진 일련의 규칙과 도구를 제공하는 인터페이스</li>
<li>소프트웨어 애플리케이션들이 서로 통신하고 데이터를 공유할 수 있게 해주는 방법</li>
<li>데이터를 요청하고 응답을 받는 데 사용되며, 여러 애플리케이션이나 서비스 간의 상호 작용을 용이하게 만듦<br>

</li>
</ul>
<h2 id="2-why-should-i-hide-api-keys">2. Why should I hide API Keys?</h2>
<h3 id="1-보안">1) 보안</h3>
<ul>
<li>API Key는 중요한 정보이며, 공개되면 무단으로 API를 사용하거나 액세스할 수 있게 됨</li>
<li>악의적인 공격자가 API Key를 알게 되면 데이터 유출, 개인정보 침해 등의 보안 문제가 발생할 수 있음</li>
<li>API Key를 숨기면 이러한 보안 위험을 최소화할 수 있음</li>
</ul>
<h3 id="2-권한-관리">2) 권한 관리</h3>
<ul>
<li>API Key는 누가 어떤 작업을 수행할 수 있는지를 제어하는 데 사용됨<ul>
<li>ex1. 서비스 제공 업체는 특정 애플리케이션 또는 개발자에 대한 액세스 권한을 설정할 수 있음</li>
</ul>
</li>
<li>API Key를 사용하여 API를 호출하는 애플리케이션의 신원을 확인하고, 그에 따라 허용되는 작업을 결정할 수 있음<ul>
<li>ex2. 유료 계정을 가진 애플리케이션에만 API Key를 제공하고, 무료 계정을 가진 애플리케이션에는 제공하지 않는 식으로 권한을 관리할 수 있음</li>
</ul>
</li>
</ul>
<h3 id="3-비용-관리">3) 비용 관리</h3>
<ul>
<li>API Key를 사용하여 API 호출을 추적하고 비용을 관리할 수 있음</li>
<li>무단으로 API를 호출하면 불필요한 비용이 발생할 수 있으므로 API Key를 사용하여 비용을 제어하고 모니터링할 수 있음</li>
</ul>
<br>

<h2 id="3-hide-api-key">3. Hide API Key</h2>
<ul>
<li><span style="background-color: #F5F0FF; color: #462679; font-weight: bold">&nbsp;안드로이드 스튜디오 버전: Giraffe&nbsp;</span></li>
<li><span style="background-color: #F5F0FF; color: #462679; font-weight: bold">&nbsp;빌드 구성(Gradle) 언어: DLS&nbsp;</span></li>
<li><span style="background-color: #F5F0FF; color: #462679; font-weight: bold">&nbsp;사용할 API: Google Map&nbsp;</span></li>
<li>API 키 제한은 따로 하지 않음<ul>
<li>애플리케이션 보안을 강화하려면 선택적으로 하면 됨</li>
</ul>
</li>
</ul>
<ul>
<li>사용할 파일들!  <img src="https://velog.velcdn.com/images/ars_yeon/post/3bf7f114-3569-480b-abc4-d30bc34aefbf/image.png" width="60%" height="60%">

</li>
</ul>
<h3 id="1-buildgradlekts-project">1) build.gradle.kts (Project)</h3>
<ul>
<li><code>build.gradle (Project)</code> 파일에 아래 코드를 추가!</li>
</ul>
<pre><code>buildscript {
    dependencies {
        classpath(&quot;com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1&quot;)
    }
}</code></pre><ul>
<li><span style="background-color: #F5F0FF; color: #462679; font-weight: bold">&nbsp;Android용 Secrets Gradle Plugin&nbsp;</span>으로 API Key를 비교적 쉽게 숨길 수 있음<ul>
<li>최신버전으로 사용하기!<br>

</li>
</ul>
</li>
</ul>
<h3 id="2--buildgradlekts-module-app">2)  build.gradle.kts (Module :app)</h3>
<ul>
<li><code>build.gradle (Module :app)</code> 파일에 아래 코드를 추가!</li>
</ul>
<pre><code>plugins {
    // ...
    id(&quot;com.google.android.libraries.mapsplatform.secrets-gradle-plugin&quot;)
}</code></pre><br>

<h3 id="3-localproperties">3) local.properties</h3>
<ul>
<li><code>local.properties</code> 파일에 아래 코드를 추가!</li>
</ul>
<pre><code>GOOGLE_API_KEY=&quot;YOUR_API_KEY&quot;</code></pre><ul>
<li><code>GOOGLE_API_KEY</code>는 사용할 API Key 구분을 위한 이름</li>
<li><code>YOUR_API_KEY</code>는 사용할 API 키로 변경!<br>

</li>
</ul>
<h3 id="4-androidmanifestxml">4) AndroidManifest.xml</h3>
<ul>
<li><code>AndroidManifest.xml</code> 파일에 아래 코드를 추가!</li>
</ul>
<pre><code>&lt;meta-data
    android:name=&quot;com.google.android.geo.API_KEY&quot;
    android:value=&quot;${GOOGLE_MAP_KEY}&quot; /&gt;</code></pre><ul>
<li><code>name</code><ul>
<li>API Key의 유형과 버전을 명시하는 역할을 함</li>
<li>API 버전에 따라 이름이 달라지는 경우도 있음</li>
</ul>
</li>
<li><code>value</code><ul>
<li>API Key 값</li>
<li>local.properties에서 작성한 <code>GOOGLE_API_KEY</code>로 대체하여 작성!</li>
</ul>
</li>
</ul>
<br>

<hr>
<br>

<p>[참고 사이트]</p>
<p><a href="https://brunch.co.kr/@operator/65">&#39;API란 무엇일까? API 쉽게 이해하기&#39;, 채널톡(blu)</a>
<a href="https://developers.google.com/maps/documentation/android-sdk/secrets-gradle-plugin?hl=ko">&#39;Secrets Gradle 플러그인&#39;, Google Maps Platform(developers)</a>
<a href="https://github.com/google/secrets-gradle-plugin">&#39;Secrets Gradle Plugin for Android&#39;, google(GitHub)</a>
<a href="https://velog.io/@chloedewyes/%EA%B5%AC%EA%B8%80-%EB%A7%B5-API-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B3%A0-API-%ED%82%A4-%EC%88%A8%EA%B8%B0%EA%B8%B0">&#39;구글 맵 API 사용하고 API 키 숨기기&#39;, chloedewyes.log</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] #37 SharedPreferences]]></title>
            <link>https://velog.io/@ars_yeon/TIL-37-SharedPreferences</link>
            <guid>https://velog.io/@ars_yeon/TIL-37-SharedPreferences</guid>
            <pubDate>Tue, 12 Sep 2023 10:52:46 GMT</pubDate>
            <description><![CDATA[<h2 id="1-sharedpreferences">1. SharedPreferences?</h2>
<ul>
<li>안드로이드 앱에서 간단한 키-값 쌍 형태로 데이터를 저장하고 관리할 수 있는 메커니즘</li>
<li>이 데이터는 앱을 종료하고 다시 실행할 때에도 유지됨</li>
<li>주로 설정, 세션 정보, 앱 상태 등을 저장하기 위해 사용됨</li>
</ul>
<br>

<h2 id="2-how-to-use-sharedpreferences">2. How to use SharedPreferences</h2>
<h3 id="1-sharedpreferences-객체-생성">1) SharedPreferences 객체 생성</h3>
<p><code>getSharedPreferences()</code> 메서드를 사용</p>
<pre><code>val prefs = context.getSharedPreferences(&quot;my_preferences&quot;, Context.MODE_PRIVATE)</code></pre><ul>
<li><code>my_preferences</code>: <strong>SharedPreferences</strong> 파일의 이름<ul>
<li>여러 앱에서 공유할 필요가 없다면 이 이름은 고유해야 함</li>
</ul>
</li>
<li><code>Context.MODE_PRIVATE</code>: <strong>SharedPreferences</strong> 파일의 모드<ul>
<li>파일의 모드에는 4가지가 있지만 현재 Android에서는 주로 <code>Context.MODE_PRIVATE</code>를 사용하여 <strong>SharedPreferences</strong> 파일을 생성하고 앱 내에서만 접근 가능하도록 함!</li>
<li>다른 모드들은 보안 및 데이터 문제로 인해 권장되지 않으며, 사용을 피하는 것이 좋음!</li>
</ul>
</li>
</ul>
<br>

<h3 id="2-데이터-저장">2) 데이터 저장</h3>
<p><code>SharedPreferences</code>에 데이터를 저장하기 위해서는 <code>SharedPreferences.Editor</code> 객체를 얻어야 함</p>
<pre><code>val editor = prefs.edit()
editor.putString(&quot;username&quot;, &quot;ars_yeon&quot;)
editor.putInt(&quot;age&quot;, 25)
editor.apply()</code></pre><br>

<h3 id="3-데이터-불러오기">3) 데이터 불러오기</h3>
<pre><code>val username = prefs.getString(&quot;username&quot;, &quot;&quot;)
val age = prefs.getInt(&quot;age&quot;, 0)</code></pre><ul>
<li>첫 번째 매개변수: 키</li>
<li>두 번째 매개변수: 해당 키로 저장된 값이 없을 때의 기본값</li>
</ul>
<br>

<h2 id="3-example">3. Example</h2>
<h3 id="1-mainactivitykt">1) MainActivity.kt</h3>
<pre><code>class MainActivity : AppCompatActivity() {
    private lateinit var prefsUtil: PrefsUtil

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

        prefsUtil = PrefsUtil(this)

        // 데이터 저장
        prefsUtil.saveUsername(&quot;ars_yeon&quot;)
        prefsUtil.saveAge(25)

        // 데이터 불러오기
        val username = prefsUtil.getUsername()
        val age = prefsUtil.getAge()

        // 불러온 데이터 사용
        println(&quot;사용자 이름: $username&quot;)
        println(&quot;나이: $age 세&quot;)
    }
}
</code></pre><br>

<h3 id="2-prefsutilkt">2) PrefsUtil.kt</h3>
<pre><code>class PrefsUtil(context: Context) {
    private val prefs = context.getSharedPreferences(&quot;pref&quot;, Context.MODE_PRIVATE)

    fun getUsername(defaultValue: String = &quot;&quot;): String {
        return prefs.getString(&quot;username&quot;, defaultValue) ?: defaultValue
    }

    fun getAge(defaultValue: Int = 0): Int {
        return prefs.getInt(&quot;age&quot;, defaultValue)
    }

    fun saveUsername(username: String) {
        prefs.edit().putString(&quot;username&quot;, username).apply()
    }

    fun saveAge(age: Int) {
        prefs.edit().putInt(&quot;age&quot;, age).apply()
    }
}</code></pre><br>

<hr>
<br>

<p>[참고 사이트]</p>
<p><a href="https://developer.android.com/training/data-storage/shared-preferences?hl=ko">&#39;SharedPreferences로 단순 데이터 저장하기&#39;, Developer</a>
<a href="https://youngdroidstudy.tistory.com/entry/Kotlin-%EC%BD%94%ED%8B%80%EB%A6%B0%EC%9D%98-SharedPreferences-%EB%9E%80">&#39;Kotlin 코틀린의 SharedPreferences&#39;, 용이의 개발블로그</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] #36 Bundle]]></title>
            <link>https://velog.io/@ars_yeon/TIL-36-Bundle</link>
            <guid>https://velog.io/@ars_yeon/TIL-36-Bundle</guid>
            <pubDate>Mon, 11 Sep 2023 13:47:13 GMT</pubDate>
            <description><![CDATA[<h2 id="1-bundle">1. Bundle?</h2>
<ul>
<li>key-value 쌍의 데이터 컨테이너</li>
<li>데이터를 저장하고 전달하는 데 사용함<ul>
<li>key: String</li>
<li>value: Int, String, Serializable, Parcelable...</li>
</ul>
</li>
<li>Android에서는 객체를 전달할 때 보통 Parcelable을 구현한 객체를 전달함<ul>
<li>참고: <a href="https://developer.android.com/guide/components/activities/parcelables-and-bundles?hl=ko">Parcelabel</a> </li>
</ul>
</li>
</ul>
<h2 id="2-주요-기능">2. 주요 기능</h2>
<h3 id="1-데이터-전달">1) 데이터 전달</h3>
<ul>
<li>인텐트(Intent)를 통해 다른 액티비티로 데이터를 전달하는 데 사용<ul>
<li>ex) 다른 액티비티로 문자열, 숫자, 객체 등의 데이터를 전달할 수 있음</li>
</ul>
</li>
</ul>
<h3 id="2-상태-저장-및-복원">2) 상태 저장 및 복원</h3>
<ul>
<li>화면 회전 또는 앱이 일시 중지된 경우와 같은 상황에서 Bundle을 사용하여 액티비티 또는 프래그먼트의 상태를 저장하고 다시 복원할 수 있음</li>
</ul>
<h3 id="3-프래그먼트-간-통신">3) 프래그먼트 간 통신</h3>
<ul>
<li>프래그먼트 간에 데이터를 전달하는 데에도 Bundle을 사용할 수 있음</li>
<li>이를 통해 프래그먼트 간의 통신이 가능해짐</li>
</ul>
<br>

<h2 id="3-사용-방법">3. 사용 방법</h2>
<h3 id="1-데이터-추가">1) 데이터 추가</h3>
<ul>
<li>데이터를 추가하려면 <code>put</code> 메서드 사용</li>
</ul>
<pre><code>val bundle = Bundle()
bundle.putString(&quot;key&quot;, &quot;value&quot;)</code></pre><h3 id="2-데이터-추출">2) 데이터 추출</h3>
<ul>
<li>데이터를 추출하려면 <code>get</code> 메서드 사용</li>
</ul>
<pre><code>val value = bundle.getString(&quot;key&quot;)</code></pre><h3 id="3-인텐트와-함께-사용">3) 인텐트와 함께 사용</h3>
<ul>
<li><code>Intent</code>에 첨부하여 다른 액티비티로 데이터 전달</li>
</ul>
<pre><code>val intent = Intent(this, SecondActivity::class.java)
intent.putExtra(&quot;dataBundle&quot;, bundle)
startActivity(intent)</code></pre><h3 id="4-상태-저장-및-복원">4) 상태 저장 및 복원</h3>
<ul>
<li><code>onSaveInstanceState</code>와 <code>onRestoreInstanceState</code> 메서드를 사용하여 액티비티 상태 저장 및 복원</li>
</ul>
<pre><code>override fun onSaveInstanceState(outState: Bundle) {
    outState.putString(&quot;key&quot;, &quot;value&quot;)
    super.onSaveInstanceState(outState)
}

override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    super.onRestoreInstanceState(savedInstanceState)
    val value = savedInstanceState.getString(&quot;key&quot;)
}</code></pre><h3 id="5-프래그먼트-간-통신">5) 프래그먼트 간 통신</h3>
<ul>
<li>프래그먼트 간에 데이터 전달</li>
</ul>
<pre><code>val fragment = MyFragment()
val bundle = Bundle()
bundle.putString(&quot;key&quot;, &quot;value&quot;)
fragment.arguments = bundle</code></pre><br>

<h2 id="4-bundle의-주의-사항">4. Bundle의 주의 사항</h2>
<h3 id="1-크기-제한">1) 크기 제한</h3>
<ul>
<li>데이터를 저장하고 전달하는 데 사용되지만 크기에 제한이 있음</li>
<li>큰 데이터를 저장하려면 다른 방법을 고려해야 할 수 있음</li>
</ul>
<h3 id="2-직렬화">2) 직렬화</h3>
<ul>
<li>저장할 수 있는 데이터 유형에는 제한이 있으며 사용자 정의 클래스를 저장하려면 직렬화를 구현해야 함</li>
</ul>
<h3 id="3-키-설정">3) 키 설정</h3>
<ul>
<li>데이터를 추출할 때 사용하는 키는 고유해야 함</li>
<li>중복된 키를 사용하면 데이터가 덮어씌워질 수 있음</li>
</ul>
<h3 id="4-null-값-다루기">4) Null 값 다루기</h3>
<ul>
<li><code>null</code> 값을 저장하려면 <code>put</code> 메서드 대신 <code>putNullable</code> 메서드를 사용해야 함</li>
</ul>
<br>

<hr>
<br>

<p>[참고 사이트]</p>
<p><a href="https://kotlinworld.com/45">&#39;[Bundle] Android Bundle 이란 무엇인가? Bundle을 이용해 데이터 전달하기&#39;, 조세영의 Kotlin World</a>
<a href="https://developer.android.com/guide/components/activities/parcelables-and-bundles?hl=ko">&#39;Parcelabel 및 번들&#39;, Developer</a> </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[KTP] #4 CESCO 회고]]></title>
            <link>https://velog.io/@ars_yeon/KTP-4-CESCO</link>
            <guid>https://velog.io/@ars_yeon/KTP-4-CESCO</guid>
            <pubDate>Mon, 11 Sep 2023 12:04:23 GMT</pubDate>
            <description><![CDATA[<h1 id="1-한-주의-흐름">1. 한 주의 흐름</h1>
<h2 id="1-한-일">1) 한 일</h2>
<h3 id="1-프로젝트">(1) 프로젝트</h3>
<ul>
<li>연락처 앱 만들기</li>
<li><strong>Party Pokemon</strong></li>
</ul>
<h3 id="2-필수-작업">(2) 필수 작업</h3>
<ul>
<li><input checked="" disabled="" type="checkbox"> TabLayout 와 ViewPager</li>
<li><input checked="" disabled="" type="checkbox"> 연락처 리스트 (ContactListFragment)</li>
<li><input checked="" disabled="" type="checkbox"> 연락처 추가 (AddContactDialog or AddContactDialogFragment)</li>
<li><input checked="" disabled="" type="checkbox"> 상세 정보 (ContactDetailFragment)</li>
<li><input checked="" disabled="" type="checkbox"> 마이 페이지 (MyPageFragment)</li>
</ul>
<h3 id="3-추가-작업">(3) 추가 작업</h3>
<ul>
<li><input checked="" disabled="" type="checkbox"> ItemViewType 변경 적용</li>
<li><input checked="" disabled="" type="checkbox"> Event 시간에 맞춰 Notification 표시</li>
<li><input checked="" disabled="" type="checkbox"> Swipe-to-Action</li>
<li><input checked="" disabled="" type="checkbox"> 실제 폰에 있는 연락처 불러오기</li>
<li><input disabled="" type="checkbox"> 그 밖에 <code>MotionLayout</code>, <code>RecyclerView.ItemAnimator</code> 등의 특수 효과도 활용해 보세요.</li>
<li><input checked="" disabled="" type="checkbox"> 북마크 기능</li>
</ul>
<br>

<h2 id="2-keep">2) KEEP</h2>
<ul>
<li>팀 커뮤니케이션이 다음 팀 혹은 다른 프로젝트까지 원활하게 이어졌으면 좋겠음</li>
<li>앞으로도 팀 보드와 이슈를 이용하여 각 역할 할당 및 관리가 철저하게 이루어졌으면 좋겠음 </li>
<li>브랜치명 및 PR룰, 커밋 룰 등을 세분화해서 사용하는 것이 유지되었으면 좋겠음</li>
<li>끈기 있게 포기하지 않고 물고 늘어졌던 자세를 잊지 않았으면 좋겠음</li>
</ul>
<br>

<h2 id="3-problem">3) PROBLEM</h2>
<ul>
<li>데드 라인을 좀 더 명확하게 설정하고 실패했으면 바로 다른 사람에게 넘기거나 코드 리뷰 등으로 대처할 수 있었으면 좋겠음.</li>
<li>PR룰 제목 통일성 등 좀 더 세분화해볼 것</li>
<li>회의록을 조금 더 세세하게 적고 특정 시간 회의록 뿐만 아니라 논의 점을 문서화 시켜서 시간 비용을 줄일 것</li>
<li>테스트 케이스를 확실히 설정해서 빌드를 한 다음에 확실하게 머지를 할 것</li>
<li>각자의 코드 리뷰하여 구현한 것을 다른 사람하고 확실하게 공유 해 볼 것</li>
</ul>
<br>

<h2 id="4-try">4) TRY</h2>
<ul>
<li>중간 점검을 설정하여 역할의 재분배가 일어날 수 있도록 할 것</li>
<li>팀 노션 작성 시 회의를 조금 더 세분화 해볼 것</li>
<li>꾸준한 문서 기록을 남기는 습관을 들일 것</li>
<li>본인이 구현한 기능에 대해서 완전히 이해를 하고 영향을 미칠 기능들에 대해 파악하여 리스트를 작성해 볼 것</li>
<li>팀 프로젝트 시간이 빡빡하더라도 8시 이후 저녁 회의 시간에 새로운 기술 등의 리뷰 혹은 구현한 기술에 관한 문서 자료등의 적극적인 공유</li>
<li>프로젝트 주간에 데일리 스크럼 제대로 작성할 것</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WIL] #6 내배캠 9주차 주간회고]]></title>
            <link>https://velog.io/@ars_yeon/WIL-6-nbcamp-week9</link>
            <guid>https://velog.io/@ars_yeon/WIL-6-nbcamp-week9</guid>
            <pubDate>Mon, 11 Sep 2023 02:25:38 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-facts">📌 FACTS</h2>
<ul>
<li>안드로이드 앱개발 숙련 팀프로젝트 - 연락처 앱 만들기<ul>
<li><a href="https://github.com/Sesco-nb13/contact-app.git">Team Oragnizations - Repository</a></li>
</ul>
</li>
</ul>
<br>

<h2 id="📌-feelings">📌 FEELINGS</h2>
<ul>
<li>이번 팀 프로젝트가 가장 도움이 많이 되었던 것 같다.<ul>
<li>할 수 있는 것들보다 안해본 것들을 주로 구현하여서 배운 점이 많았다.</li>
</ul>
</li>
<li>항상 느끼지만 팀원 간의 소통이 가장 중요한 것 같다!<ul>
<li>팀원들과 편해져서 소통이 원활해서 어려운 점이나 버그가 생겼을 때 바로바로 질문하고 해결할 수 있었다.</li>
</ul>
</li>
<li>팀 프로젝트를 하면서 Kanban Board, Issue, PR을 활용하는 건 처음이었는데, 누가 어떤걸 진행하고 있는지, 어떤걸 해결했는지 등을 쉽게 확인할 수 있어서 편했다.</li>
</ul>
<br>

<h2 id="📌-findings">📌 FINDINGS</h2>
<ul>
<li>GitHub: Kanban Board, Issue, PR 활용</li>
<li>연락처, 전화, 갤러리 권한 설정 및 권한 요청<ul>
<li>갤러리 권한은 API 33 이상부터는 다르게 한다는 것을 알게 됨</li>
<li>갤러리에서 선택한 이미지를 화면에 나타내기 위해 <code>onActivityResult</code> 메서드에 이미지 URI를 불러오는 코드를 작성해야 된다는 것을 알게 됨</li>
</ul>
</li>
<li><code>setImageURI</code>로 기기의 이미지 사용<ul>
<li><code>Glide</code>나 <code>Picasso</code> 등으로도 가능하지만 <code>setImageURI</code>가 더 간편했음</li>
</ul>
</li>
<li><code>swipe</code>로 전화걸기 로직을 구현</li>
</ul>
<br>

<h2 id="📌-future">📌 Future</h2>
<ul>
<li>지금까지 배운 것 복습하기<ul>
<li>특히, 갤러리 권한 API 33 이상은 어떻게 구현해야 하는지 공부!</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] #35 Notification]]></title>
            <link>https://velog.io/@ars_yeon/TIL-35-Notification</link>
            <guid>https://velog.io/@ars_yeon/TIL-35-Notification</guid>
            <pubDate>Fri, 08 Sep 2023 14:20:26 GMT</pubDate>
            <description><![CDATA[<h2 id="1-notification">1. Notification?</h2>
<ul>
<li>앱의 UI와 별도로 사용자에게 앱과 관련한 정보를 보여주는 기능</li>
<li>알림을 터치하여 해당 앱을 열 수 있음<ul>
<li>바로 간단한 작업(ex. 문자 답하기)을 할 수 있음 (Android 7.0부터)</li>
</ul>
</li>
<li>보통 단말기 상단 부분에 표시되고, 앱 아이콘의 배지로도 표시 (Android 8.0부터)</li>
</ul>
<br>

<h2 id="2-notification-channel-android-80-and-later">2. Notification Channel (Android 8.0 and later)</h2>
<ul>
<li>Android 8.0이상의 경우는 알림을 만들기 전에 알림 채널을 먼저 만들어야 함</li>
<li>알림 채널은 알림을 그룹하여 알림 활성화나 방식을 변경 할 수 있음</li>
<li>현재 앱이 실행 중인 안드로이드 버전을 확인하여 8.0 이상인 경우만 채널 생성</li>
</ul>
<pre><code>private val NotifyID = 1
private val channelID = &quot;default&quot;

private fun createNotificationChannel() {
    if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.O) { // Android 8.0
        val channel = NotificationChannel(channelID, &quot;default channel&quot;,
            NotificationManager.IMPORTANCE_DEFAULT)
        channel.description = &quot;description text of this channel.&quot;
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(channel)
    }
}</code></pre><br>

<h2 id="3-create-notifications">3. Create Notifications</h2>
<h3 id="1-notificationcompatbuilder-객체에서-알림에-대한-ui정보와-작업-지정">1) NotificationCompat.Builder 객체에서 알림에 대한 UI정보와 작업 지정</h3>
<ul>
<li>setSmallIcon() : 작은 아이콘</li>
<li>setContentTitle() : 제목</li>
<li>setContentText() : 세부텍스</li>
</ul>
<h3 id="2-notificationcompatbuilderbuild호출">2) NotificationCompat.Builder.build()호출</h3>
<ul>
<li>Notification 객체를 반환</li>
</ul>
<h3 id="3-notificationmanagercompatnotify를-호출해서-시스템에-notification객체-전달">3) NotificationManagerCompat.notify()를 호출해서 시스템에 Notification객체 전달</h3>
<pre><code>private val NotifyID = 1

private fun showNotification() {
    val builder = NotificationCompat.Builder(this, channelID)
        .setSmallIcon(R.mipmap.ic_launcher)
        .setContentTitle(&quot;title&quot;)
        .setContentText(&quot;notification text&quot;)
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
    NotificationManagerCompat.from(this).notify(NotifyID, builder.build())
}</code></pre><br>

<h2 id="4-notification-importance">4. Notification Importance</h2>
<h3 id="1-채널-중요도-android-80이상">1) 채널 중요도 (Android 8.0이상)</h3>
<ul>
<li>NotificationChannel<ul>
<li>channelID, &quot;defaultchannel&quot;, NotificationManager.IMPORTANCE_DEFAULT</li>
</ul>
</li>
</ul>
<h3 id="2-알림-우선순위-android-71이하">2) 알림 우선순위 (Android 7.1이하)</h3>
<ul>
<li>NotificationCompat.Builder(this,channelID).setPriority(NotificationCompat.PRIORITY_DEFAULT)</li>
</ul>
<h3 id="3-중요도-순위">3) 중요도 순위</h3>
<p>(채널 / 알림 / 중요도 / 우선순위 수준)</p>
<table>
<thead>
<tr>
<th>중요도</th>
<th>설명</th>
<th>중요도 (Android 8.0 이상)</th>
<th>우선순위 (Android 7.1 이하)</th>
</tr>
</thead>
<tbody><tr>
<td>긴급</td>
<td>알림음이 울림, 헤드업 알림 표시</td>
<td>IMPORTANCE_HIGH</td>
<td>PRIORITY_HIGH</td>
</tr>
<tr>
<td>높음</td>
<td>알림음이 울림</td>
<td>IMPORTANCE_DEFAULT</td>
<td>PRIORITY_DEFAULT</td>
</tr>
<tr>
<td>중간</td>
<td>알림음 X</td>
<td>IMPORTANCE_LOW</td>
<td>PRIORITY_LOW</td>
</tr>
<tr>
<td>낮음</td>
<td>알림음 X, 상태 표시줄 표시 X</td>
<td>IMPORTANCE_MIN</td>
<td>PRIORITY_MIN</td>
</tr>
</tbody></table>
<br>

<p>[참고 사이트]</p>
<p><a href="https://developer.android.com/develop/ui/views/notifications/build-notification">&#39;Create a Notification&#39;, developers</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] #34 Date Picker Dialog - Specifying a Date Range]]></title>
            <link>https://velog.io/@ars_yeon/TIL-34-Date-Picker-Dialog-Specifying-a-Date-Range</link>
            <guid>https://velog.io/@ars_yeon/TIL-34-Date-Picker-Dialog-Specifying-a-Date-Range</guid>
            <pubDate>Thu, 07 Sep 2023 12:22:34 GMT</pubDate>
            <description><![CDATA[<h1 id="📅-date-picker-날짜-범위-지정">📅 Date Picker 날짜 범위 지정</h1>
<h2 id="1-maxdate">1. maxDate</h2>
<p>미래를 쓰지 않고 오늘을 포함한 과거 날짜만 불러오려면 <code>maxDate</code>를 사용하여 DatePicker에 최대 날짜를 설정했다!</p>
<p>생년월일 같이 미래 날짜가 필요 없을때 사용하면 좋다!</p>
<pre><code>val calendar = Calendar.getInstance()
var selectedYear = calendar.get(Calendar.YEAR)
var selectedMonth = calendar.get(Calendar.MONTH)
var selectedDay = calendar.get(Calendar.DAY_OF_MONTH)

// 최대 날짜 설정
val maxDateCalendar = Calendar.getInstance()
maxDateCalendar.set(selectedYear, selectedMonth, selectedDay, 0, 0, 0)
datePicker.maxDate = maxDateCalendar.timeInMillis</code></pre><br>


<h2 id="2-mindate">2. minDate</h2>
<p>만약 과거를 쓰지 않고 미래 날짜만 쓰고 싶다면 <code>minDate</code>를 사용하여 아래와 같이 최소 날짜를 설정하면 된다!</p>
<pre><code>val calendar = Calendar.getInstance()
var selectedYear = calendar.get(Calendar.YEAR)
var selectedMonth = calendar.get(Calendar.MONTH)
var selectedDay = calendar.get(Calendar.DAY_OF_MONTH)

// 최소 날짜 설정
val minDateCalendar = Calendar.getInstance()
minDateCalendar.set(selectedYear, selectedMonth, selectedDay, 0, 0, 0)
datePicker.minDate = minDateCalendar.timeInMillis</code></pre><br>

<h2 id="3-maxdate--mindate">3. maxDate + minDate</h2>
<p>만약 일정 기간의 날짜만 사용하고 싶다면 최대 날짜와 최소 날짜 둘 다 써주면 된다!</p>
<p>최소 날짜를 오늘 날짜로, 최대날짜는 1년 후로 지정한 코드이다!</p>
<pre><code>val calendar = Calendar.getInstance()
var selectedYear = calendar.get(Calendar.YEAR)
var selectedMonth = calendar.get(Calendar.MONTH)
var selectedDay = calendar.get(Calendar.DAY_OF_MONTH)

// 최소 날짜 설정
val minDateCalendar = Calendar.getInstance()
minDateCalendar.set(selectedYear, selectedMonth, selectedDay, 0, 0, 0)
datePicker.minDate = minDateCalendar.timeInMillis

// 최대 날짜 설정
val maxDateCalendar = Calendar.getInstance()
maxDateCalendar.set(selectedYear+1, selectedMonth, selectedDay, 0, 0, 0)
datePicker.maxDate = maxDateCalendar.timeInMillis</code></pre><br>

<h2 id="4-결과물">4. 결과물</h2>
<p>아래는 최소 날짜 설정, 최대 날짜 설정, 둘 다 설정한 코드를 순서대로 실행한 것이다.</p>
<p><img src="https://velog.velcdn.com/images/ars_yeon/post/1a97e740-b3b3-4a69-95f4-0e126198406e/image.gif" alt="example"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] #33 Date Picker Dialog]]></title>
            <link>https://velog.io/@ars_yeon/TIL-33-Date-Picker-Dialog</link>
            <guid>https://velog.io/@ars_yeon/TIL-33-Date-Picker-Dialog</guid>
            <pubDate>Wed, 06 Sep 2023 13:03:08 GMT</pubDate>
            <description><![CDATA[<h2 id="1-xml-code">1. Xml Code</h2>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:backgroundTint=&quot;@color/ice_blue&quot;
    android:padding=&quot;16dp&quot;&gt;

    &lt;DatePicker
        android:id=&quot;@+id/dialog_datepicker&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:calendarViewShown=&quot;false&quot;
        android:datePickerMode=&quot;spinner&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

    &lt;Button
        android:id=&quot;@+id/dialog_datepicker_cancel&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:backgroundTint=&quot;@color/pale_gray&quot;
        android:text=&quot;cancel&quot;
        android:textColor=&quot;@color/charcoal_black&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/dialog_datepicker&quot; /&gt;

    &lt;Button
        android:id=&quot;@+id/dialog_datepicker_save&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:backgroundTint=&quot;@color/pale_gray&quot;
        android:text=&quot;Save&quot;
        android:textColor=&quot;@color/charcoal_black&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/dialog_datepicker&quot; /&gt;
&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;</code></pre><br>

<h2 id="2-kotlin-code">2. Kotlin Code</h2>
<pre><code>class DatePickDialog : BottomSheetDialogFragment() {
    private var _binding: DialogDatePickBinding? = null
    private val binding get() = _binding!!

    private var dateSelectedListener: DateSelectedListener? = null

    companion object {
        const val TAG = &quot;DatePickDialog&quot;
        private const val ARG_TEXT = &quot;text&quot;

        fun newInstance(text: String): DatePickDialog {
            val fragment = DatePickDialog()
            val args = Bundle()
            args.putString(ARG_TEXT, text)
            fragment.arguments = args
            return fragment
        }
    }

    interface DateSelectedListener {
        fun onDateSelected(formattedDate: String)
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = DialogDatePickBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initViews()
    }

    private fun initViews() = with(binding) {
        val datePicker = dialogDatepicker
        val saveButton = dialogDatepickerSave
        val cancelButton = dialogDatepickerCancel

        val dateFormat = &quot;%04d-%02d-%02d&quot;

        val calendar = Calendar.getInstance()
        var selectedYear = calendar.get(Calendar.YEAR)
        var selectedMonth = calendar.get(Calendar.MONTH)
        var selectedDay = calendar.get(Calendar.DAY_OF_MONTH)

        // DatePicker의 최대 날짜 설정
        val maxDateCalendar = Calendar.getInstance()
        maxDateCalendar.set(selectedYear, selectedMonth, selectedDay, 0, 0, 0)
        datePicker.maxDate = maxDateCalendar.timeInMillis

        // DatePicker의 날짜 변경 이벤트 처리
        datePicker.init(
            selectedYear,
            selectedMonth,
            selectedDay
        ) { _, year, monthOfYear, dayOfMonth -&gt;
            selectedYear = year
            selectedMonth = monthOfYear + 1
            selectedDay = dayOfMonth
            val formattedDate = String.format(
                dateFormat,
                year,
                monthOfYear + 1,
                dayOfMonth
            )
            dateSelectedListener?.onDateSelected(formattedDate)
        }

        saveButton.setOnClickListener {
            val formattedDate = String.format(
                dateFormat,
                selectedYear,
                selectedMonth,
                selectedDay
            )
            dateSelectedListener?.onDateSelected(formattedDate)
            dismiss()
        }

        cancelButton.setOnClickListener {
            dismiss()
        }
    }

    fun setDateSelectedListener(listener: DateSelectedListener) {
        this.dateSelectedListener = listener
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] #32 Bottom Sheet Dialog]]></title>
            <link>https://velog.io/@ars_yeon/TIL-32-Bottom-Sheet-Dialog</link>
            <guid>https://velog.io/@ars_yeon/TIL-32-Bottom-Sheet-Dialog</guid>
            <pubDate>Tue, 05 Sep 2023 12:24:50 GMT</pubDate>
            <description><![CDATA[<h2 id="1-xml-code">1. Xml Code</h2>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:backgroundTint=&quot;@color/ice_blue&quot;
    android:padding=&quot;16dp&quot;&gt;

    &lt;EditText
        android:id=&quot;@+id/dialogbottom_edittext&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:hint=&quot;Enter Text&quot;
        android:inputType=&quot;text&quot;
        android:maxLines=&quot;1&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

    &lt;Button
        android:id=&quot;@+id/dialogbottom_btn_cancel&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginTop=&quot;10dp&quot;
        android:backgroundTint=&quot;@color/pale_gray&quot;
        android:text=&quot;Cancel&quot;
        android:textColor=&quot;@color/charcoal_black&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/dialogbottom_edittext&quot; /&gt;

    &lt;Button
        android:id=&quot;@+id/dialogbottom_btn_save&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginTop=&quot;10dp&quot;
        android:backgroundTint=&quot;@color/pale_gray&quot;
        android:text=&quot;Save&quot;
        android:textColor=&quot;@color/charcoal_black&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/dialogbottom_edittext&quot; /&gt;
&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;</code></pre><br>

<h2 id="2-kotlin-code">2. Kotlin Code</h2>
<pre><code>class BottomSheetDialog : BottomSheetDialogFragment() {
    private lateinit var editText: EditText
    private var selectedTextView: TextView? = null

    companion object {
        const val TAG = &quot;BottomSheetDialog&quot;

        fun newInstance(text: String, selectedTextView: TextView): BottomSheetDialog {
            val fragment = BottomSheetDialog()
            val args = Bundle()
            args.putString(ARG_TEXT, text)
            fragment.arguments = args
            fragment.selectedTextView = selectedTextView
            return fragment
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val binding = DialogBottomSheetBinding.inflate(inflater, container, false)
        val view = binding.root

        editText = binding.dialogbottomEdittext
        val saveButton = binding.dialogbottomBtnSave
        val cancelButton = binding.dialogbottomBtnCancel

        // 전달된 텍스트 설정
        editText.setText(arguments?.getString(ARG_TEXT))
        editText.setSelection(editText.text.length)

        saveButton.setOnClickListener {
            // 수정된 텍스트를 호출한 TextView에 설정
            val updatedText = editText.text.toString()
            selectedTextView?.text = updatedText
            dismiss()
        }

        cancelButton.setOnClickListener {
            dismiss()
        }

        return view
    }
}</code></pre>]]></description>
        </item>
    </channel>
</rss>