<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>featch_life_dev.log</title>
        <link>https://velog.io/</link>
        <description>개발자</description>
        <lastBuildDate>Sat, 04 Oct 2025 04:28:46 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>featch_life_dev.log</title>
            <url>https://velog.velcdn.com/images/featch_life_dev/profile/5f795604-6048-403c-af0d-64bf75b780a2/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. featch_life_dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/featch_life_dev" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[미친다 AI 믿으면 안됨]]></title>
            <link>https://velog.io/@featch_life_dev/%EB%AF%B8%EC%B9%9C%EB%8B%A4-AI-%EB%AF%BF%EC%9C%BC%EB%A9%B4-%EC%95%88%EB%90%A8</link>
            <guid>https://velog.io/@featch_life_dev/%EB%AF%B8%EC%B9%9C%EB%8B%A4-AI-%EB%AF%BF%EC%9C%BC%EB%A9%B4-%EC%95%88%EB%90%A8</guid>
            <pubDate>Sat, 04 Oct 2025 04:28:46 GMT</pubDate>
            <description><![CDATA[<h2 id="간단한-더하기-문제에-대한-ai에-대한-답입니다">간단한 더하기 문제에 대한 AI에 대한 답입니다.</h2>
<blockquote>
<p><code>epochday</code> 라는게 있습니다. 프로그램 하시는 분들은 아시겠지만 날짜를 1970.1.1. 부터 몇일째 되는 날인지 확인하는 개념입니다.</p>
</blockquote>
<p>그런데 계산기로 계산하기는 귀찮고 해서 ai에게 물어 봤습니다.</p>
<ul>
<li>MS의 <code>챗 GPT</code>입니다.
<img src="https://velog.velcdn.com/images/featch_life_dev/post/3db31007-210d-4121-930e-f958d6fdf7be/image.png" alt=""></li>
</ul>
<blockquote>
<p>2024.12.15. 로 보고 하고 있습니다.</p>
</blockquote>
<ul>
<li><p>구글 <code>제미나이</code> 입니다.
<img src="https://velog.velcdn.com/images/featch_life_dev/post/42f93ed4-5a99-4e5f-8eb5-bf21b1f2a6ce/image.png" alt=""></p>
</li>
<li><p><code>퍼플랙씨티</code>입니다.
<img src="https://velog.velcdn.com/images/featch_life_dev/post/5e9116d6-c1dc-42b5-a8b5-d8ae0dfb8363/image.png" alt=""></p>
</li>
<li><p>일론 머스크의 <code>그록</code>입니다.
<img src="https://velog.velcdn.com/images/featch_life_dev/post/00ca582b-8992-49be-8901-5f426109888e/image.png" alt=""></p>
</li>
<li><p>차이나의 <code>딥씩</code>입니다.
<img src="https://velog.velcdn.com/images/featch_life_dev/post/01fbbf01-4a33-4576-85c2-5820e7e1f645/image.png" alt=""></p>
</li>
<li><p>모든 AI의 답이 다르고 다수결도 되지 않습니다. 실제 정답은 <code>2025.2.13.</code>로 는 <code>모든 AI는  틀렸</code>습니다.</p>
</li>
<li><p>왜 이따구로 처리 될까요 ?</p>
</li>
<li><p>여러번 격은건데 AI의 더하기는 형편 없습니다. 실제로는 닝겐들이 올려논 정보를 이리 저리 조합하는데 <code>1과 1은 같다</code>가 거짓으로 인식 할 수 있는게 실수(Floatting Pointer)계산 컴퓨터입니다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[AVD에서 시스템 네비게인션바 보이게]]></title>
            <link>https://velog.io/@featch_life_dev/AVD%EC%97%90%EC%84%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%84%A4%EB%B9%84%EA%B2%8C%EC%9D%B8%EC%85%98%EB%B0%94-%EB%B3%B4%EC%9D%B4%EA%B2%8C</link>
            <guid>https://velog.io/@featch_life_dev/AVD%EC%97%90%EC%84%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%84%A4%EB%B9%84%EA%B2%8C%EC%9D%B8%EC%85%98%EB%B0%94-%EB%B3%B4%EC%9D%B4%EA%B2%8C</guid>
            <pubDate>Wed, 19 Feb 2025 19:44:38 GMT</pubDate>
            <description><![CDATA[<p>안드로이드에서 
 설정&gt;접근성&gt;시스템제어&gt;탐색모드&gt;3버튼탐색</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MAC용 추천앱]]></title>
            <link>https://velog.io/@featch_life_dev/MAC%EC%9A%A9-%EC%B6%94%EC%B2%9C%EC%95%B1</link>
            <guid>https://velog.io/@featch_life_dev/MAC%EC%9A%A9-%EC%B6%94%EC%B2%9C%EC%95%B1</guid>
            <pubDate>Tue, 18 Feb 2025 12:10:36 GMT</pubDate>
            <description><![CDATA[<h3 id="파일전송">파일전송</h3>
<ul>
<li>filezilla
<a href="https://filezilla-project.org/download.php?show_all=1">파일질라 클라이언트</a><blockquote>
<p>클라이언트는 무료</p>
</blockquote>
</li>
</ul>
<h3 id="미디어플레이어">미디어플레이어</h3>
<ul>
<li>IINA
<a href="https://iina.io/">인이니아</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[username does not match previous request (-1) 해결]]></title>
            <link>https://velog.io/@featch_life_dev/username-does-not-match-previous-request-1-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@featch_life_dev/username-does-not-match-previous-request-1-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Fri, 14 Feb 2025 14:04:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>xcode에서 git remote에서 clone을 하려는데
<code>username does not match previous request (-1)</code> 라는 에러가 계속되었다.</p>
</blockquote>
<hr>
<blockquote>
<p>이유는 알겠는데 정확한 해결책을 찾지 못했다.
그러나 임시 방편으로 가능한 방법을 기록해 두겠다.</p>
</blockquote>
<ol>
<li>추측되는 이유는 xcode에서 ~/.ssh/id_ed25519 파일로 인증을 하지 못하고 있어서 그런것 같다.</li>
<li>강제로 개인키을 지정해 주어야겠는데 xcode에서는 어떻게 해야 할지 ...</li>
<li>결국 다음 방법을 사용했다.</li>
</ol>
<hr>
<ol>
<li>터미널에서 ~/.ssh 폴더를 임시로 ~/ssh 로 이름을 변경해두었다. </li>
</ol>
<p>-&gt; xcode에서는 .ssh폴더를 찾지 못하게 하기 위해서임.</p>
<ol>
<li>xcode에서 url을 기반으로 clone을 한다. <code>ssh://user@server.url:22/path</code>을 주소창에 지정한다.</li>
<li>가져오기<code>clone</code>를 시도한다. (위 1번을 해두었기 때문에 .ssh폴더를 찾지 못해 결국 id/password 방식으로 하려한다)</li>
<li>터미널에서 ~/ssh를 다시 ~/.ssh로 변경한다.</li>
<li>xcode에서 id/password방식에서 개인키 방식으로 변경하고 아래의 선택버튼으로 ~/.ssh/id_ed25519를 선택해 개인키를 강제로 지정한다.</li>
</ol>
<hr>
<p>이 방법으로 해결은 되었는데 더 좋은 방법이 있으면 지식을 나누어 주었으면 감사하겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[변화의 감시]]></title>
            <link>https://velog.io/@featch_life_dev/%EB%B3%80%ED%99%94%EC%9D%98-%EA%B0%90%EC%8B%9C</link>
            <guid>https://velog.io/@featch_life_dev/%EB%B3%80%ED%99%94%EC%9D%98-%EA%B0%90%EC%8B%9C</guid>
            <pubDate>Thu, 26 Dec 2024 01:42:24 GMT</pubDate>
            <description><![CDATA[<h1 id="변수의-감시">변수의 감시</h1>
<blockquote>
<p>kotlin compose 상태에서 변수의 감시는 중요하다.</p>
</blockquote>
<h5 id="1-함수를-이용하는-법">1. 함수를 이용하는 법</h5>
<p>   가장 간단하며 stream 이 아니기 때문에 suspend 상태에서 호출할 필요가 없다.</p>
<h5 id="2-cold-stream-를-이용하는법---구독이-있는경우만-값을-방출">2. Cold Stream 를 이용하는법 -&gt; 구독이 있는경우만 값을 방출</h5>
<p>   <strong>Cold Stream</strong> : 특정 시점에서만 데이터를 처리하거나, 값의 비교가 필요할 때 호출하는 방식에 적합합니다.</p>
<blockquote>
<p>flow</p>
</blockquote>
<h5 id="3-hot-stream--구독이-없는-경우에도-방출">3. Hot Stream : 구독이 없는 경우에도 방출</h5>
<p>   <strong>Hot Stream</strong>을 사용하여 값을 감시하는 방법은 <code>StateFlow</code> 또는 <code>SharedFlow</code>를 활용하는 것입니다.
   이전에 설명했던 내용과 관련해, <code>Hot Stream</code>은 데이터 소비자가 없어도 값을 방출하며,
   이를 통해 실시간 데이터 감시가 가능합니다.</p>
<blockquote>
<p>StateFlow , SharedFlow</p>
</blockquote>
<pre><code class="language-kotlin">   // Viewmodel
private val _orgUser = MutableStateFlow(PersonEntity())

private val _user = MutableStateFlow(PersonEntity())
val user: StateFlow&lt;PersonEntity&gt; get() = _user

fun initInfoVM(personEntity: PersonEntity) {
    _user.value = personEntity
    _orgUser.value = personEntity
  }


fun isModified(): Boolean {
    return _user.value != _orgUser.value
  }

val isModifiedFlow = combine(_orgUser, _user) { orgUser, user -&gt;
    orgUser != user // 두 값이 다르면 true, 같으면 false
  }.distinctUntilChanged() // 중복 방지 (값이 변경될 때만 방출)

  // StateFlow로 변경 상태를 감지
val isModified: StateFlow&lt;Boolean&gt; = combine(_orgUser, _user) { orgUser, user -&gt;
    orgUser != user
  }.stateIn(
    scope = CoroutineScope(Dispatchers.Default), // CoroutineScope 필요
    started = SharingStarted.WhileSubscribed(5000L), // 구독 중일 때만 활성화
    initialValue = false // 초기값 설정
  )</code></pre>
<h3 id="compose-내에서-호출법">compose 내에서 호출법</h3>
<pre><code class="language-kotlin">//1번의 경우 if (vm.isModified()) 함수로 호출
//2번의 경우 
val isModifyed = vm.isModifiedFlow.collectAsState(false) // 로 감시</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[.gitignore 적용하기]]></title>
            <link>https://velog.io/@featch_life_dev/.gitignore-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@featch_life_dev/.gitignore-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 25 Dec 2024 03:30:19 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>.gitignore 이 적용되지 않는 경우가 있다. 
그전 캐쉬를 지우고 다시 커밋 하는 방법이다.</p>
</blockquote>
<pre><code class="language-bash">git rm -r --cached .
git add .
git commit -m &quot;fix .gitignore a untracked files apply.&quot;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[animateLongAsState 구현]]></title>
            <link>https://velog.io/@featch_life_dev/animateLongAsState-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@featch_life_dev/animateLongAsState-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Tue, 24 Dec 2024 10:13:08 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-kotlin">  val animateLong = animateValueAsState(
    targetValue = promotiondate,
    finishedListener = {
    },
    typeConverter = TwoWayConverter(
      // Long -&gt; AnimationVector1D 변환
      convertToVector = { AnimationVector1D(it.toFloat()) },
      // AnimationVector1D -&gt; Long 변환
      convertFromVector = { it.value.toLong() }
    ),
    animationSpec = tween(durationMillis = 1000)
  )</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[맥북 Home/End키 문장 이동으로 바꾸기]]></title>
            <link>https://velog.io/@featch_life_dev/%EB%A7%A5%EB%B6%81-HomeEnd%ED%82%A4-%EB%AC%B8%EC%9E%A5-%EC%9D%B4%EB%8F%99%EC%9C%BC%EB%A1%9C-%EB%B0%94%EA%BE%B8%EA%B8%B0</link>
            <guid>https://velog.io/@featch_life_dev/%EB%A7%A5%EB%B6%81-HomeEnd%ED%82%A4-%EB%AC%B8%EC%9E%A5-%EC%9D%B4%EB%8F%99%EC%9C%BC%EB%A1%9C-%EB%B0%94%EA%BE%B8%EA%B8%B0</guid>
            <pubDate>Fri, 29 Nov 2024 15:54:00 GMT</pubDate>
            <description><![CDATA[<h3 id="키-매핑-변경하기">키 매핑 변경하기</h3>
<blockquote>
<p>원래 맥에서는 문장의 시작으로 이동: <code>⌘ + ←</code> 이고 문장의 끝으로 이동: <code>⌘ + →</code> 이다.
Home과 End 키를 문장 이동 기능으로 사용할 수 있도록 매핑을 변경하는 방법입니다.</p>
</blockquote>
<ol>
<li>터미널 열기: 응용 프로그램 &gt; 유틸리티 &gt; 터미널.</li>
<li>KeyBindings 폴더 만들기:<pre><code class="language-bash">cd ~/Library
mkdir KeyBindings
cd KeyBindings</code></pre>
</li>
<li>DefaultKeyBinding.dict 파일 생성:<pre><code class="language-bash">vi DefaultKeyBinding.dict</code></pre>
</li>
<li>아래 내용 입력:<pre><code class="language-bash">/* Remap Home / End keys to be correct */
&quot;\UF729&quot; = &quot;moveToBeginningOfLine:&quot;; /* Home */
&quot;\UF72B&quot; = &quot;moveToEndOfLine:&quot;; /* End */
&quot;$\UF729&quot; = &quot;moveToBeginningOfLineAndModifySelection:&quot;; /* Shift + Home */
&quot;$\UF72B&quot; = &quot;moveToEndOfLineAndModifySelection:&quot;; /* Shift + End */</code></pre>
</li>
<li>파일 저장 후 종료: <code>esc</code>, <code>:</code>, <code>wq</code>, <code>enter</code> 입력.</li>
<li>재부팅 이나 재 로그인: 설정이 적용되도록 시스템을 재부팅합니다.
이렇게 하면 Home과 End 키가 문장 내에서의 이동으로 작동하게 됩니다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[compose 에서 Lottie 사용]]></title>
            <link>https://velog.io/@featch_life_dev/compose-%EC%97%90%EC%84%9C-Lottie-%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@featch_life_dev/compose-%EC%97%90%EC%84%9C-Lottie-%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Fri, 22 Nov 2024 10:23:41 GMT</pubDate>
            <description><![CDATA[<h2 id="설정">설정,</h2>
<h3 id="settingsgradlekts-설정">settings.gradle.kts 설정</h3>
<pre><code class="language-kotlin">dependencyResolutionManagement {
  repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
  repositories {
    google()
    mavenCentral()
    maven { url=uri(&quot;https://jitpack.io&quot;) }
    maven { url=uri (&quot;https://oss.sonatype.org/content/repositories/snapshots/&quot;) }
  }
}
</code></pre>
<blockquote>
<p><code>maven { url=uri (&quot;https://oss.sonatype.org/content/repositories/snapshots/&quot;) }</code>를 추가</p>
</blockquote>
<h3 id="libsversionstoml-에서">libs.versions.toml 에서</h3>
<pre><code>[versions]
lottieCompose = &quot;6.6.0&quot;
[libraries]
lottie-compose = { module = &quot;com.airbnb.android:lottie-compose&quot;, version.ref = &quot;lottieCompose&quot; }</code></pre><h3 id="buildgradlekts모듈단위">build.gradle.kts(모듈단위)</h3>
<pre><code>dependencies {
...
  // lottie
  implementation (libs.lottie.compose)
  }</code></pre><h2 id="사용">사용</h2>
<pre><code class="language-kotlin">// compose
// 선언
val composition by rememberLottieComposition(
    LottieCompositionSpec.Url(&quot;https://lottie.host/4add9811-6ad1-4021-9a41-b72f1f9e014a/eqSh7vm4jN.lottie&quot;)
  )
  val lottieAnimatable = rememberLottieAnimatable()
  LaunchedEffect(composition) {
    lottieAnimatable.animate(
      composition = composition,
      iterations = LottieConstants.IterateForever
    )
  }

// 적용
LottieAnimation(
      composition = composition,
      progress = lottieAnimatable.progress,
      modifier = Modifier.fillMaxSize()
    )

// stop
GoogleAuthButton(signInVm) { auth: FirebaseAuth -&gt;
        CoroutineScope(Dispatchers.Main).launch {
          lottieAnimatable.snapTo(progress = 0f)
        }
        doSigned(auth)
      }// GoogleAuthButton</code></pre>
<blockquote>
<p>중요 나갈때 stop 하지 않으면 scope 내에서 악영향을 줌.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Manifest merger failed ]]></title>
            <link>https://velog.io/@featch_life_dev/Manifest-merger-failed</link>
            <guid>https://velog.io/@featch_life_dev/Manifest-merger-failed</guid>
            <pubDate>Sun, 10 Nov 2024 12:25:58 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-shell">Manifest merger failed : Attribute property#android.adservices.AD_SERVICES_CONFIG@resource value=(@xml/gma_ad_services_config) from [com.google.android.gms:play-services-ads-lite:23.5.0] AndroidManifest.xml:109:13-59
    is also present at [com.google.android.gms:play-services-measurement-api:22.1.2] AndroidManifest.xml:32:13-58 value=(@xml/ga_ad_services_config).
    Suggestion: add &#39;tools:replace=&quot;android:resource&quot;&#39; to &lt;property&gt; element at AndroidManifest.xml to override.</code></pre>
<blockquote>
<p>이런 에러가 나면 메니페스토에 아래와 같이 수정하면 됨.</p>
</blockquote>
<pre><code>&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
          xmlns:tools=&quot;http://schemas.android.com/tools&quot;&gt;
    &lt;application&gt;
        &lt;property
            android:name=&quot;android.adservices.AD_SERVICES_CONFIG&quot;
            android:resource=&quot;@xml/gma_ad_services_config&quot;
            tools:replace=&quot;android:resource&quot; /&gt;
    &lt;/application&gt;
&lt;/manifest&gt;</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[android Room 설정]]></title>
            <link>https://velog.io/@featch_life_dev/android-Room-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@featch_life_dev/android-Room-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Sat, 26 Oct 2024 16:07:09 GMT</pubDate>
            <description><![CDATA[<h2 id="안드로이드에서-sqlite에서-room을-덥기">안드로이드에서 sqlite에서 Room을 덥기</h2>
<blockquote>
<p>아마 아직도 sqlite 만을 사용하는 개발자가 있을지 모르겠다.
나는 sqlite 에서 room 으로 옮겨온지가 꽤 지나서 sqlite는 이제 가물거린다.
언젠가는 room 설정도 가물거릴까 걱정하는 마음에 현제 사용하는 설정을 기록해 두려한다.</p>
</blockquote>
<h2 id="libsversionstoml">libs.versions.toml</h2>
<pre><code class="language-xml">[versions]
room = &quot;2.6.1&quot;
[libraries]
androidx-room-ktx = { group = &quot;androidx.room&quot;, name = &quot;room-ktx&quot;, version.ref = &quot;room&quot; }
androidx-room-runtime = { module = &quot;androidx.room:room-runtime&quot;, version.ref = &quot;room&quot; }
androidx-room-compiler = { module = &quot;androidx.room:room-compiler&quot;, version.ref = &quot;room&quot; }</code></pre>
<h2 id="buildgradlektsapp">build.gradle.kts(:app)</h2>
<pre><code class="language-kotlin">plugins {
//...
    alias(libs.plugins.ksp)
  // Existing plugins
  kotlin(&quot;plugin.serialization&quot;) version &quot;2.0.21&quot;
}
android{
ksp {
    arg(RoomSchemaArgProvider(File(projectDir, &quot;schemas&quot;)))
  }
}
dependencies {
  //room database
  // https://developer.android.com/jetpack/androidx/releases/room?hl=ko
  implementation(libs.androidx.room.runtime)
  ksp(libs.androidx.room.compiler)
  implementation(libs.androidx.room.ktx)
}

class RoomSchemaArgProvider(
  @get:InputDirectory
  @get:PathSensitive(PathSensitivity.RELATIVE)
  val schemaDir: File
) : CommandLineArgumentProvider {
  @Suppress(&quot;RETURN_TYPE_MISMATCH_ON_OVERRIDE&quot;) // Kotlin 의 Iterable 와 override 한 java 의 Iterable를 다른것으로 인식하는 버그 수정
  override fun asArguments(): Iterable&lt;String&gt; {
    return listOf(&quot;room.schemaLocation=${schemaDir.path}&quot;) // ksp
  }
}
</code></pre>
<h2 id="database-객체설정">Database 객체설정</h2>
<pre><code class="language-kotlin">@TypeConverters(Converters::class)
@Database(
  version = 1,
  entities = [
    LunaInfoEntity::class, SpcdEntity::class, ScheduleEntity::class, ShiftEntity::class,
    ShiftPatternEntity::class,ShiftAddonEntity::class,
    UserEntity::class,
             ],
  exportSchema = false // Todo !!! 배포시 true 로 변경
)

abstract class EodeunCalendarMemo : RoomDatabase() {
  companion object {
    @Volatile  // 중요 정보로 Ram 을 이용함
    private var INSTANCE: EodeunCalendarMemo? = null

    /**
     * Get instance
     * if the INSTANCE is not null, then return it,
     * if it is, then create the database and save in instance variable then return it.
     *
     * @param context
     * @return
     */
    fun getInstance(context: Context): EodeunCalendarMemo {
      return INSTANCE ?: synchronized(this) {
        val instance = Room.databaseBuilder(
          context.applicationContext, EodeunCalendarMemo::class.java, &quot;EodeunCalendarMemo.db&quot;
        ).fallbackToDestructiveMigration() // todo !!! 배포시 삭제. 스키마 관리 포기 하고 강제로 진행
          .build()
        INSTANCE = instance
        instance // return instance
      } //
    } // fun
  } // companion object

  abstract fun lunaInfoDao(): LunaInfoDao
  abstract fun spcdDao(): SpcdDao
  abstract fun scheduleDao(): ScheduleDao
  abstract fun shiftDao(): ShiftDao
  abstract fun shiftPatternDao(): ShiftPatternDao
  abstract fun userDao(): UserDao
  abstract fun shiftAddOnDao(): ShiftAddonDao
}</code></pre>
<h2 id="convertor">convertor</h2>
<blockquote>
<p>Room은 Int,String,Long 같은 프라임타입만 지원하기에 저장시 변환해서 저장해야 한다.</p>
</blockquote>
<h3 id="room에서-지원하는-type">Room에서 지원하는 Type</h3>
<ul>
<li>NULL</li>
<li>INTEGER (Int, Long)</li>
<li>REAL (Float, Double)</li>
<li>TEXT (String)</li>
<li>BLOB (ByteArray)</li>
</ul>
<pre><code class="language-kotlin">class Converters {

  @TypeConverter
  fun intToTermTime(value: Int): TermTime {
    return TermTime(value)
  }

  @TypeConverter
  fun termTimeToInt(value: TermTime): Int {
    return value.toInt()
  }

  @TypeConverter
  fun uLongToLong(value:ULong):Long = value.toLong()

  @TypeConverter
  fun longToULong(value:Long):ULong = value.toULong()

  @TypeConverter
  fun listStringToString(value: List&lt;String&gt;): String = Gson().toJson(value)

  @TypeConverter
  fun stringToShiftEntity(value: String?): ShiftEntity? {
    return value?.let { Gson().fromJson(it, ShiftEntity::class.java) }
  }

  @TypeConverter
  fun shiftEntityToString(value: ShiftEntity?): String? {
    return value?.let { Gson().toJson(it) }
  }

  @TypeConverter
  fun stringToShiftEvent(value: String?): ShiftEvent? {
    return value?.let { Gson().fromJson(it, ShiftEvent::class.java) }
  }

  @TypeConverter
  fun shiftEntityToString(value: ShiftEvent?): String? {
    return value?.let { Gson().toJson(it) }
  } // shiftEntityToString

  @TypeConverter
  fun shiftEventListToString(value: List&lt;ShiftEvent&gt;): String {
    return Gson().toJson(value)
  } // List&lt;ShiftEvent&gt; -&gt; String

  @TypeConverter
  fun stringToShiftEventList(value: String): List&lt;ShiftEvent&gt; {
    return Gson().fromJson(
      value, object : TypeToken&lt;List&lt;ShiftEvent&gt;&gt;() {}.type
    ) // return Gson().fromJson(value, Array&lt;ShiftEvent&gt;::class.java).toList() // Array -&gt; List 로 기능은 같은데 메모리를 약간 더 씀
  } // String -&gt; List&lt;ShiftEvent&gt;

  @TypeConverter
  fun shiftPatternToString(value: ShiftPattern?): String? {
    return value?.let { Gson().toJson(it) }
  } // ShiftPattern -&gt; String

  @TypeConverter
  fun stringToShiftPattern(value: String?): ShiftPattern? {
    return value?.let { Gson().fromJson(it, ShiftPattern::class.java) }
  }

  @TypeConverter
  fun workHistoryItemListToString(value: List&lt;WorkHistoryItem&gt;): String? {
    return value.let { Gson().toJson(it) }
  }

  @TypeConverter
  fun stringToWorkHistoryItemList(value: String?): List&lt;WorkHistoryItem&gt;? {
    return Gson().fromJson(
      value, object : TypeToken&lt;List&lt;WorkHistoryItem&gt;&gt;() {}.type
    )
  }
}</code></pre>
<h2 id="entity">@Entity</h2>
<blockquote>
<p>Table 을 @Entity(tableName=&quot;String&quot;)으로 저장한다.</p>
</blockquote>
<pre><code class="language-kotlin">@Entity(tableName = &quot;luna_info_table&quot;)
data class LunaInfoEntity(
  @PrimaryKey(autoGenerate = true)
  val rowId: Long = 0L, // 값이 0 이면 자동 증가
  // 양력 epoch 값
  val epoch: Long = 0L,
  // 음력정보
  val lunLeapmonth: Boolean = false, // 음력 평달 여부
  val lunYear: Int = 0,
  val lunMonth: Int = 0,
  val lunDay: Int = 0,
  val solLeapyear: Boolean = false,
  //  val solYear: Int = 0,
  //  val solMonth: Int = 0,
  //  val solDay: Int = 0,
  val lunIljin: String = &quot;&quot;,
  val lunNday: Int = 0,
  val lunSecha: String = &quot;&quot;,
  val lunWolgeon: String = &quot;&quot;,
  val solJd: Long = 0L,
  //  val solWeek: String = &quot;&quot;
) {
  fun toLog(): String {
    val solDate = LocalDate.ofEpochDay(epoch)
    return &quot;solYMD:${solDate.year}.${solDate.monthValue}.${solDate.dayOfMonth}.(${solDate.dayOfWeek}요일[${if (solLeapyear) &quot;윤&quot; else &quot;평&quot;}년])는 &quot; +
            &quot;lunYMD:${lunYear}.${lunMonth}.${lunDay}.(${if (lunLeapmonth) &quot;윤&quot; else &quot;평&quot;}달) (lunIljin:${lunIljin}/lunSecha:${lunSecha}/lunWolgeon:${lunWolgeon})&quot;
  }

  fun toLunaShort():String{
    return &quot;${lunMonth}.${lunDay}.&quot;
  }

  fun toLabel(): String {
    return &quot; ${lunYear}년 (${if (lunLeapmonth) &quot;윤&quot; else &quot;평&quot;}) ${lunMonth}월 ${lunDay}일&quot;
  }

  fun toGanJi(): String {
    return &quot;${lunSecha}년 ${lunWolgeon}월 ${lunIljin}일&quot;
  }
}</code></pre>
<h2 id="dao">@Dao</h2>
<blockquote>
<p>DataAccessObject 는 Interface로 선언한다.</p>
</blockquote>
<pre><code class="language-kotlin">@Dao
interface LunaInfoDao {

  @Insert(onConflict = OnConflictStrategy.REPLACE)
  suspend fun insert(lunaInfoEntity: LunaInfoEntity): Long

  @Query(&quot;SELECT EXISTS (SELECT * FROM  luna_info_table WHERE epoch  = :epoch) &quot;)
  suspend fun isExists(epoch:DateLong): Boolean

  @Query(&quot;SELECT * FROM  luna_info_table WHERE epoch = :epoch &quot;)
  fun readFlow(epoch:DateLong): Flow&lt;LunaInfoEntity?&gt;

  @Query(&quot;SELECT * FROM  luna_info_table WHERE epoch = :dateLong &quot;)
  suspend fun read(dateLong: DateLong): LunaInfoEntity?

  @Query(&quot;SELECT * FROM  luna_info_table WHERE epoch = :epoch &quot;)
  fun fetch(epoch:DateLong): Flow&lt;LunaInfoEntity?&gt;

  @Query(&quot;SELECT * FROM luna_info_table &quot;)
  fun fetchAll(): Flow&lt;List&lt;LunaInfoEntity&gt;&gt;
}</code></pre>
<h2 id="repository">Repository</h2>
<blockquote>
<p>DB 는 Repository 를 경유해서 접근한다.
repository 를 interface로 하여 구현체를 별도로 관리한다.</p>
</blockquote>
<pre><code class="language-kotlin">interface LunaRepo {
  suspend fun isExist(dateLong: DateLong): Boolean
  fun fetchFlow(dateLong: DateLong): Flow&lt;LunaInfoEntity?&gt;
  suspend fun isExistMonth(year: Int, month: Int)
}</code></pre>
<blockquote>
<p>Repo 구현체
실제처리를 구현하고 간단한 처리를 담당해서 상단의 Viewmodel에서 처리하도록 돕니다.</p>
</blockquote>
<pre><code class="language-kotlin">class LunaRepoImpl @Inject constructor(
  private val lunaDb: LunaDBRepo
) : LunaRepo {
  override suspend fun isExist(dateLong: DateLong): Boolean {
    return lunaDb.isExist(dateLong)
  }

  override fun fetchFlow(dateLong: DateLong): Flow&lt;LunaInfoEntity?&gt; {
    return lunaDb.fetchFlow(dateLong)
  }

  private val _requestedLunaYearMonths = mutableListOf&lt;Int&gt;()

  override suspend fun isExistMonth(year: Int, month: Int) {
    val seed = year * 12 + month
    if (_requestedLunaYearMonths.contains(seed)) return //
    _requestedLunaYearMonths.add(seed)
    if (lunaDb.isExist(
        LocalDate.of(year, month, 1).toEpochDay()
      )
    ) return // Api로 부터 holiday 를 가져 오도록 함
    try {
      DataGoKr.getLunCalInfo(year, month).response?.body?.items?.item?.forEach { it: LunaItem? -&gt;
        it?.let { lunaDb.insert(it.toEntity()) }
      } //forEach
    } catch (e: Exception) {
      Log.e(&quot;Jim&quot;, &quot;${e.message}&quot;)
    } //try-catch
  } //
}</code></pre>
<h2 id="injection">Injection</h2>
<blockquote>
<p>코드주입을 위해 <code>Hilt</code>를 사용하게 되는데 그 주입설정이다.</p>
</blockquote>
<pre><code class="language-kotlin">@InstallIn(SingletonComponent::class)
@Module
object Injections {

  //   DB 와 관련된 인젝션들
  @Singleton
  @Provides
  fun providesAppDB(@ApplicationContext context: Context): EodeunCalendarMemo {
    return EodeunCalendarMemo.getInstance(context = context)
  }

  /* Dao 와 관련된 인젝션들 */
  @Singleton
  @Provides
  fun provideLunaInfoDao(db: EodeunCalendarMemo): LunaInfoDao {
    return db.lunaInfoDao()
  }

  @Singleton
  @Provides
  fun provideSpcdDao(db: EodeunCalendarMemo): SpcdDao {
    return db.spcdDao()
  }

  @Singleton
  @Provides
  fun provideScheduleDao(db: EodeunCalendarMemo): ScheduleDao {
    return db.scheduleDao()
  }

  @Singleton
  @Provides
  fun provideShiftDao(db: EodeunCalendarMemo): ShiftDao {
    return db.shiftDao()
  }

  @Singleton
  @Provides
  fun provideShiftPatternDao(db: EodeunCalendarMemo): ShiftPatternDao {
    return db.shiftPatternDao()
  }

  @Singleton
  @Provides
  fun provideShiftAddonDao(db: EodeunCalendarMemo): ShiftAddonDao {
    return db.shiftAddOnDao()
  }

  @Singleton
  @Provides
  fun provideUserDao(db: EodeunCalendarMemo): UserDao {
    return db.userDao()
  }

  /**
  Repository 주입
   */

  // It is DB repository
  @Singleton
  @Provides
  fun provideLunaInfoRepository(dao: LunaInfoDao): LunaDBRepo {
    return LunaDBRepoImpl(dao)
  } // LunaDb

  // It is not DB repository (over DB)
  @Singleton
  @Provides
  fun provideLunaRepository(lunaDBRepo: LunaDBRepo): LunaRepo {
    return LunaRepoImpl(lunaDBRepo)
  } // Luna

  // it is DB repository
  @Singleton
  @Provides
  fun provideSpcdRepository(dao: SpcdDao): SpcdDBRepo {
    return SpcdDBRepoImpl(dao)
  }  // SpcdDB

  // It is not DB repository (over DB)
  @Singleton
  @Provides
  fun provideHolidayRepository(spcdRepo: SpcdDBRepo): SpcdRepo {
    return SpcdRepoImpl(spcdRepo)
  } //Spcd

  @Singleton
  @Provides
  fun provideShiftRepository(shiftDao: ShiftDao): ShiftRepo {
    return ShiftRepoImpl(shiftDao)
  }

  @Singleton
  @Provides
  fun provideScheduleRepository(scheduleDao: ScheduleDao): ScheduleRepo {
    return ScheduleRepoImpl(scheduleDao)
  }

  @Singleton
  @Provides
  fun provideShiftPatternRepository(dao: ShiftPatternDao): ShiftPatternRepo {
    return ShiftPatternRepoImpl(dao)
  }

  @Singleton
  @Provides
  fun provideShiftAddonRepository(dao: ShiftAddonDao): ShiftAddonRepo {
    return ShiftAddonRepoImpl(dao)
  }

  @Singleton
  @Provides
  fun provideUserRepository(dao: UserDao): UserRepo {
    return UserRepoImpl(dao)
  }

  /**
   * Viewmodel 주입
   */

  @Singleton
  @Provides
  fun provideCoreViewmodel(
    spcdRepo: SpcdRepo,
    lunaRepo: LunaRepo,
    userRepo: UserRepo,
    scheduleRepo: ScheduleRepo
  ): CoreVM {
    return CoreVM(spcdRepo, lunaRepo, userRepo, scheduleRepo)
  }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체의 내용 비교]]></title>
            <link>https://velog.io/@featch_life_dev/%EA%B0%9D%EC%B2%B4%EC%9D%98-%EB%82%B4%EC%9A%A9-%EB%B9%84%EA%B5%90</link>
            <guid>https://velog.io/@featch_life_dev/%EA%B0%9D%EC%B2%B4%EC%9D%98-%EB%82%B4%EC%9A%A9-%EB%B9%84%EA%B5%90</guid>
            <pubDate>Sat, 19 Oct 2024 07:30:26 GMT</pubDate>
            <description><![CDATA[<p>Kotlin에서 객체의 내용을 비교하기 위해서 <code>if (a==b)</code> 라는 실수는 자주 하게 된다
--항상 <code>false</code> 이 보장된다.--</p>
<p>객체의 비교는 생성된 객체 instance 의 비교로 되기 때문에 객체 값을 개별로 비교하게 되는 번거러움이 있다.</p>
<pre><code class="language-kotlin">class TermTime(private val value: Int=1440/*00:00~24:00*/) {

  /**
   * Equals
   * ```
   * val a = TermTime(1)
   * val b = TermTime(1)
   * assert(a == b)  // 이제 true가 됩니다.
   * ```
   *
   * @param other
   * @return
   */
  override fun equals(other: Any?): Boolean {
    if (this === other) return true
    if (other !is TermTime) return false
    return value == other.value
  }

  override fun hashCode(): Int {
    return value
  }</code></pre>
<blockquote>
<p>이렇게 <code>equals(other: Any?)</code>를 override 해야 한다 other의 Any? 의 자료형에 유의하자 특정자료형으로 선언하면 새로운 function 으로 인식한다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Test실행할때  A failure occurred while executing ]]></title>
            <link>https://velog.io/@featch_life_dev/Test%EC%8B%A4%ED%96%89%ED%95%A0%EB%95%8C-A-failure-occurred-while-executing</link>
            <guid>https://velog.io/@featch_life_dev/Test%EC%8B%A4%ED%96%89%ED%95%A0%EB%95%8C-A-failure-occurred-while-executing</guid>
            <pubDate>Fri, 18 Oct 2024 13:33:52 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Execution failed for task &#39;:app:mergeDebugAndroidTestJavaResource&#39;.</p>
</blockquote>
<blockquote>
<p>A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction</p>
</blockquote>
<pre><code>6 files found with path &#39;META-INF/LICENSE.md&#39;.
Adding a packaging block may help, please refer to
https://developer.android.com/reference/tools/gradle-api/8.7/com/android/build/api/dsl/Packaging
for more information</code></pre><p>위와 같은 에러가 발생할때 </p>
<blockquote>
<p>해결하는 법</p>
</blockquote>
<pre><code class="language-kotlin">// build.gradle.kts(Module :app)
android{
packaging {
    resources {
      excludes += &quot;/META-INF/{AL2.0,LGPL2.1}&quot;
      excludes += &quot;META-INF/LICENSE.md&quot; // &lt;- 추가 
      excludes += &quot;META-INF/LICENSE-notice.md&quot; &lt;- 추가
    }
   }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[settings.gradle.kts 에 maven { url 'https://jitpack.io' } 추가하는 법]]></title>
            <link>https://velog.io/@featch_life_dev/settings.gradle.kts-%EC%97%90-maven-url-httpsjitpack.io-%EC%B6%94%EA%B0%80%ED%95%98%EB%8A%94-%EB%B2%95</link>
            <guid>https://velog.io/@featch_life_dev/settings.gradle.kts-%EC%97%90-maven-url-httpsjitpack.io-%EC%B6%94%EA%B0%80%ED%95%98%EB%8A%94-%EB%B2%95</guid>
            <pubDate>Sun, 01 Sep 2024 11:35:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>settings.gradle.kts 에 
maven { url &#39;<a href="https://jitpack.io&#39;">https://jitpack.io&#39;</a> } 추가하는 법</p>
</blockquote>
<pre><code>pluginManagement {
  repositories {
    google {
      content {
        includeGroupByRegex(&quot;com\\.android.*&quot;)
        includeGroupByRegex(&quot;com\\.google.*&quot;)
        includeGroupByRegex(&quot;androidx.*&quot;)
      }
    }
    mavenCentral()
    gradlePluginPortal()
  }
  resolutionStrategy {
    eachPlugin {
      if (requested.id.id == &quot;com.google.android.gms.oss-licenses-plugin&quot;) {
        useModule(&quot;com.google.android.gms:oss-licenses-plugin:${requested.version}&quot;)
      } // if
    } //eachPlugin
  }//repositories
}//plugins

dependencyResolutionManagement {
  repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
  repositories {
    google()
    mavenCentral()
    maven { url=uri(&quot;https://jitpack.io&quot;) }
  }
}

rootProject.name = &quot;EodunCalendarMemo&quot;
include(&quot;:app&quot;)
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[text 두줄안되게]]></title>
            <link>https://velog.io/@featch_life_dev/text-%EB%91%90%EC%A4%84%EC%95%88%EB%90%98%EA%B2%8C</link>
            <guid>https://velog.io/@featch_life_dev/text-%EB%91%90%EC%A4%84%EC%95%88%EB%90%98%EA%B2%8C</guid>
            <pubDate>Fri, 23 Aug 2024 11:56:03 GMT</pubDate>
            <description><![CDATA[<pre><code>Text(
    text = &quot;$date&quot;,
    style = MaterialTheme.typography.displaySmall.copy(color = color),
    modifier = Modifier.sharedElement(
      state = rememberSharedContentState(key = &quot;date.id${dateId}&quot;),
      animatedVisibilityScope = animatedVisibilityScope
    ) // sharedElement
  ) // text</code></pre><blockquote>
<p>하면 20 인 경우 2 하고 0 이 다움즐에 나오는 버그가 생긴다.
그럴 때<code>softWrap = false</code>를 사용하면 2줄로 넘어 가는 문제를 방지 할 수 있다.
<code>softWrap</code>는 기본값이 true 이다.</p>
</blockquote>
<pre><code>Text(
    text = &quot;$date&quot;,
    style = MaterialTheme.typography.displaySmall.copy(color = color),
    modifier = Modifier.sharedElement(
      state = rememberSharedContentState(key = &quot;date.id${dateId}&quot;),
      animatedVisibilityScope = animatedVisibilityScope
    )// sharedElement
    .softWrap = false
  ) // text</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[웹에 간단히 동영상을 첨부하는법.]]></title>
            <link>https://velog.io/@featch_life_dev/%EC%9B%B9%EC%97%90-%EA%B0%84%EB%8B%A8%ED%9E%88-%EB%8F%99%EC%98%81%EC%83%81%EC%9D%84-%EC%B2%A8%EB%B6%80%ED%95%98%EB%8A%94%EB%B2%95</link>
            <guid>https://velog.io/@featch_life_dev/%EC%9B%B9%EC%97%90-%EA%B0%84%EB%8B%A8%ED%9E%88-%EB%8F%99%EC%98%81%EC%83%81%EC%9D%84-%EC%B2%A8%EB%B6%80%ED%95%98%EB%8A%94%EB%B2%95</guid>
            <pubDate>Mon, 12 Aug 2024 09:01:21 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<blockquote>
<p>화면을 캡쳐해서 동영상을 첨부할 때 간단하게 사용하는 프로그램을 소개한다.</p>
</blockquote>
<p><a href="https://www.screentogif.com/">스크린toGif</a>
홈페이지로 사용법을 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[debian 에서 error log 보기]]></title>
            <link>https://velog.io/@featch_life_dev/debian-%EC%97%90%EC%84%9C-error-log-%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@featch_life_dev/debian-%EC%97%90%EC%84%9C-error-log-%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 12 Aug 2024 09:00:41 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-bash">root# grep -i error /var/log/*</code></pre>
<h3 id="최근-1시간-이내-만-보기">최근 1시간 이내 만 보기</h3>
<pre><code class="language-bash"># 1시간 이내에 발생한 &quot;error&quot; 메시지 출력
sudo grep -i error /var/log/* | grep -E &quot;$(date -d &#39;-1 hour&#39; +%Y-%m-%d\T%H:%M:%S)..$(date +%Y-%m-%d\T%H:%M:%S)&quot;

# 다른거
sudo find /var/log -type f -mtime -1 -exec grep -i error {} \;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ksp] java.lang.IllegalArgumentException: List has more than one element.]]></title>
            <link>https://velog.io/@featch_life_dev/ksp-java.lang.IllegalArgumentException-List-has-more-than-one-element</link>
            <guid>https://velog.io/@featch_life_dev/ksp-java.lang.IllegalArgumentException-List-has-more-than-one-element</guid>
            <pubDate>Mon, 03 Jun 2024 04:50:57 GMT</pubDate>
            <description><![CDATA[<ol>
<li>에러 : <code>[ksp] java.lang.IllegalArgumentException: List has more than one element.</code>라는 에러가 발생했을때</li>
<li>발생이유 : Room 에서 목록에 없는 타입이 사용되어 발생한다.</li>
</ol>
<ul>
<li>room db에서는 목록에 정의된 Int, Long, String, Boolean ... 등 자료형이 아니면 typeconverter 가 가능해야 하는데 그런게 없다면 이런 에러가 발생하는데 발생위치를 바로 지적하지 못하기 때문에 좀 찾아야 한다.
내경우는 ULong 과 Long 으로 인해서 발생했다.<pre><code class="language-kotlin">//.entity
@Entity(tableName = &quot;date_box&quot;)
data class DateBoxEntity(
@PrimaryKey(autoGenerate = false)
val dateLong: Long,
var color: ULong,
var holiday: String,
var luna: String,
var memo: String,
var work: String,
)</code></pre>
위 소스에서 <code>var color: ULong,</code>의 ULong 은 Room 에서 알지못하는 자료형이라 에러가 나서
ULong을 Long으로 변환하고 적용시 <code>color.value = Color(value.color.toULong())</code>이렇게 변환토록 했다. 
아니면 TypeConvetor 로 ULong 과 Long을 변환토록 해봤는데 Error가 KSP가 먼저 접근해서 인지 에러가 고쳐지지 않았다.</li>
</ul>
<pre><code class="language-kotlin">//.converter

@TypeConverter
fun longToULong(value: Long): ULong = value.toULong()

@TypeConverter
fun uLongToLong(value: ULong): Long = value.toLong()</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Uncaught exception received. android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: date_box.dateLong (code 1555 SQLITE_CONSTRAINT_PRIMARYKEY)
]]></title>
            <link>https://velog.io/@featch_life_dev/Uncaught-exception-received.-android.database.sqlite.SQLiteConstraintException-UNIQUE-constraint-failed-datebox.dateLong-code-1555-SQLITECONSTRAINTPRIMARYKEY</link>
            <guid>https://velog.io/@featch_life_dev/Uncaught-exception-received.-android.database.sqlite.SQLiteConstraintException-UNIQUE-constraint-failed-datebox.dateLong-code-1555-SQLITECONSTRAINTPRIMARYKEY</guid>
            <pubDate>Mon, 03 Jun 2024 04:18:25 GMT</pubDate>
            <description><![CDATA[<ol>
<li><p>빌드에는 에러가 나지 않고 logcat 에 <code>Uncaught exception received. android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: date_box.dateLong (code 1555 SQLITE_CONSTRAINT_PRIMARYKEY)</code>라는 에러를 뿜는다.</p>
</li>
<li><p>발생이유는 아래와 같이 index 를 자동증가를 하지 않고 insert를   @Insert(onConflict = OnConflictStrategy.REPLACE) 하지 않으면 index가 중복될수 있어서 에러로 뿜는다.</p>
</li>
</ol>
<pre><code class="language-kotlin">//.entity

@Entity
data class test(
@Primary
val rid : Long</code></pre>
<pre><code class="language-kotlin">//.dao
@Insert </code></pre>
<ol start="3">
<li>해결책 : <strong>insert 에 onConflict = OnConflictStrategy.REPLACE</strong> 를 추가한다.<pre><code class="language-kotlin">//.entity
</code></pre>
</li>
</ol>
<p>@Entity
data class test(
@PrimaryKey(autoGenerate = false)
val rid : Long</p>
<pre><code>
```kotlin
//.dao
  @Insert(onConflict = OnConflictStrategy.REPLACE)
  suspend fun insert(entity: DateBoxEntity): Long</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Release 버젼에서 Log.d 지우기]]></title>
            <link>https://velog.io/@featch_life_dev/Release-%EB%B2%84%EC%A0%BC%EC%97%90%EC%84%9C-Log.d-%EC%A7%80%EC%9A%B0%EA%B8%B0</link>
            <guid>https://velog.io/@featch_life_dev/Release-%EB%B2%84%EC%A0%BC%EC%97%90%EC%84%9C-Log.d-%EC%A7%80%EC%9A%B0%EA%B8%B0</guid>
            <pubDate>Wed, 15 May 2024 12:48:07 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Android 개발을 하던중 Log.d 로 로그를 남기는 경우, <code>release</code>버젼에서는 log 코드가 남는것을 원하지 않를 때가 있다. 이럴때 PorGuard 의 설정으로 Log.d 의 내용을 삭제 하는 방법이 있다.</p>
</blockquote>
<ul>
<li><p>release 버젼과 debug 버젼에 각각 다른 proguard-rules 을 적용한다.</p>
<pre><code class="language-java">buildTypes {
  release {
    isMinifyEnabled = true
    proguardFiles(getDefaultProguardFile(&quot;proguard-android-optimize.txt&quot;), &quot;proguard-rules-release.pro&quot;)
  }
  debug {
    isMinifyEnabled = false
    proguardFiles(getDefaultProguardFile(&quot;proguard-android-optimize.txt&quot;), &quot;proguard-rules.pro&quot;)

  }
}</code></pre>
</li>
<li><p>다음 내용을 proguard-rules-release.pro 에 추가 하낟.
```java</p>
</li>
<li><p>assumenosideeffects class android.util.Log {
   public static boolean isLoggable(java.lang.String, int);
   public static int v(...);
   public static int i(...);
   public static int w(...);
   public static int d(...);
   public static int e(...);
}</p>
<pre><code></code></pre></li>
</ul>
<p>음...  OK</p>
]]></description>
        </item>
    </channel>
</rss>