<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Kong-droid</title>
    <link>https://ddusi-dod.tistory.com/</link>
    <description> &amp;zwj;  Android Developer </description>
    <language>ko</language>
    <pubDate>Mon, 20 Jul 2026 10:42:59 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>콩드로이드</managingEditor>
    <image>
      <title>Kong-droid</title>
      <url>https://tistory1.daumcdn.net/tistory/3140819/attach/048758838e66407c87a37646d4315ede</url>
      <link>https://ddusi-dod.tistory.com</link>
    </image>
    <item>
      <title>[Android] SharedPreferences 대신 DataStore  #2</title>
      <link>https://ddusi-dod.tistory.com/entry/Android-SharedPreferences-%EB%8C%80%EC%8B%A0-DataStore-2</link>
      <description>&lt;h3 data-end=&quot;429&quot; data-start=&quot;395&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DataStore는 암호화를 자동으로 제공 X&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DataStore를 사용할 때 가장 주의해야 할 부분인 거 같습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;542&quot; data-start=&quot;519&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;523&quot; data-start=&quot;519&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;토큰&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;535&quot; data-start=&quot;524&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자 식별 정보&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;542&quot; data-start=&quot;536&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개인정보&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;598&quot; data-start=&quot;544&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 위와 같은 값들을 그대로 저장하면 루팅된 기기나 백업 파일을 통해 노출될 가능성이 있습니다,,&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;598&quot; data-start=&quot;544&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 암호화 작업이 필요해요&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-end=&quot;598&quot; data-start=&quot;544&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt; EncryptedSharedPreferences&amp;nbsp;Deprecated&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원래는 EncryptedSharedPreference가 공식 대안이었지만, 현재는 Deprecated 되었습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대신 &lt;b&gt;Jetpack Security (Crypto API)와 DataStore&lt;/b&gt;의 조합이 권장되었습니다!&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Crypto API를 사용해서 DataStore를 암호화해서 사용하는 방법에 대해 알아보겠습니다&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Crypto API&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;코드는 단순 예시라서 사용 플로우만 확인하면 좋을 거 같습니다..!&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;라이브러리 추가&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1766662454263&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation(&quot;androidx.security:security-crypto:1.1.0-alpha06&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Master Key 생성&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;MasterKey는 암호화/복호화에 직접 사용되는 키라기보다는, Android Keystore에 저장되는 최상위 키 역할입니다&lt;br /&gt;&lt;br /&gt;즉, MasterKey를 통해 Android Keystore에 위임되기 때문에&amp;nbsp; 키가 코드나 저장소에 노출되는 것을 방지할 수 있습니다&lt;/p&gt;
&lt;pre id=&quot;code_1766662492994&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private fun getMasterKey(context: Context): MasterKey {
    return MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;암호화/복호화&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;암호화&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766662891115&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun encrypt(
    context: Context,
    plainText: String
): String {
    val masterKey = getMasterKey(context)

    val keyStore = KeyStore.getInstance(&quot;AndroidKeyStore&quot;).apply {
        load(null)
    }

    //MasterKey가 관리하는 실제 SecretKey 가져오기,,
    val secretKey = keyStore.getKey(masterKey.keyAlias, null) as SecretKey

    val cipher = Cipher.getInstance(&quot;AES/GCM/NoPadding&quot;)
    cipher.init(Cipher.ENCRYPT_MODE, secretKey)

    val iv = cipher.iv
    val encryptedBytes = cipher.doFinal(plainText.toByteArray(Charsets.UTF_8))

    val combined = ByteArray(iv.size + encryptedBytes.size)
    System.arraycopy(iv, 0, combined, 0, iv.size)
    System.arraycopy(encryptedBytes, 0, combined, iv.size, encryptedBytes.size)

    return Base64.encodeToString(combined, Base64.DEFAULT)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;복호화&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766662910491&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun decrypt(
    context: Context,
    encryptedText: String
): String {
    val masterKey = getMasterKey(context)

    val keyStore = KeyStore.getInstance(&quot;AndroidKeyStore&quot;).apply {
        load(null)
    }

    val secretKey = keyStore.getKey(masterKey.keyAlias, null) as SecretKey

    val combined = Base64.decode(encryptedText, Base64.DEFAULT)

    val iv = combined.copyOfRange(0, 12)
    val encryptedBytes = combined.copyOfRange(12, combined.size)

    val cipher = Cipher.getInstance(&quot;AES/GCM/NoPadding&quot;)
    val spec = GCMParameterSpec(128, iv)
    cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)

    val decryptedBytes = cipher.doFinal(encryptedBytes)
    return String(decryptedBytes, Charsets.UTF_8)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;DataStore에 암호화 적용하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저장&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766662930031&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;suspend fun saveEncryptedToken(
    context: Context,
    token: String
) {
    val encrypted = encrypt(context, token)
	
    context.dataStore.edit { prefs -&amp;gt;
        prefs[stringPreferencesKey(&quot;저장할 때 쓸 키 값&quot;)] = encrypted
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;불러오기&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766662956867&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun getDecryptedToken(
    context: Context
): Flow&amp;lt;String?&amp;gt; {
    return context.dataStore.data.map { prefs -&amp;gt;
        prefs[stringPreferencesKey(&quot;저장할 때 썼던 키 값&quot;)]
            ?.let { decrypt(context, it) }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-end=&quot;2836&quot; data-start=&quot;2817&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;암호화를 사용해서 어떻게 저장하는지 간단한 예시로 알아봤어요 ..!&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2858&quot; data-start=&quot;2838&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DataStore에 저장하고 불러오는&amp;nbsp; 모든 값을 암호화할 필요는 없지만, 아래와 같은 노출되면 안 되는 정보들은 암호화가 필수입니다&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2935&quot; data-start=&quot;2874&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2904&quot; data-start=&quot;2874&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Access Token / Refresh Token&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2916&quot; data-start=&quot;2905&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자 식별 정보&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2935&quot; data-start=&quot;2917&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이메일, 전화번호 등 개인정보&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Android</category>
      <category>Android</category>
      <category>android data store</category>
      <category>android data store encrypt</category>
      <category>android datastore</category>
      <category>android sharedpreference</category>
      <category>data store</category>
      <category>datastore</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/173</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Android-SharedPreferences-%EB%8C%80%EC%8B%A0-DataStore-2#entry173comment</comments>
      <pubDate>Thu, 25 Dec 2025 20:59:56 +0900</pubDate>
    </item>
    <item>
      <title>[Android] SharedPreferences 대신 DataStore  #1</title>
      <link>https://ddusi-dod.tistory.com/entry/Android-SharedPreferences-%EB%8C%80%EC%8B%A0-DataStore-1</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DataStore를 이번에 처음 제대로 써봤습니다..!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SharedPreferences &amp;rarr; EncryptedSharedPreferences &amp;rarr; DataStore로&amp;nbsp;왜 넘어가야 하는지 궁금해서 정리해봤어요!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;SharedPreferences가 왜 더 이상 추천되지 않을까 ?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 동기 방식이므로 ANR 위험성이 존재&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;: commit() / apply() 모두 파일 I/O 기반&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;commit() &amp;rarr; 동기(blocking), 완료될 때까지 UI Thread 멈춤&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;apply() &amp;rarr; 비동기처럼 보이지만 디스크 I/O는 백그라운드에서 처리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사실 자주 발생하진 않을 것 같은데, 저장량이 많거나 여러 쓰기 요청이 한 번에 몰리면 영향이 있을 수 있습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 앱 내부 저장소에 XML 파일 형태로 저장되므로 값이 그대로 노출&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;: 이를 해결하기 위해 공식으로 지원되는&amp;nbsp;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;Encrypted&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;Shared&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Preferences&lt;/a&gt; 가 있었는데 Deprecated됨&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;-&amp;gt; 이러한 이유들로 현재 구글에서 공식적으로 권장하는 방법은 DataStore를 사용하는 것입니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;DataStore란 ?&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SharedPreferences의 XML 기반&amp;middot;동기 저장 방식을 개선한 Jetpack 기반 &lt;b&gt;비동기 Key-Value 저장소&lt;/b&gt;입니다 (Binary 포맷으로 저장)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용법은 아래와 같습니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1️⃣ library 연동&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;//toml 파일에 선언
datastore = { group = &quot;androidx.datastore&quot;, name = &quot;datastore-preferences&quot;, 
	version.ref = &quot;datastore&quot;} // 1.1.1 사용
    
//build.gradle(app) 선언
implementation(libs.datastore)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2️⃣ Context기반으로 접근가능한 datastore 생성&lt;/span&gt;&lt;/h4&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val Context.dataStore by preferencesDataStore(
    name = &quot;test_datastore&quot; // datastore 명칭
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3️⃣ &lt;b&gt;비동기 형태&lt;/b&gt;로 save, get 함수 생성&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DataStore는 기본적으로&amp;nbsp;비동기 + 코루틴 기반이라,&amp;nbsp;값을 저장할 때도&amp;nbsp;suspend, 가져올 때는&amp;nbsp;Flow로 동작&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;suspend fun &amp;lt;T&amp;gt; save(
    context: Context,
    key: Preferences.Key&amp;lt;T&amp;gt;,
    value: T
) {
    context.dataStore.edit { prefs -&amp;gt;
        prefs[key] = value
    }
}


fun &amp;lt;T&amp;gt; get(
    context: Context,
    key: Preferences.Key&amp;lt;T&amp;gt;
): Flow&amp;lt;T?&amp;gt; {
   return context.dataStore.data.map { prefs -&amp;gt;
       prefs[key]
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4️⃣ 저장, 불러오기&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1763882281934&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//위에서 생성한 함수 사용
/
save(context, stringPreferencesKey(&quot;token&quot;), &quot;토큰값&quot;)

get(context, stringPreferencesKey(&quot;token&quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용법은 생각보다 꽤 간단했어요!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정리해보자면, SharedPreference와 DataStore의 차이는 아래와 같습니다&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 71.8604%; height: 112px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px; width: 19.6512%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px; width: 36.2791%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;SharedPreference&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px; width: 40%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;DataStore&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 19px; width: 19.6512%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;구조&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px; width: 36.2791%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;XML 파일&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px; width: 40%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Binary file&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 19px; width: 19.6512%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;I/O 방식&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px; width: 36.2791%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동기&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px; width: 40%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;완전 비동기(Coroutine + Flow)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 19px; width: 19.6512%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;스레드 안전성&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px; width: 36.2791%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;완벽히 안전하지 않음&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px; width: 40%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;철저히 스레드-safe&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 19px; width: 19.6512%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;대용량 처리&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px; width: 36.2791%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비효율적&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px; width: 40%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Stream 기반으로 안전하게 처리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;text-align: left; width: 19.6512%; height: 19px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;타입 안정성&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 36.2791%; height: 19px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;key-string 기반 , 비교적 느슨&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 40%; height: 19px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;키 자체에 타입 존재&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서 각각 타입 안정성만 좀 확인해보자면,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[SharePreference] : 다른 타입 가져오려고 하면 &lt;u&gt;런타임에서 오류 발생&amp;nbsp;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763881488734&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val prefs = context.getSharedPreferences(&quot;test&quot;, Context.MODE_PRIVATE)

prefs.edit()
    .putString(&quot;age&quot;, &quot;20&quot;)   // age에 스트링 저장 
    .apply()


//값을 가져올 때 
val age: Int = prefs.getInt(&quot;age&quot;, 0)  //런타임에서 ClassCastException&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[DataStore] : 타입 미일치 시 &lt;u&gt;컴파일에서 오류 발생&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;왜냐하면, DataStore는 &lt;b&gt;키가 타입을 정의&lt;/b&gt;하기 때문에, 저장할 때도, 읽을 때도 컴파일 단계에서 오류를 잡아줍니다!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763881561250&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object PrefKeys {
    val AGE = intPreferencesKey(&quot;age&quot;)
    val NICKNAME = stringPreferencesKey(&quot;nickname&quot;)
}

prefs[PrefKeys.AGE] = &quot;20&quot;  //int에 string 넣으려고 해서 컴파일타임에서 오류&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만, DataStore도 암호화가 자동으로 지원되진 않기 때문에 민감 정보 저장 시 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;권장사항은 Crypto API 와 DataStore를 같이 쓰는 것입니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 정보를 암호화할 필욘 없지만 토큰과 같은 민감정보들, 그리고 개인정보들은 암호화를 사용해야겠죠..!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;암호화 방법에 대해서는 2탄 포스팅으로 돌아오겠습니다!&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>  Android</category>
      <category>Android</category>
      <category>android datastore</category>
      <category>android datastore implementation</category>
      <category>datastore</category>
      <category>datastore crypto</category>
      <category>EncryptedSharedPreferences</category>
      <category>EncryptedSharedPreferences deprecated</category>
      <category>sharepreference</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/172</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Android-SharedPreferences-%EB%8C%80%EC%8B%A0-DataStore-1#entry172comment</comments>
      <pubDate>Sun, 23 Nov 2025 16:54:13 +0900</pubDate>
    </item>
    <item>
      <title>[Android] ForegroundService - SecurityException</title>
      <link>https://ddusi-dod.tistory.com/entry/Android-ForegroundService-SecurityException</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761182658616&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Caused by: java.lang.SecurityException: Starting FGS with type microphone&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;service 실행 중 SecurityException이 발생했었는데, 원인을 살펴보면 Android 14부터 강화된 Foreground Service(이하 FGS) 정책과 관련된 경우였습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우선 Android 14와 무관하게, 서비스 실행 시점이 foreground 상태가 아닐 경우 FGS 관련 SecurityException이 발생합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(이 포스팅에서는 이 부분은 제외하고, Android 14 변경사항 위주로 정리해볼게요..!)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Android 14부터는 아래의&amp;nbsp;특정 FGS 유형(Foreground Service Type) 에 대해 추가적인 조건이 요구됩니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div class=&quot;txc-textbox&quot; style=&quot;background-color: #fdfaff; border: dashed; border-width: 0.1px; border-color: #e2e2e2; border-radius: 10px; padding: 20px 20px 20px 20px;&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;camera, connectedDevice, dataSync, health, location,&amp;nbsp;mediaPlayback, mediaProjection&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;microphone, phoneCall, remoteMessaging, shortService, specialUse, systemExempted&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. android:foregroundServiceType 지정 (AndroidManifest.xml)&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;Android10에서는 service를 사용할 때 manifest에서 android:foregroundServiceType을 지정할 수 있었다면,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Android14에서는 특정 권한들은 아래와 같이 android:foregroundServiceType 지정이 필수가 되었습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761185196171&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  &amp;lt;service android:name=&quot;서비스 클래스명&quot;
           android:foregroundServiceType=&quot;dataSync&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;Manifest에서 특정 서비스의 유형을 정의하지 않으면 시스템은 해당 서비스의 startForeground() 호출 시&amp;nbsp; &lt;/span&gt;MissingForegroundServiceTypeException&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;을 발생할 수 있습니다,,&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2. startForeground()호출 시 타입 명시&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1761267068030&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Manifest에 정의된 타입과 startForeground() 호출 시 지정한 타입이 일치해야 &amp;nbsp;시스템이 서비스 목적을 명확히 인식하고 권한 검사를 올바르게 수행합니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;3.&amp;nbsp; Runtime 권한(Prerequisites) 확인&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Android 14에서는 포그라운드 서비스 유형(Foreground Service Type)에 따라 런타임에서 반드시 충족해야 하는 권한 조건이 추가되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들어,&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;FOREGROUND_SERVICE_TYPE_LOCATION &amp;rarr; ACCESS_COARSE_LOCATION 또는 ACCESS_FINE_LOCATION&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;FOREGROUND_SERVICE_TYPE_CAMERA &amp;rarr; CAMERA&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;FOREGROUND_SERVICE_TYPE_MICROPHONE &amp;rarr; RECORD_AUDIO&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;startForeground() 호출 시, 해당 서비스 유형에 맞는 런타임 권한이 이미 허용되어 있는지를 검사합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 앱이 아직 권한을 요청하거나 승인받기 전 상태에서 startForeground()를 호출하면&amp;nbsp;SecurityException이 발생합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정리하자면 Android 14에서는 포그라운드 서비스의 유형 선언, 권한 요청 시점이 모두 명확해야 합니다&lt;br /&gt;Manifest, startForeground(), 권한 요청 시점 &amp;mdash; 이 세 개가 맞물리지 않으면&amp;nbsp; SecurityException이&amp;nbsp;발생할&amp;nbsp;수&amp;nbsp;있어요&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;a style=&quot;color: #0593d3;&quot; href=&quot;https://developer.android.com/about/versions/14/changes/fgs-types-required?utm_source=chatgpt.com#use-cases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;&lt;/span&gt;를 살펴보면 아래와 같이 각각 어떤 값을 설정해줘야하는지 나와있습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-24 오전 10.21.47.png&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;950&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lEI2s/dJMb8V0QYEM/krRrKwrim1kIaC7cvbPop1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lEI2s/dJMb8V0QYEM/krRrKwrim1kIaC7cvbPop1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lEI2s/dJMb8V0QYEM/krRrKwrim1kIaC7cvbPop1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlEI2s%2FdJMb8V0QYEM%2FkrRrKwrim1kIaC7cvbPop1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1572&quot; height=&quot;950&quot; data-filename=&quot;스크린샷 2025-10-24 오전 10.21.47.png&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;950&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;특정 FGS 유형를 사용하시는 경우엔 공식문서에서 요건들을 확인하면 될 거 같습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <category>  Android</category>
      <category>andrid service securityexception</category>
      <category>android fgs</category>
      <category>android foregroundservice</category>
      <category>android manifest</category>
      <category>android securityexception</category>
      <category>android service</category>
      <category>android service exception</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/171</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Android-ForegroundService-SecurityException#entry171comment</comments>
      <pubDate>Fri, 24 Oct 2025 20:28:43 +0900</pubDate>
    </item>
    <item>
      <title>[Android] AudioManager와 isSpeakerPhoneOn Deprecated</title>
      <link>https://ddusi-dod.tistory.com/entry/Android-AudioManager%EC%99%80-isSpeakerPhoneOn-Deprecated</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;AudioManager란?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AudioManager는 안드로이드에서 오디오 장치 제어, 상태 관리를 하는 시스템 서비스입니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보통 이럴 때 사용합니다&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;블루투스, 이어폰, 수화기, 스피커 등 오디오 출력 장치 전환&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마이크 음소거, 볼륨 제어&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오디오 모드 설정 (MODE_NORMAL, MODE_IN_CALL, MODE_IN_COMMUNICATION 등)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;⚠️&amp;nbsp;&lt;/b&gt;isSpeakerphoneOn의 deprecated&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Android 14 (API 34, UpsideDownCake)부터 audioManager.isSpeakerphoneOn과 audioManager.setSpeakerphoneOn()이 deprecated 됐습니다..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이전 방식은 스피커폰이 켜져 있다는 단일 상태만 다뤘지만, 다양한 오디오 장치가 나온 만큼, 어떤 장치로 출력 시킬지 정하는 방식이 되었어요&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제는 availableCommunicationDevices 리스트를 통해 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;u&gt;사용 가능한 오디오 장치들&lt;/u&gt;&lt;/b&gt;&lt;/i&gt;을 가져와서 직접 설정을 해줘야해요&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래 예시는 스피커 모드 on / off 입니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;private fun toggleSpeakerMode(turnOn: Boolean) {
    val audioManager = requireContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager
    audioManager.mode = AudioManager.MODE_IN_COMMUNICATION

    if (Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
        var speakerDevice: AudioDeviceInfo? = null
        val devices = audioManager.availableCommunicationDevices
        for (device in devices) {
            if (device.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
                speakerDevice = device
                break
            }
        }
        if (speakerDevice != null) {
            if (turnOn) {
                audioManager.setCommunicationDevice(speakerDevice)
                //특정 오디오 장치로 통화 음성을 출력
            } else {
                audioManager.clearCommunicationDevice()
            }
        }
    } else {
        // 구버전 호환
        audioManager.isSpeakerphoneOn = turnOn
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 출력장치를 정할 수 있는데, 단말기로&amp;nbsp; 볼륨을 조절 할 때는 사용하고 있는 Stream의 볼륨이 조절됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;stream의 종류는 아래와 같습니당&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 54.4186%; height: 166px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 48.0233%; height: 19px;&quot;&gt;&lt;b&gt;stream 종류&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 51.8605%; height: 19px;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 48.0233%; height: 19px;&quot;&gt;&lt;span&gt;STREAM_MUSIC&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 51.8605%; height: 19px;&quot;&gt;&lt;span&gt;음악, 영상, 게임 등 일반 미디어&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 48.0233%; height: 19px;&quot;&gt;&lt;span&gt;STREAM_VOICE_CALL&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 51.8605%; height: 19px;&quot;&gt;&lt;span&gt;통화, VoIP&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 48.0233%; height: 19px;&quot;&gt;&lt;span&gt;STREAM_RING&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 51.8605%; height: 19px;&quot;&gt;&lt;span&gt;전화벨 소리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 48.0233%; height: 19px;&quot;&gt;&lt;span&gt;STREAM_ALARM&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 51.8605%; height: 19px;&quot;&gt;&lt;span&gt;알람음&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 48.0233%; height: 19px;&quot;&gt;&lt;span&gt;STREAM_NOTIFICATION&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 51.8605%; height: 19px;&quot;&gt;&lt;span&gt;알림음&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 48.0233%; height: 19px;&quot;&gt;&lt;span&gt;STREAM_SYSTEM&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 51.8605%; height: 19px;&quot;&gt;&lt;span&gt;시스템 UI 소리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;보통 자동으로 사용중인 스트림의 보륨이 조절되는데, 좀 특이한 경우들이 있어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;VoIP나 직접 오디오트랙을 관리하는 경우엔 모드를 설정해줘야 스트림의 볼륨조절이 가능합니다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AudioManager에서 아래와 같이 모드를 지정해야 합니다&lt;/p&gt;
&lt;pre id=&quot;code_1760662722551&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;audioManager.mode = AudioManager.MODE_IN_COMMUNICATION&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;볼륨조절을 하는 스트림을 명시적으로 설정할 수도 있는데, 자동으로 잘 붙어요&lt;/p&gt;
&lt;pre id=&quot;code_1760663354546&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;requireActivity().volumeControlStream = AudioManager.STREAM_VOICE_CALL&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고) Mode 종류는 아래와 같습니다&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 149px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20.6977%; height: 19px;&quot;&gt;&lt;b&gt;&lt;span&gt;Mode 종류&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;&lt;b&gt;&lt;span&gt;설명&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 31.3953%; height: 19px;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;b&gt;사용 예시&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 22.907%; height: 19px;&quot;&gt;&lt;b&gt;&lt;span&gt;매핑 Stream&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20.6977%; height: 19px;&quot;&gt;&lt;span&gt;MODE_NORMAL&lt;span&gt; (기본)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;&lt;span&gt;일&lt;/span&gt;&lt;span&gt;반 오디오 재생&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 31.3953%; height: 19px;&quot;&gt;&lt;span&gt;&lt;span&gt;대부분의 앱&lt;/span&gt;(음악, 영상, 게임, 효과음)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 22.907%; height: 19px;&quot;&gt;&lt;span&gt;STREAM_MUSIC&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20.6977%; height: 19px;&quot;&gt;&lt;span&gt;MODE_RINGTONE&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;&lt;span&gt;전화 &lt;span&gt;벨소리&lt;/span&gt; 재생 모드&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 31.3953%; height: 19px;&quot;&gt;&lt;span&gt;벨소리&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 22.907%; height: 19px;&quot;&gt;&lt;span&gt;STREAM_RING&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20.6977%; height: 19px;&quot;&gt;&lt;span&gt;MODE_IN_CALL&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;&lt;span&gt;&lt;span&gt;통신사를 통한 통화&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 31.3953%; height: 19px;&quot;&gt;&lt;span&gt;시스템/기본 전화앱 전용&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 22.907%; height: 19px;&quot;&gt;&lt;span&gt;STREAM_VOICE_CALL&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;width: 20.6977%; height: 38px;&quot;&gt;&lt;span&gt;MODE_IN_COMMUNICATION&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 38px;&quot;&gt;&lt;span&gt;&lt;span&gt;VoIP/WebRTC&lt;/span&gt; 등 네트워크 기반 통화&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 31.3953%; height: 38px;&quot;&gt;&lt;span&gt;Zoom/Discord/게임 보이스챗/Chime 등 &lt;span&gt;네트통 음성&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 22.907%; height: 38px;&quot;&gt;&lt;span&gt;STREAM_VOICE_CALL&lt;span&gt;로 자동 매핑&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 35px;&quot;&gt;
&lt;td style=&quot;width: 20.6977%; height: 35px;&quot;&gt;&lt;span&gt;MODE_CALL_SCREENING&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 35px;&quot;&gt;&lt;span&gt;수신전화 &lt;span&gt;콜 스크리닝&lt;/span&gt; 중 오디오&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 31.3953%; height: 35px;&quot;&gt;&lt;span&gt;기본 다이얼러/콜스크리닝 앱&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 22.907%; height: 35px;&quot;&gt;&lt;span&gt;STREAM_VOICE_CALL&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Android</category>
      <category>Android</category>
      <category>android audio</category>
      <category>android audio device</category>
      <category>android speaker</category>
      <category>android speaker toggle</category>
      <category>audio manager</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/170</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Android-AudioManager%EC%99%80-isSpeakerPhoneOn-Deprecated#entry170comment</comments>
      <pubDate>Sat, 18 Oct 2025 08:51:30 +0900</pubDate>
    </item>
    <item>
      <title>[Android] 소켓 연결하기 - WebSocket , Socket.IO</title>
      <link>https://ddusi-dod.tistory.com/entry/Android-%EC%86%8C%EC%BC%93-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0-WebSocket-SocketIO</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Socket IO를 사용할 일이 있었는데, 웹소켓과 Socket IO에 대해 정리해보면 좋을 거 같아서 정리를 해봅니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;425&quot; data-start=&quot;283&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모두 실시간 양방향 통신을 위해 사용되지만, 내부 구조와 동작 방식에는 분명한 차이가 있어요&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;502&quot; data-start=&quot;427&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 포스팅에서는 WebSocket과 Socket.IO의 차이점, 그리고 각각을 Android에서 어떻게 연결해야하는지에 대해 알아보겠습니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;502&quot; data-start=&quot;427&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 122px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;WebSocket&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Socket IO&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;프로토콜&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;순수 WebSocket 프로토콜 (RFC 6455)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;WebSocket 기반 + 추가 기능 (자체 프로토콜)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;통신 방식&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단순한 양방향 통신&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;양방향 통신 + &lt;b&gt;이벤트 기반 통신&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;재연결 지원&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기본적으로 없음 (직접 구현해야 함)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자동 재연결 내장 (reconnect = boolean 으로 설정)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Fallback 지원&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;없음&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;WebSocket이 안 되면 HTTP long polling 사용 가능&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;브로드캐스트&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;직접 구현 필요&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기본 지원 (io.emit, socket.broadcast.emit)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;WebSocket은 낮은 수준의 통신 채널이고, Socket.IO는 그 위에 올라가는 고수준 프레임워크&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉 Socket IO도 웹소켓을 기반으로 하지만 프로토콜이 다르기 때문에 연결 방식이 다름&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그러므로 서버가 Socket.IO 기반이라면 클라이언트도 Socket.IO 클라이언트를 사용해야 정상적인 통신이 가능&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;WebSocket 연결&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 라이브러리 추가&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758957017768&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation(&quot;com.squareup.okhttp3:okhttp:4.12.0&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 연결&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758957032313&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val client = OkHttpClient()

val request = Request.Builder()
    .url(연결할 소켓 주소)
    .build()

val listener = object : WebSocketListener() {
    override fun onOpen(webSocket: WebSocket, response: Response) {
        // 연결 성공 시
    }

    override fun onMessage(webSocket: WebSocket, text: String) {
        // 서버로부터 메시지를 수신했을 때
    }

    override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
       // 에러 발생 시
    }
}

val webSocket = client.newWebSocket(request, listener)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Request에서는 주소 외에도 헤더, 쿠키 등 옵션을 지정&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;client.newWebSocket()을 호출하면 즉시 비동기적으로 연결 시도가 시작&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;rarr; SocketIO와 달리 별도의 .connect()를 호출할 필요는 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;연결 결과는 WebSocketListener 콜백을 통해 확인&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 서버로 메시지 전송 시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758957539440&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;webSocket.send(미리 약속된 형식의 데이터)

//string
webSocket.send(&quot;Hello World!&quot;)


//json
val json = &quot;&quot;&quot;
    {
        &quot;type&quot;: &quot;chat&quot;,
        &quot;message&quot;: &quot;Hello&quot;
    }
&quot;&quot;&quot;.trimIndent()
webSocket.send(json)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서버와 약속된 형식(JSON 등)에 맞춰 데이터를 전송해야함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전송 가능한 데이터 타입&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2092&quot; data-start=&quot;2038&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2048&quot; data-start=&quot;2038&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;String&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2064&quot; data-start=&quot;2051&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JSON 형식 문자열&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2092&quot; data-start=&quot;2067&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Binary Data&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Socket IO 연결&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 라이브러리 연결&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758957132400&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation(&quot;io.socket:socket.io-client:2.1.2&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 소켓 생성 및 연결&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758957194929&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import io.socket.client.IO
import io.socket.client.Socket
import org.json.JSONObject

lateinit var mSocket: Socket

try {
    mSocket = IO.socket(연결할 소켓 주소 )
} catch (e: URISyntaxException) {
    e.printStackTrace()
}

mSocket.on(Socket.EVENT_CONNECT) {
  //
}

mSocket.on(&quot;message&quot;) { args -&amp;gt;
    // 서버에서 내려오는 메시지가 찍히는 부분
    // args로 payload를 가져올 수 있습니다,,
}

mSocket.connect()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 서버로 메시지 전송 시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758958333310&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mSocket.emit(미리 약속된 이벤트명, 보낼 데이터)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; Socket.IO는 이벤트 기반 통신이기 때문에, 약속된 이벤트 이름 + 데이터 형태로 메시지 전송&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전송 가능한 데이터는 대부분의 기본 타입이 포함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안드로이드는 서버의 상황에 따라 가게 되지만..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Socket.IO는 자체 프로토콜을 사용하기 때문에 서버와 클라이언트는 반드시 같은 방식으로 구성되어야 하고,&lt;br /&gt;WebSocket은 표준 프로토콜이라서 다양한 플랫폼에 독립적으로 연결할 수 있는 장점이 있지만, 그만큼 기능은 단순하고 직접 구현해야할 부분이 많다는 단점이 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Android</category>
      <category>Android</category>
      <category>android socket connect</category>
      <category>android socketio</category>
      <category>android socketio listener</category>
      <category>android websocket</category>
      <category>android websocket connect</category>
      <category>android wss</category>
      <category>android wss connect</category>
      <category>socket client</category>
      <category>Websocket</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/167</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Android-%EC%86%8C%EC%BC%93-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0-WebSocket-SocketIO#entry167comment</comments>
      <pubDate>Sat, 27 Sep 2025 16:49:35 +0900</pubDate>
    </item>
    <item>
      <title>[Android] 하나의 화면에 다양한 UI 블록을 구성하는 3가지 방식</title>
      <link>https://ddusi-dod.tistory.com/entry/Android-%ED%95%98%EB%82%98%EC%9D%98-%ED%99%94%EB%A9%B4%EC%97%90-%EB%8B%A4%EC%96%91%ED%95%9C-UI-%EB%B8%94%EB%A1%9D%EC%9D%84-%EA%B5%AC%EC%84%B1%ED%95%98%EB%8A%94-3%EA%B0%80%EC%A7%80-%EB%B0%A9%EC%8B%9D</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;앱 만들다 보면 한 화면에 여러 종류의 UI를 스크롤 되게 구성해야 할 일이 자주 생기는데,&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이런 경우에 사용하는 3가지 방식에 대해 알아보겠습니다&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;FragmentContainerView&amp;nbsp;방식 (Fragment 삽입)&lt;/span&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;nbsp;특징&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;각 UI 블록을 Fragment로 나눠서 FragmentContainerView에 삽입&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;고정된 레이아웃이나, 특정 위치에 동적으로 fragment를 붙이는 방식&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;nbsp;장점&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;각 블록마다 생명주기, ViewModel 분리 가능&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로직이 복잡하거나 독립된 기능일 경우 구조화에 유리&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;nbsp;단점&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;fragment가 많아지면 성능 저하 발생&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ScrollView 안에 fragment 여러 개 → 비추천 구조 (nested fragment 문제 등)&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[layout]&lt;/span&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;&amp;lt;ScrollView
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:layout_width=&quot;match_parent&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:layout_height=&quot;match_parent&quot;&amp;gt;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;LinearLayout
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:orientation=&quot;vertical&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:layout_width=&quot;match_parent&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:layout_height=&quot;wrap_content&quot;&amp;gt;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;androidx.fragment.app.FragmentContainerView
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:id=&quot;@+id/bannerFragmentContainer&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:layout_width=&quot;match_parent&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:layout_height=&quot;wrap_content&quot; /&amp;gt;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;androidx.fragment.app.FragmentContainerView
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:id=&quot;@+id/listFragmentContainer&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:layout_width=&quot;match_parent&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:layout_height=&quot;wrap_content&quot; /&amp;gt;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/LinearLayout&amp;gt;
&amp;lt;/ScrollView&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[activity]&lt;/span&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;&amp;nbsp;&amp;nbsp;supportFragmentManager.beginTransaction()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.replace(R.id.bannerFragmentContainer, BannerFragment())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.commit()

&amp;nbsp;&amp;nbsp;supportFragmentManager.beginTransaction()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.replace(R.id.listFragmentContainer, ItemListFragment())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.commit()&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;addLayout()방식&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;nbsp;특징&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;API 응답을 순회하며 뷰를 직접 생성해서 addView()로 붙이는 방식&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;LinearLayout이나 ConstraintLayout 안에서 뷰 조합&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;nbsp;장점&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구조가 단순하고 유연함 (서대로 뷰만 붙이면 됨)&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빠르게 화면 만들기 좋고, 레이아웃 순서를 동적으로 바꾸기도 쉬움&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;nbsp;단점&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;뷰 재사용이 안 되기 때문에 수가 많아지면 성능 저하&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;코드가 복잡해지기 쉬움 (뷰, 데이터, 로직이 뒤엉김)&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[layout]&lt;/span&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;&amp;lt;ScrollView
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:layout_width=&quot;match_parent&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:layout_height=&quot;match_parent&quot;&amp;gt;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;LinearLayout
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:id=&quot;@+id/container&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:orientation=&quot;vertical&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:layout_width=&quot;match_parent&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:layout_height=&quot;wrap_content&quot; /&amp;gt;
&amp;lt;/ScrollView&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[activity]&lt;/span&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class MainActivity : AppCompatActivity() {

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private lateinit var container: LinearLayout

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;override fun onCreate(savedInstanceState: Bundle?) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.onCreate(savedInstanceState)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setContentView(R.layout.activity_main)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;container = findViewById(R.id.container)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val titles = listOf(&quot;Title 1&quot;, &quot;Title 2&quot;, &quot;Title 3&quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;titles.forEach { title -&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val textView = TextView(this).apply {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;text = title
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;textSize = 20f
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setPadding(16, 16, 16, 16)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;container.addView(textView)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;RecyclerView + ViewType&amp;nbsp;방식&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;nbsp;특징&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다양한 UI 블록을 ViewType으로 분기해서 하나의 RecyclerView로 구성&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ViewHolder 기반으로 뷰 재사용 가능&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;nbsp;장점&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;성능 최고 (뷰 재사용, ViewHolder 풀링)&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;각 타입별로 클래스 분리 가능 → 유지보수, 테스트 용이&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구조화된 패턴으로 확장성 높음&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;nbsp;단점&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;초기 설계가 조금 번거롭다 (ViewType 정의, sealed class 설계..)&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[layout]&lt;/span&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;&amp;lt;androidx.recyclerview.widget.RecyclerView
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:id=&quot;@+id/recyclerView&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:layout_width=&quot;match_parent&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;android:layout_height=&quot;match_parent&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[adapter]&lt;/span&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;sealed class UiModel {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data class Title(val text: String) : UiModel()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data class Image(val url: String) : UiModel()
}

class MultiViewAdapter(private val items: List&amp;lt;UiModel&amp;gt;) : RecyclerView.Adapter&amp;lt;RecyclerView.ViewHolder&amp;gt;() {

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;override fun getItemViewType(position: Int): Int {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return when (items[position]) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is UiModel.Title -&amp;gt; 0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is UiModel.Image -&amp;gt; 1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return when (viewType) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;0 -&amp;gt; TitleViewHolder(LayoutInflater.from(parent.context).inflate(android.R.layout.simple_list_item_1, parent, false))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else -&amp;gt; ImageViewHolder(ImageView(parent.context))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;override fun getItemCount() = items.size

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;when (val item = items[position]) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is UiModel.Title -&amp;gt; (holder as TitleViewHolder).bind(item.text)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is UiModel.Image -&amp;gt; (holder as ImageViewHolder).bind(item.url)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;class TitleViewHolder(view: View) : RecyclerView.ViewHolder(view) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun bind(text: String) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(itemView as TextView).text = text
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;class ImageViewHolder(private val imageView: ImageView) : RecyclerView.ViewHolder(imageView) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun bind(url: String) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;imageView.setImageResource(android.R.drawable.ic_menu_gallery)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[activity]&lt;/span&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;&amp;nbsp;&amp;nbsp;val recyclerView: RecyclerView = findViewById(R.id.recyclerView)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val items = listOf(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UiModel.Title(&quot;Hello&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UiModel.Image(&quot;https://example.com/image1.jpg&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UiModel.Title(&quot;World&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UiModel.Image(&quot;https://example.com/image2.jpg&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;recyclerView.layoutManager = LinearLayoutManager(this)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;recyclerView.adapter = MultiViewAdapter(items)&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;각각 3가지 타입이 있는데,&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;기능 단위로 분리하고 싶고, 각각 UI가 독립적이면&lt;/b&gt; → FragmentContainerView&lt;/li&gt;&lt;li&gt;&lt;b&gt;뷰 몇 개 순서대로 붙이기만 하면 되면&lt;/b&gt; → addLayout()&lt;/li&gt;&lt;li&gt;&lt;b&gt;여러 UI 타입이 섞여 있고 리스트가 길어질 수 있으면&lt;/b&gt; → RecyclerView + ViewType&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 와중에 FragmentContainerView랑 recyclerview가 각각 언제 사용되면 좋은지 좀 헷갈렸는데,&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;이 블록이 미니 화면처럼 동작하고 독립적으로 동작해야 한다? : &amp;nbsp;FragmentContainerView&lt;br&gt;(ex. 영상 플레이어, 지도 등)&lt;/li&gt;&lt;li&gt;그냥 여러 UI를 스크롤되게 보여주고 성능도 중요하다? :&amp;nbsp;RecyclerView + ViewType&lt;br&gt;(ex. 홈피드, 뉴스 피드, 상품 리스트 등)&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;이렇게 생각하면 이해가 편하더라구요!&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Android</category>
      <category>addLayout</category>
      <category>android addlayout</category>
      <category>android Fragment</category>
      <category>android viewType</category>
      <category>FragmentContainerView</category>
      <category>recyclerview</category>
      <category>RecyclerView ViewType</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/166</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Android-%ED%95%98%EB%82%98%EC%9D%98-%ED%99%94%EB%A9%B4%EC%97%90-%EB%8B%A4%EC%96%91%ED%95%9C-UI-%EB%B8%94%EB%A1%9D%EC%9D%84-%EA%B5%AC%EC%84%B1%ED%95%98%EB%8A%94-3%EA%B0%80%EC%A7%80-%EB%B0%A9%EC%8B%9D#entry166comment</comments>
      <pubDate>Mon, 28 Jul 2025 21:55:26 +0900</pubDate>
    </item>
    <item>
      <title>[Android] NetworkInfo Deprecated</title>
      <link>https://ddusi-dod.tistory.com/entry/Android-NetworkInfo-Deprecated</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;과거 방식:&amp;nbsp;NetworkInfo&amp;nbsp;&amp;rarr; 이제는 ❌&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예전 사용법&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;code&gt;val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = cm.activeNetworkInfo
val isConnected = networkInfo?.isConnectedOrConnecting == true
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 이 방식은&amp;nbsp;API 29(Android 10)&amp;nbsp;부터 &lt;b&gt;deprecated&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단순히 연결 유무만 알 수 있고,&amp;nbsp;인터넷 실제 연결 상태는 확인 불가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ex) Wi-Fi는 연결되었지만 인터넷이 안 터지는 경우에도 true로 찍힘,,&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Android의&amp;nbsp;Doze Mode, App Standby, VPN, 제한 네트워크&amp;nbsp;등 새로운 개념을 반영 못 함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;rarr; 더 정확한&amp;nbsp;NetworkCapabilities&amp;nbsp;기반 방식으로 변경됨&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;NetworkCapabilities&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;u&gt;실제로 인터넷 연결 가능한지 확인&lt;/u&gt;&lt;/b&gt;&lt;/i&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;code&gt;fun isNetworkAvailable(context: Context): Boolean {
    val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    val network = connectivityManager.activeNetwork ?: return false
    val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
    return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;activeNetwork &amp;rarr; 현재 기기에서 실제로 연결되어 있는 네트워크를 의미함&lt;/li&gt;
&lt;li&gt;hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)&amp;rarr; 그중에서도 &quot;인터넷에 연결 가능한지&quot; 여부를 확인하는 메서드&lt;/li&gt;
&lt;li&gt;getNetworkCapabilities(network) &amp;rarr; 해당 네트워크가 지원하는 기능들(인터넷 가능 여부, Wi-Fi인지 등)을 확인 가능&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;네트워크 연결 변화 감지하는 방식 (callback 기반)&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 위에서의 기능은 함수를 실행하는 시점의 네트워크 연결 여부를 판단한다면,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 callback은 네트워크 변화를 추적할 수 있음&amp;nbsp; !! 다만 콜백 해제에 유의해야함 !&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val callback = object : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network: Network) {
        Log.d(&quot;Network&quot;, &quot;인터넷 사용가능&quot;)
    }

    override fun onLost(network: Network) {
        Log.d(&quot;Network&quot;, &quot;인터넷 끊김&quot;)
    }
}

val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val request = NetworkRequest.Builder().build()
cm.registerNetworkCallback(request, callback)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Android</category>
      <category>android connectivitymanager</category>
      <category>Android Network</category>
      <category>android network callback</category>
      <category>android network connectivity</category>
      <category>android network status</category>
      <category>android networkcallback</category>
      <category>android wifi</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/165</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Android-NetworkInfo-Deprecated#entry165comment</comments>
      <pubDate>Fri, 6 Jun 2025 12:31:10 +0900</pubDate>
    </item>
    <item>
      <title>입사 3개월차의 회고록</title>
      <link>https://ddusi-dod.tistory.com/entry/%EC%9E%85%EC%82%AC-3%EA%B0%9C%EC%9B%94%EC%B0%A8%EC%9D%98-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  어느덧 3개월차...?&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정말 정신없이 입사해서 여유가 하나도 없었는데, 어느덧 입사한 지 3개월..!&amp;nbsp;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 처음엔 모르겠는 것들 투성이였는데, &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제야 조금 적응해가고 있다는 느낌 ㅎㅅㅎ&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 한번,&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그동안의 기록을 남겨보려고 합니다 ✍️&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;266&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDHnjv/btsOlUwLhUp/QlLYwW5m6Zljt7bv3FCKk1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDHnjv/btsOlUwLhUp/QlLYwW5m6Zljt7bv3FCKk1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDHnjv/btsOlUwLhUp/QlLYwW5m6Zljt7bv3FCKk1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDHnjv%2FbtsOlUwLhUp%2FQlLYwW5m6Zljt7bv3FCKk1%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;266&quot; height=&quot;200&quot; data-origin-width=&quot;266&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  내가 새롭게 해본 것들&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;입사하고 나서 실무에서 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처음으로 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다뤄본 기술들이 많았어요 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;s&gt;역시 배움에 끝이란 없다..&amp;nbsp;&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jetpack Compose&amp;nbsp;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SDK 멀티모듈&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자동화 스크립트&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안드로이드 위젯&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;InstallReferrer&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Notification에 다양한 스타일 입혀보기&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하나하나 처음엔 어렵고 걱정이 너무 앞섰지만,&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;지금은 “아! 그건 내가 해봤어” 라고 말할 수 있을 정도 !?&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  →  ‍  으로 성장 중,,,,&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래도 처음이란 두려움보다 이제 그래 해보지 뭐! 라는 마인드가 되었다는게 너무나도 큰 수확같아요&amp;nbsp;&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  앞으로 나아갈 것들&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사실 예전엔 당연하다고 생각한 코드들,&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;막상 속을 들여다보면 성능적으로 손해 보는 구조일 때가 있어서 차근차근 메모를 남겨나가는 중입니다  &lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 코틀린&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구조 분해&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;고차 함수&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;람다&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이런 문법들은 꽤 손에 익었어요&amp;nbsp;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;나아가서 이젠 사용 전에 “왜 이렇게 써야 하지?” 를 생각하게 된 게 큰 변화 중 하나입니다..!&amp;nbsp;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 ..!&amp;nbsp;&amp;nbsp;같이 고민해주는 분들이 있는 게 정말 큰 안정감을 주어서 더 열심히 해야지 하는 원동력이 되는 거 같아요&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  스스로 아쉬웠던 점&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;u&gt;&lt;i&gt;일정 산출&lt;/i&gt;&lt;/u&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;... 그게 젤 어려워요 아직도 진행 중..&amp;nbsp;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;주체적으로 일정을 정해본 적이 드물기에, 이런 산출이 정말 어려웠습니다&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래도 팀장님의 도움하에 조금씩 산출해보고 있어요 좀 더 익으면 쉽게 쉽게 되겠죠,,?&amp;nbsp;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 또, 쓰고 있는 기술뿐만 아니라 다양한 기술들도 조금씩 접목해 볼 것..!!&amp;nbsp;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;너무 급하게 생각하지 말 것! ! !&amp;nbsp;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;☀️&amp;nbsp; 마무리&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3개월밖에 안 됐는데도 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정말 많은 걸 보고, 배우고, 느꼈습니다&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;익숙하지 않음을 견디다 보면, 어느 순간 내가 조금은 익숙해져 있는 게 느껴지더라구요&amp;nbsp;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;앞으로도&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;u&gt;&lt;i&gt;&lt;b&gt;“모른다 → 해본다 → 익숙해진다”&lt;/b&gt;&lt;/i&gt;&lt;/u&gt;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 흐름을 반복하며 쌓아갈 수 있으면 좋겠어요  &lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;역시 기록은 쌓이면 뭐든 됩니다 :)&amp;nbsp;&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;357&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mjvzb/btsOkMmAxLr/OOQFCrnWXg121qbTK1PyP1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mjvzb/btsOkMmAxLr/OOQFCrnWXg121qbTK1PyP1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mjvzb/btsOkMmAxLr/OOQFCrnWXg121qbTK1PyP1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/mjvzb/btsOkMmAxLr/OOQFCrnWXg121qbTK1PyP1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;357&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;357&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;</description>
      <category>개발자회고록</category>
      <category>기록이쌓이면뭐든된다</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/164</guid>
      <comments>https://ddusi-dod.tistory.com/entry/%EC%9E%85%EC%82%AC-3%EA%B0%9C%EC%9B%94%EC%B0%A8%EC%9D%98-%ED%9A%8C%EA%B3%A0%EB%A1%9D#entry164comment</comments>
      <pubDate>Sat, 31 May 2025 14:38:35 +0900</pubDate>
    </item>
    <item>
      <title>[Android] MVI 패턴, 이름을 왜이렇게 헷갈리게 지었어요?</title>
      <link>https://ddusi-dod.tistory.com/entry/Android-MVI-%ED%8C%A8%ED%84%B4-%EC%9D%B4%EB%A6%84%EC%9D%84-%EC%99%9C%EC%9D%B4%EB%A0%87%EA%B2%8C-%ED%97%B7%EA%B0%88%EB%A6%AC%EA%B2%8C-%EC%A7%80%EC%97%88%EC%96%B4%EC%9A%94</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  MVI란?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;MVI는 Model - View - Intent&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서 말하는 &lt;b&gt;Intent는 Android Intent가 아니라 !!!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  &amp;ldquo;사용자의 의도(Intent)부터 상태(State) 변경까지 이어지는 흐름 전체&amp;rdquo;를 뜻하는 개념적인 용어,,&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;color: #0e0e0e;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;❗️그러니까 MVI의 Intent &amp;ne; UIEvent &amp;ne; 안드로이드 Intent &amp;rarr; 절대 헷갈리면 안된다..&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;nbsp;MVI 흐름&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;gcode&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;[사용자 행동 (UIEvent)]
&amp;rarr; ViewModel이 받음
&amp;rarr; 상태(UIState) 변경 or 효과(UIEffect) 발생
&amp;rarr; View가 변화됨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이 흐름의&lt;/span&gt;&lt;span style=&quot;color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;출발점은 항상&lt;/b&gt;&lt;b&gt; UIEvent&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 화면은 항상&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;UIState 하나로만&lt;/b&gt;&lt;span&gt;&amp;nbsp;관리&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  자주 사용되는 타입들&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  UIState&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;현재 화면의 상태 (ex. 키워드, 로딩 중, 결과 등)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;data class&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  UIEvent&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자 액션 (ex. 버튼 클릭, 키 입력 등)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;sealed class&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⚡ UIEffect&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Toast, 화면 이동 등 일회성 처리&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;sealed class&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MVI의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;구성요소&lt;/b&gt;는 아니고, MVI를&lt;span&gt;&amp;nbsp;&lt;/span&gt;Android에서 구현할 때 자주 등장하는 타입들&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  왜 타입이 다를까?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;-&amp;nbsp; UIState &amp;rarr;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;data class&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;현재 화면의 상태를 나타내는 데이터 덩어리 라서 &amp;rarr; data class로 관리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일부만 바꿀 땐 copy()로 간편하게 처리 가능&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;val newState = oldState.copy(keyword = &quot;아몬드&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 하면 상태도 immutable하게 유지되고, Compose에서 recomposition도 정확히 됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;- UIEvent, UIEffect &amp;rarr;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;sealed class&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자 액션이나, 그에 따른 동작이 다양하니 &amp;rarr; sealed class로 묶어 관리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;sealed class BookSearchEvent {
    data class SearchKeywordChanged(val keyword: String) : BookSearchEvent()
    object SearchButtonClicked : BookSearchEvent()
    data class BookClicked(val bookId: String) : BookSearchEvent()
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;sealed class BookSearchEffect {
    data class ShowToast(val message: String) : BookSearchEffect()
    data class NavigateToDetail(val bookId: String) : BookSearchEffect()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;✅&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MVI는 구조이고, Intent는 흐름 전체를 의미함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제 구현할 때는 이 3가지 단위를 다룸:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;UIEvent&lt;/b&gt; &amp;rarr; 사용자 입력&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;UIState&lt;/b&gt; &amp;rarr; 화면 상태&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;UIEffect&lt;/b&gt; &amp;rarr; 일회성 작업 (토스트 같은..)&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;UIEvent에서 시작해서 ViewModel이 처리 &amp;rarr; State or Effect로 변환 &amp;rarr; View 변화&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;예시 코드&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;// UIEvent
sealed class BookSearchEvent {
    data class SearchKeywordChanged(val keyword: String) : BookSearchEvent()
    object SearchButtonClicked : BookSearchEvent()
    data class BookClicked(val bookId: String) : BookSearchEvent()
}

// UIState
data class BookSearchState(
    val keyword: String = &quot;&quot;,
    val isLoading: Boolean = false,
    val results: List&amp;lt;Book&amp;gt; = emptyList(),
    val errorMessage: String? = null
)

// UIEffect
sealed class BookSearchEffect {
    data class ShowToast(val message: String) : BookSearchEffect()
    data class NavigateToDetail(val bookId: String) : BookSearchEffect()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[viewModel]&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1748312631350&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@HiltViewModel
class BookSearchViewModel @Inject constructor() : ViewModel() {

    private val _uiState = MutableStateFlow(BookSearchState())
    val uiState: StateFlow&amp;lt;BookSearchState&amp;gt; = _uiState

    private val _effect = MutableSharedFlow&amp;lt;BookSearchEffect&amp;gt;()
    val effect = _effect.asSharedFlow()

    fun onEvent(event: BookSearchEvent) {
        when (event) {
            is BookSearchEvent.SearchKeywordChanged -&amp;gt; {
                _uiState.update { it.copy(keyword = event.keyword) }
            }

            BookSearchEvent.SearchButtonClicked -&amp;gt; {
                _uiState.update { it.copy(isLoading = true) }

                // 가짜 API 호출 예시
                viewModelScope.launch {
                    delay(1000)
                    val results = getFakeBooks(event = _uiState.value.keyword)
                    _uiState.update { it.copy(isLoading = false, results = results) }

                    if (results.isEmpty()) {
                        _effect.emit(BookSearchEffect.ShowToast(&quot;검색 결과가 없습니다&quot;))
                    }
                }
            }

            is BookSearchEvent.BookClicked -&amp;gt; {
                viewModelScope.launch {
                    _effect.emit(BookSearchEffect.NavigateToDetail(event.bookId))
                }
            }
        }
    }

    private fun getFakeBooks(event: String): List&amp;lt;Book&amp;gt; {
        return if (event.contains(&quot;아몬드&quot;)) listOf(Book(&quot;1&quot;, &quot;아몬드&quot;)) else emptyList()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[Screen]&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1748312645798&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun BookSearchScreen(
    navController: NavController,
    viewModel: BookSearchViewModel = hiltViewModel()
) {
    val uiState by viewModel.uiState.collectAsState()
    val context = LocalContext.current

    // 상태 반영
    Column(modifier = Modifier.padding(16.dp)) {
        TextField(
            value = uiState.keyword,
            onValueChange = {
                viewModel.onEvent(BookSearchEvent.SearchKeywordChanged(it))
            },
            label = { Text(&quot;검색어 입력&quot;) }
        )

        Spacer(modifier = Modifier.height(8.dp))

        Button(onClick = {
            viewModel.onEvent(BookSearchEvent.SearchButtonClicked)
        }) {
            Text(&quot;검색&quot;)
        }

        Spacer(modifier = Modifier.height(16.dp))

        if (uiState.isLoading) {
            CircularProgressIndicator()
        } else {
            LazyColumn {
                items(uiState.results) { book -&amp;gt;
                    Text(
                        text = book.title,
                        modifier = Modifier
                            .fillMaxWidth()
                            .clickable {
                                viewModel.onEvent(BookSearchEvent.BookClicked(book.id))
                            }
                            .padding(8.dp)
                    )
                }
            }
        }
    }

    // 일회성 Effect 처리
    LaunchedEffect(Unit) {
        viewModel.effect.collect { effect -&amp;gt;
            when (effect) {
                is BookSearchEffect.ShowToast -&amp;gt; {
                    Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show()
                }

                is BookSearchEffect.NavigateToDetail -&amp;gt; {
                    navController.navigate(&quot;detail/${effect.bookId}&quot;)
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Android</category>
      <category>Android</category>
      <category>android compose</category>
      <category>android mvi</category>
      <category>android mvi pattern</category>
      <category>mvi compose</category>
      <category>mvi pattern</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/163</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Android-MVI-%ED%8C%A8%ED%84%B4-%EC%9D%B4%EB%A6%84%EC%9D%84-%EC%99%9C%EC%9D%B4%EB%A0%87%EA%B2%8C-%ED%97%B7%EA%B0%88%EB%A6%AC%EA%B2%8C-%EC%A7%80%EC%97%88%EC%96%B4%EC%9A%94#entry163comment</comments>
      <pubDate>Tue, 27 May 2025 21:43:34 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] XML처럼 TextField 쓰면 Compose에선 망해요</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-XML%EC%B2%98%EB%9F%BC-TextField-%EC%93%B0%EB%A9%B4-Compose%EC%97%90%EC%84%A0-%EB%A7%9D%ED%95%B4%EC%9A%94</link>
      <description>&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Compose에서 TextField로 입력할 때, 글자 하나 칠 때마다 뭔가 버벅임을 느꼈습니다..&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처음 Compose로 전환했을 때&amp;nbsp; XML에서 하던 습관대로&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;code&gt;OutlinedTextField(
    value = text,
    onValueChange = {
        text = it
        viewModel.search(it) // ❌ 글자 칠 때마다 API 호출
    }
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 작성했는데&amp;hellip; 텍스트 입력할 때마다&amp;nbsp; 밀리는 느낌?,, 스크롤과 겹치면 거의 끊기는 수준까지 가더라고요.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원인은&amp;nbsp;&lt;b&gt;recomposition....&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Compose는 상태가 바뀌면 해당 Composable이 다시 그려지니, &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;TextField도 마찬가지였습니다,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;근데 onValueChange에서 상태 변경 + API 호출까지 동시에 하니 ☠️&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;입력 &amp;rarr; 상태 변경 &amp;rarr; recomposition&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;recomposition 중&amp;nbsp;launch,&amp;nbsp;joinToString,&amp;nbsp;format&amp;nbsp;등 무거운 연산 있음 &amp;rarr;&amp;nbsp;스크롤 버벅임&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;XML 기반 View에선 이런 식으로 해도 이벤트만 전달되기 때문에 이런 문제가 안 드러났어서 모르고 있었지만&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;, &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Compose에선 진짜 망하는 길이더라구요..&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;XML vs Compose&amp;nbsp;&lt;/span&gt;&lt;/h2&gt;
&lt;table style=&quot;color: #000000; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;상태 변화&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;내부에서 직접 보유 (EditText 자체에 있음)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;외부에서 상태를 전달 (state)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이벤트 처리 방식&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;텍스트 변경 이벤트만 따로 발생&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;value가 바뀌면&amp;nbsp;&lt;b&gt;recomposition 전체 발생&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;API 호출 위치&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;TextWatcher에서 직접 호출 가능 (re-draw 영향 없음)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;onValueChange에서 호출하면&amp;nbsp;UI 전체 다시 그림&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;렌더링 방식&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OS 뷰 시스템이 직접 그림 (뷰 재사용 가능)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;선언형 구조라&amp;nbsp;매번 다시 그릴 수 있도록 설계됨&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;성능 최적화 필요성&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;뷰 재사용 구조라 성능 최적화 덜 민감함&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;recomposition 민감 &amp;rarr;&amp;nbsp;조금만 무거워도 랙 발생&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해결 방법: 입력은 입력만,&amp;nbsp; 로직은 분리&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;stylus&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;code&gt;val input = remember { mutableStateOf(&quot;&quot;) }

OutlinedTextField(
    value = input.value,
    onValueChange = { input.value = it }
)

LaunchedEffect(Unit) {
    snapshotFlow { input.value }
        .debounce(300) // 입력 끝나고 300ms 기다림
        .filter { it.length &amp;gt;= 2 }
        .collectLatest {
            viewModel.search(it)
        }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이런 식으로 입력과 로직을 분리시켰어요&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;snapshotFlow로 Compose 상태를 Flow처럼 다룸&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;debounce로 중복 호출 방지&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제 검색 로직은 입력과 완전히 분리됨 &amp;rarr; 스크롤 개선&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Compose TextInput 사용 시 고려 사항&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅&amp;nbsp;onValueChange엔 상태 변경만 &amp;mdash; 무거운 로직은 절대 ❌&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ 포맷팅, 스타일 계산 등은&amp;nbsp;remember {}&amp;nbsp;안에서&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ recomposition이 자주 일어나는 부분은 가능한 단순하게&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;XML 습관 그대로 Compose에서 쓰면 성능이 무너지는 대표적인 예가 바로 TextField입니다..&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Compose에선 &quot;입력은 입력만 처리하고, 로직은 나중에 처리&quot;가 진짜 진짜 중요하더라구요&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;recomposition에 대해 글을 여러 번 적지만, 매번 겪어보지 않고 고려하기가 어렵네요..  &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;필요하다면 기존 LazyColumn 성능 튜닝과도 연결해서 글 이어보세요&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://ddusi-dod.tistory.com/entry/Compose-LazyColumn-%EC%84%B1%EB%8A%A5%EC%9D%84-%EC%9C%84%ED%95%B4&quot;&gt;  LazyColumn 성능 정리한 글&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>  Compose</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/162</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-XML%EC%B2%98%EB%9F%BC-TextField-%EC%93%B0%EB%A9%B4-Compose%EC%97%90%EC%84%A0-%EB%A7%9D%ED%95%B4%EC%9A%94#entry162comment</comments>
      <pubDate>Fri, 16 May 2025 22:38:12 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] LazyColumn 성능을 위해</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-LazyColumn-%EC%84%B1%EB%8A%A5%EC%9D%84-%EC%9C%84%ED%95%B4</link>
      <description>&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;✅ LazyColumn이란?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Compose에서 RecyclerView를 대체하는 스크롤 가능한 Composable&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 내부적으로 보이는 항목만 그림 (지연 로딩)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 데이터가 많아질수록 Recomposition에 주의&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용하면서 느낀 성능 관련 주의점&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;key 설정 필요&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Recomposition의 기준 설정&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1747125689285&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LazyColumn {
    items(list) { item -&amp;gt;
        Text(item.name)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;key를 설정하지 않으면 &amp;rarr; Compose는 &lt;u&gt;index를 기준으로 recomposition 설정&lt;/u&gt;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약, 리스트에서 요소가 삽입, 삭제되면 &amp;rarr; 순서 바뀌면서&amp;nbsp;&lt;b&gt;다시 그려짐&lt;/b&gt; (☠️ 성능 낭비)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ 해결 방법:&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747125819753&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LazyColumn {
    items(list, key = { it.id }) { item -&amp;gt;
        Text(item.name)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;key를 고유한 값으로 설정 -&amp;gt;&amp;nbsp;항목 자체의 고유 값으로 비교 &amp;rarr; 순서가 바뀌어도 &lt;b&gt;기존 UI를 재사용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;LazyColumn안의&amp;nbsp;&lt;/span&gt;forEach&lt;/b&gt;&lt;b&gt;는 Compose가 추적 불가능&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;-LazyColumn은 기본적으로 &lt;b&gt;보이는 항목만 그려주는 지연 렌더링(Lazy Rendering)&lt;/b&gt; 구조&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;근데 forEach로 직접 Composable을 호출하면, Compose는 그걸 Lazy 항목으로 인식하지 않고 일반적인 Composable 호출로 처리 -&amp;gt; 즉 forEach 항목을 매번 새로 그림 -&amp;gt; Lazy가 의미가 없어짐&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747126194835&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LazyColumn {
    // ❌ 이렇게 하면 LazyColumn의 역할이 의미 X
    list.forEach { item -&amp;gt;
        Text(item.name)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;✅ 해결 방법: items { } 사용&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;- 내부적으로&amp;nbsp;&lt;/span&gt;LazyListItem으로 관리 &amp;rarr; 리스트 스크롤 시 화면에 보여야 할 항목만 렌더링&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747126353463&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val userList = listOf(
    User(&quot;방&quot;, 33),
    User(&quot;케&quot;, 31),
    User(&quot;필&quot;, 31),
    User(&quot;돈&quot;, 29),
)

LazyColumn {
    items(userList, key = { it.name }) { user -&amp;gt;
        Text(
            text = &quot;${user.name}, 나이: ${user.age}&quot;,
            modifier = Modifier.padding(16.dp)
        )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;항상 `items {}`를 쓰고, `key`를 설정하자 &amp;rarr; 그래야 Compose가 항목을 재사용하고 성능이 떨어지지 않는다..!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>android compose</category>
      <category>compose</category>
      <category>compose column</category>
      <category>compose lazy</category>
      <category>compose lazycolumn</category>
      <category>LazyColumn</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/161</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-LazyColumn-%EC%84%B1%EB%8A%A5%EC%9D%84-%EC%9C%84%ED%95%B4#entry161comment</comments>
      <pubDate>Tue, 13 May 2025 21:54:51 +0900</pubDate>
    </item>
    <item>
      <title>[Android / Compose] Compose Context</title>
      <link>https://ddusi-dod.tistory.com/entry/Android-Compose-Compose-Context</link>
      <description>&lt;p data-end=&quot;374&quot; data-start=&quot;330&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;까먹기 전에 줍줍..&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;374&quot; data-start=&quot;330&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  XML vs Compose의 &quot;생명주기 + context&quot; 구조 차이&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;389&quot; data-start=&quot;376&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;XML 기반&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;552&quot; data-start=&quot;390&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;427&quot; data-start=&quot;390&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Activity나 Fragment가 View를 직접 소유&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;475&quot; data-start=&quot;428&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;View는 Activity가 죽을 때&amp;nbsp; context도 같이 정리됨 &amp;rarr; context를 View 내부에서 써도 큰 문제가 없다&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;552&quot; data-start=&quot;519&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, UI는 생명주기를 따라감&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;698&quot; data-start=&quot;681&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;Compose 기반&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;871&quot; data-start=&quot;699&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;726&quot; data-start=&quot;699&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;UI 자체가 &quot;함수로 구성된 상태 기반 UI&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;773&quot; data-start=&quot;727&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;상태에 따라 UI가 수시로 재구성(recomposition) 되기에&amp;nbsp;Activity와 느슨하게 연결됨&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;871&quot; data-start=&quot;809&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 UI 함수 내에서 context를 막 쓰면, &lt;u&gt;&lt;b&gt;그 시점에 context가 유효하지 않을 수 있음&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-end=&quot;1385&quot; data-start=&quot;1219&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p data-end=&quot;1385&quot; data-start=&quot;1221&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;XML :&amp;nbsp; context가 항상 Activity와 뷰 안에 있고, 생명주기를 같이 가져가니까 자유롭게 써도 괜찮았지만,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Compose : UI 함수들이 Activity 생명주기와 독립적으로 돌아가기도 하니까, context를 직접 관리하지 않으면 위험&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-end=&quot;1418&quot; data-start=&quot;1392&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;✅ 그래서 context는 명시적으로 전달해야 한다!&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1746694043639&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MyComposable(context: Context) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;:&amp;nbsp; 이 함수가 context가 꼭 필요한 걸 &lt;b&gt;명확히 드러낼 수 있음&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;호출 시점에서도 context를 보장된 상태에서 넘기게 되니까 &amp;rarr; 버그 줄고, 메모리 릭 위험도 줄고, 테스트도 쉬워짐&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;⚠️&amp;nbsp; 잊지말자&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Compose는 함수형 UI는 예측 불가능한 시점에 다시 실행될 수 있다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들어 context를 갖고 있던 composable 함수가 ViewModel의 상태 변화로 인해 다시 실행되었는데 &lt;span style=&quot;text-align: start;&quot;&gt;&amp;rarr;&amp;nbsp;&lt;/span&gt; 앱이 크래시 나거나, 이상한 동작 발생&amp;nbsp; ,,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;compose하다보면 Recomposition이 굉장히 중요한데, 이렇게 context같은 부분을 빼먹지않도록 유념해야한다,,&amp;nbsp;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>Android</category>
      <category>android compose</category>
      <category>Android Context</category>
      <category>compose</category>
      <category>compose context</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/160</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Android-Compose-Compose-Context#entry160comment</comments>
      <pubDate>Sun, 11 May 2025 14:52:39 +0900</pubDate>
    </item>
    <item>
      <title>[Android] Widget 사용해보기</title>
      <link>https://ddusi-dod.tistory.com/entry/Android-Widget-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
      <description>&lt;p data-end=&quot;128&quot; data-start=&quot;102&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;위젯을 처음 사용해봤어서, 언젠가 쓸 날을 위해 정리해보도록 하겠습니다&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;128&quot; data-start=&quot;102&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;&lt;b&gt;Widget&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;- 홈 화면에 배치 하는 뷰 컴포넌트 (RemoteView 기반)&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;- RemoteView이기에 일반 View처럼 &lt;u&gt;직접 조작 불가&lt;/u&gt;&amp;nbsp; 즉, 동적 뷰 생성이 불가하기 때문에 RecyclerView, ListView, Glide등이 사용 불가&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-end=&quot;477&quot; data-start=&quot;454&quot;&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h3 data-end=&quot;477&quot; data-start=&quot;454&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;&lt;b&gt;⚙️ Widget의 필수 구성 요소&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;/span&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000; text-align: start;&quot;&gt;AppWidgetProvider - &lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; text-align: start;&quot;&gt;위젯 생명주기 관리&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000; text-align: start;&quot;&gt;widget_info.xml - &lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; text-align: start;&quot;&gt;위젯 크기, 초기 레이아웃, 업데이트 주기, configureActivity 설정&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000; text-align: start;&quot;&gt;RemoteViews - &lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; text-align: start;&quot;&gt;위젯 UI를 위한 뷰 생성 도구, 동적 뷰는 생성 불가능&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000; text-align: start;&quot;&gt;ConfigureActivity - 위젯 추가 시 보여지는 Activity (widget_info.xml에서 선언한 경우만 보여짐)&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-end=&quot;839&quot; data-start=&quot;836&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;864&quot; data-start=&quot;841&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;위젯 동작 전체 흐름&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1️⃣ &lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;위젯은 widget_info.xml, AppWidgetProvider, RemoteViews로 구성&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2️⃣ 위젯&amp;nbsp;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;추가 시 ConfigureActivity가 있으면 설정 화면(ConfigureActivity로 선언한 화면)으로 진입&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3️⃣ 설&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; text-align: left;&quot;&gt;정 완료 시 setResult(RESULT_OK)로 위젯 ID 등록&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4️⃣ 시&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; text-align: left;&quot;&gt;스템이 onUpdate() 호출 &amp;rarr; RemoteViews로 위젯 UI 구성&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5️⃣ &lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;AppWidgetManager.updateAppWidget()으로 실제 위젯 업데이트&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전체 코드&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;widget ui&lt;/h4&gt;
&lt;pre id=&quot;code_1745554678660&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;LinearLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:id=&quot;@+id/widget_container&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    android:background=&quot;#FFFFFF&quot;
    android:orientation=&quot;vertical&quot;
    android:gravity=&quot;center&quot;&amp;gt;

    &amp;lt;TextView
        android:id=&quot;@+id/widget_text&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;Hello Widget!&quot;
        android:textSize=&quot;18sp&quot;
        android:textColor=&quot;#000000&quot; /&amp;gt;
&amp;lt;/LinearLayout&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;widget_info&lt;/h4&gt;
&lt;pre id=&quot;code_1745554693976&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;appwidget-provider xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:minWidth=&quot;180dp&quot;
    android:minHeight=&quot;60dp&quot;
    android:updatePeriodMillis=&quot;86400000&quot; //업데이트 주기
    android:initialLayout=&quot;@layout/widget_ui&quot; //사용할 위젯 ui
    android:resizeMode=&quot;horizontal|vertical&quot;
    android:widgetCategory=&quot;home_screen&quot;  // 홈화면에서 사용할지, 잠금화면에서 사용할지
    /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;widget provider&lt;/h4&gt;
&lt;pre id=&quot;code_1745554724776&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SimpleTextWidgetProvider : AppWidgetProvider() {

    override fun onUpdate(
        context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray
    ) {
        for (appWidgetId in appWidgetIds) {
            // RemoteViews 생성
            val views = RemoteViews(context.packageName, R.layout.widget_simple)

            // 텍스트 동적으로 변경 가능 (필요 시)
            views.setTextViewText(R.id.widget_text, &quot;Hello Widget!&quot;)

            // 위젯 업데이트
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;manifest&lt;/h4&gt;
&lt;pre id=&quot;code_1745554742025&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;receiver android:name=&quot;.SimpleTextWidgetProvider&quot; &amp;gt;
    &amp;lt;intent-filter&amp;gt;
        &amp;lt;action android:name=&quot;android.appwidget.action.APPWIDGET_UPDATE&quot; /&amp;gt;
    &amp;lt;/intent-filter&amp;gt;

    &amp;lt;meta-data
        android:name=&quot;android.appwidget.provider&quot;
        android:resource=&quot;@xml/simple_widget_info&quot; /&amp;gt;
&amp;lt;/receiver&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-end=&quot;1133&quot; data-start=&quot;1130&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1168&quot; data-start=&quot;1135&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;  &lt;b&gt;onEnabled()&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;호출 시점&lt;/b&gt;: 위젯이 홈 화면에 &amp;lsquo;처음&amp;rsquo; 추가될 때 &lt;b&gt;&lt;u&gt;딱 한 번 호출&lt;/u&gt;&lt;/b&gt;됨.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;주요 용도&lt;/b&gt;: 시스템이 onUpdate()를 누락하거나 앱이 재실행될 때 강제 업데이트 trigger&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt; &amp;nbsp;&lt;/span&gt;&lt;i&gt;앱이 다시 실행돼도 시스템이 onUpdate()를 보장하지 않기 때문에 &amp;rarr; onEnabled()나 앱 onCreate() 시점에서 수동 갱신 권장&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;사용 방식&lt;/b&gt;: AppWidgetManager.getAppWidgetIds()로 위젯 ID를 가져와 ACTION_APPWIDGET_UPDATE 브로드캐스트 전송.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;주의사항&lt;/b&gt;: UI 업데이트는 여기서 직접 XXX&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1594&quot; data-start=&quot;1475&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-end=&quot;1599&quot; data-start=&quot;1596&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1623&quot; data-start=&quot;1601&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt; &amp;nbsp; 알아두면 좋을&amp;nbsp;팁&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;ConfigureActivity가 안 뜰 때 : &lt;/b&gt;info.xml에 선언되어 있는지 확인 필요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;미리보기 이미지 등록&lt;/b&gt;: AndroidManifest.xml에서 &amp;lt;meta-data&amp;gt;로 android.appwidget.preview 등록&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;RemoteViews 제약&lt;/b&gt;: &amp;nbsp;setTextViewText, setImageViewBitmap 등 제한된 API만 가능(동적 뷰는 XX)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;주기적 갱신&lt;/b&gt;: AlarmManager, WorkManager 등을 활용해 수동 트리거. 단, 실시간 갱신은 비추천.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;2111&quot; data-start=&quot;2108&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;2132&quot; data-start=&quot;2113&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;❓ 테스트하며 들었던 의문들.,&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;2170&quot; data-start=&quot;2134&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Q. Glide로 불러온 이미지를 위젯에 넣을 수 있을까?&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-end=&quot;2224&quot; data-start=&quot;2172&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;✅ &lt;/span&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Glide에서 비트맵으로 만든 후 setImageViewBitmap()으로 넣으면 가능&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1745554371995&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Glide.with(context) 
	.asBitmap()
    .circleCrop() 
    .load(imageUrl) 
    .into(object : CustomTarget&amp;lt;Bitmap&amp;gt;() 
    { 
    	override fun onResourceReady(resource: Bitmap, transition: Transition&amp;lt;in Bitmap&amp;gt;?) 
        { 
        	views.setImageViewBitmap(R.id.widget_iv, resource) 
        }
    })&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-end=&quot;2535&quot; data-start=&quot;2532&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-end=&quot;2580&quot; data-start=&quot;2537&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Q. configureActivity가 한 번 안 뜨면 계속 안 뜰 때?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-end=&quot;2684&quot; data-start=&quot;2582&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;왜냐하면 시스템이 &lt;b&gt;해당 위젯 ID는 이미 설정 완료된 것으로 간주 &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;rarr; setResult(RESULT_OK) 또는 RESULT_CANCELED를 명확히 호출해야 재설정 가능&lt;/span&gt;&lt;/p&gt;
&lt;hr data-end=&quot;2689&quot; data-start=&quot;2686&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-end=&quot;2717&quot; data-start=&quot;2691&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-end=&quot;2717&quot; data-start=&quot;2691&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Q. 앱 재설치하면&amp;nbsp; 다시 잘 뜨는 이유?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-end=&quot;2802&quot; data-start=&quot;2719&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;✅ 시스템은 위젯 상태를 &lt;b&gt;앱 패키지 단위로 캐싱&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;2802&quot; data-start=&quot;2719&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;:&amp;nbsp; 앱을 삭제하면 위젯 관련 정보도 전부 삭제됨 &amp;rarr; 새 앱처럼 동작 &amp;rarr; 설정 화면 다시 뜸&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;2802&quot; data-start=&quot;2719&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-end=&quot;2807&quot; data-start=&quot;2804&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-end=&quot;2838&quot; data-start=&quot;2809&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-end=&quot;2838&quot; data-start=&quot;2809&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Q. 위젯 크기가 실제보다 너무 클 때&lt;/span&gt;&lt;/h4&gt;
&lt;p data-end=&quot;2904&quot; data-start=&quot;2840&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;✅ res/xml/widget_info.xml의 minHeight, minWidth 값을 조정해야 함(grid 단위도 있으니 확인 필요)&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;2904&quot; data-start=&quot;2840&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-end=&quot;3087&quot; data-start=&quot;3084&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-end=&quot;3122&quot; data-start=&quot;3089&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;✅&amp;nbsp; 위젯 미리보기 이미지는 metat-data에 등록필수&lt;/span&gt;&lt;/h4&gt;
&lt;p data-end=&quot;3166&quot; data-start=&quot;3124&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;AndroidManifest.xml에서 &amp;lt;meta-data&amp;gt; 등록&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1745554516979&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;meta-data 
android:name=&quot;android.appwidget.preview&quot; 
android:resource=&quot;@drawable/preview_image&quot; /&amp;gt; 

&amp;lt;meta-data android:name=&quot;android.appwidget.description&quot; 
android:value=&quot;@string/widget_description&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>android widget</category>
      <category>android widget manifest</category>
      <category>android widget preview</category>
      <category>android widget ui</category>
      <category>Widget</category>
      <category>widgetprovider</category>
      <category>안드로이드 위젯</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/159</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Android-Widget-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0#entry159comment</comments>
      <pubDate>Sat, 26 Apr 2025 15:21:21 +0900</pubDate>
    </item>
    <item>
      <title>[Android] WorkManager</title>
      <link>https://ddusi-dod.tistory.com/entry/Android-WorkManager</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;WorkManager는 처음 사용해봐서 정리해두려고 함,,  &lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;✅ WorkManager란?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Android Jetpack의 &lt;i&gt;&lt;b&gt;백그라운드 작업&lt;/b&gt;&lt;/i&gt; 처리 라이브러리&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;네트워크 연결 상태, 충전 중 여부 등 다양한 제약 조건 설정 가능&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; 언제 사용하면 좋을까 ?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 작업이 백그라운드에서도 꼭 실행돼야 할 때 (ex. 업로드 같은 동작)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 네트워크 활성화 / 충전 중 등 조건을 걸고 싶을 때&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 주기적으로 실행되어야 할 때 (ex. 하루에 한 번 데이터 전송 예약)&amp;nbsp;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  WorkManager 구성요소&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Worker&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 실제 작업을 정의한 클래스 즉, doWork() 구현&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 작업 방식(동기 / 코루틴 / RxJava)에 따라 Worker는 Worker, CoroutineWorker, RxWorker 3가지 타입으로 나뉨&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1744794315365&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyWorker(
    context: Context,
    workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {

    override suspend fun doWork(): Result {
        val input = inputData.getString(&quot;key&quot;)
        
        // TODO: 비동기 작업 수행 (예: 네트워크 요청, 파일 저장 등)
        return Result.success()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;WorkRequest&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 어떤 작업을 어떻게 실행할지 방법을 정의&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 한 번만 실행 &amp;rarr; OneTimeWorkRequestBuilder&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;nbsp;정기적 실행 &amp;rarr; PeriodicWorkRequestBuilder&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744794390684&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val work = OneTimeWorkRequestBuilder&amp;lt;MyWorker&amp;gt;()
    .setInputData(workDataOf(&quot;key&quot; to &quot;odense&quot;))
    .build()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;WorkManager&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 작업을 큐에 넣고 실행하는 핵심 요소&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744794436732&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WorkManager.getInstance(context).enqueue(work)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Constraints (optional)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 제약 조건 설정&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;219&quot; data-start=&quot;195&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;와이파이 연결됐을 때만 작업하고 싶을 때&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;243&quot; data-start=&quot;220&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;충전 중일 때만 실행되게 하고 싶을 때&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;272&quot; data-start=&quot;244&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기기가 *유휴 상태일 때만 실행되게 하고 싶을 때&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유휴 상태가 필요하나 싶었는데 예를 보니까 필요할 때도 있을 거 같다,,&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자가 기기를 안 만지는 상태 (잠김, 화면 꺼짐 등), 백그라운드에서 시스템이 쉬는 상태&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744793942120&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val constraints = Constraints.Builder()
    .setRequiresCharging(true)
    .setRequiredNetworkType(NetworkType.UNMETERED)
    .setRequiresDeviceIdle(true)
    .build()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉 간단하게 줄이자면&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Worker&lt;/b&gt; : 할 일 정의&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;WorkRequest&lt;/b&gt; : 할 일 요청&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Constraints&lt;/b&gt; : 특정 조건을 걸지 말지&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;WorkManager&lt;/b&gt; : 실행 담당&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;전체 코드&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class MyWorker(
    context: Context,
    workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {

    override suspend fun doWork(): Result {
        val value = inputData.getString(&quot;key&quot;)
        Log.d(&quot;MyWorker&quot;, &quot;값 받음: $value&quot;)

        // TODO: 백그라운드에서 수행할 작업
        return Result.success()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1744795181342&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 보통 Activity, ViewModel 등에 작성됨
val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.UNMETERED)
    .setRequiresCharging(true)
    .build()

val work = OneTimeWorkRequestBuilder&amp;lt;MyWorker&amp;gt;()
    .setInputData(workDataOf(&quot;key&quot; to &quot;odense&quot;))
    .setConstraints(constraints)
    .build()

WorkManager.getInstance(context).enqueue(work)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어떤 파일에 어떤 구성 요소가 들어가야하는지 헷갈릴 수 있다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구조를 보자면 아래와 같다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  project&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;├──&amp;nbsp; &amp;nbsp;work&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;MyWorker.kt&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;larr; Worker 클래스 정의&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;├──   ui 관련&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;MainActivity.kt&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;larr; WorkerRequest, WorkerManager, constraints&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;백그라운드 작업을 많이 안 해봐서 접근이 어려웠지만 꼭 써먹어봐야지 ㅎㅅㅎ&lt;/p&gt;</description>
      <category>  Android</category>
      <category>android worker</category>
      <category>android worker structure</category>
      <category>Android WorkManager</category>
      <category>coroutineworker</category>
      <category>WorkManager</category>
      <category>WorkRequest</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/158</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Android-WorkManager#entry158comment</comments>
      <pubDate>Fri, 18 Apr 2025 23:42:29 +0900</pubDate>
    </item>
    <item>
      <title>[Android] 클린 아키텍처 적용 시 고민했던 3가지 의문점</title>
      <link>https://ddusi-dod.tistory.com/entry/Android-%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%A0%81%EC%9A%A9-%EC%8B%9C-%EA%B3%A0%EB%AF%BC%ED%96%88%EB%8D%98-3%EA%B0%80%EC%A7%80-%EC%9D%98%EB%AC%B8%EC%A0%90</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;새 프로젝트를 시작하며 새로운 디자인 패턴(MVI)를 적용하려고 하다보니, 클린 아키텍처에 대한 고심?이 필요했습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;들었던 의문점들을 정리해보았어요  &lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  Q1. Repository는 화면을 따라가야 할까? 데이터 주체를 따라가야 할까?&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;  A. Repository는 무조건 &lt;i&gt;&amp;ldquo;데이터 주체&amp;rdquo; 기준&lt;/i&gt;으로 네이밍해야 함!&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;❌ MainRepository, BookScreenRepository&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ BookRepository, UserRepository&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;:&amp;nbsp; Repository는 데이터 주체를 중심으로 만들어야 여러 화면에서 재사용 가능하고, 구조가 깔끔하게 유지&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt; &lt;/b&gt;&amp;nbsp;Q2. Repository는 domain 계층인데, 구현체는 왜 data에 있을까?&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;  A. Repository의 정의는 도메인에 있고, 실제 구현은 data에 있어야 함!&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;BookRepository (interface) &amp;rarr; domain&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;BookRepositoryImpl (구현체) &amp;rarr; data&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;: 이유는 도메인은 외부 기술(Retrofit, Room 등)을 몰라야 하기 때문&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;rarr; 기술 독립성 유지가 클린 아키텍쳐에서 제일 중요함!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  Q3. UseCase에서 Repository 호출하면, 결국 어차피 Repository의 구현체(RepositoryImpl)가 동작하는 거 아닐까?&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그럼 UseCase를 사용할 필요가 있나?&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;377&quot; data-start=&quot;339&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &amp;nbsp; 1. 계층 간 의존 역전 원칙(DIP)을 지키기 위해&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;font-size: 1.12em; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;구현체가 동작하긴 하지만&lt;/span&gt;&lt;span style=&quot;font-size: 1.12em; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-size: 1.12em; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;UseCase나 Repository는 인터페이스 기준으로만 동작해야 함!&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;UseCase는 BookRepository를 부르고,&amp;nbsp;실제로는 Hilt가 BookRepositoryImpl을 주입해주는 구조&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;: UseCase나 Repository에서 외부 기술을 직접 사용하면 계층 간 의존 역전 원칙(DIP)이 깨짐&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;상위 계층이 하위계층에 의존하면 X, 인터페이스에만 의존해야함! &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;hr data-end=&quot;603&quot; data-start=&quot;600&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-end=&quot;644&quot; data-start=&quot;605&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt; &amp;nbsp; 2&lt;/span&gt;. 관심사 분리로 유지보수성과 테스트성을 높이기 위해&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;734&quot; data-start=&quot;646&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;687&quot; data-start=&quot;646&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ViewModel은 상태 관리와 UI 이벤트 처리만 담당&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;734&quot; data-start=&quot;688&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;UseCase는 비즈니스 로직, 데이터 처리 등의 실제 기능을 담당&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;813&quot; data-start=&quot;736&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 분리하면&lt;b&gt; 각 계층이 하나의 책임만&lt;/b&gt; 갖게 돼서&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;rarr; 로직이 더 깔끔해지고&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;rarr; 테스트 작성, 기능 확장, 유지보수 용이&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 프로젝트 구조를 짜는 게 제일 어려운 거 같아요 꾸준한 연습하기,, !&amp;nbsp;&lt;/p&gt;</description>
      <category>  Android</category>
      <category>Android</category>
      <category>android ca</category>
      <category>Android Clean Architecture</category>
      <category>Android Repository</category>
      <category>android usecase</category>
      <category>Clean Architecture</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/157</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Android-%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%A0%81%EC%9A%A9-%EC%8B%9C-%EA%B3%A0%EB%AF%BC%ED%96%88%EB%8D%98-3%EA%B0%80%EC%A7%80-%EC%9D%98%EB%AC%B8%EC%A0%90#entry157comment</comments>
      <pubDate>Sat, 12 Apr 2025 11:56:29 +0900</pubDate>
    </item>
    <item>
      <title>[Android / RecyclerView] onCreateViewHolder vs onBindViewHolder: 클릭 리스너는 어디에 둘까?</title>
      <link>https://ddusi-dod.tistory.com/entry/Android-RecyclerView-onCreateViewHolder-vs-onBindViewHolder-%ED%81%B4%EB%A6%AD-%EB%A6%AC%EC%8A%A4%EB%84%88%EB%8A%94-%EC%96%B4%EB%94%94%EC%97%90-%EB%91%98%EA%B9%8C</link>
      <description>&lt;h2 data-end=&quot;144&quot; data-start=&quot;102&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  RecyclerView 클릭 리스너, 어디서 설정하는 게 좋을까?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;300&quot; data-start=&quot;146&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저는 원래 RecyclerView의 Adapter에 리스너를 설정할 때, 자연스레 onBindViewHolder()에서 뷰홀더로 넣어주는 방식을 많이 사용했었는데, 이게 성능과 메모리에 좋지 않다는 걸 알게 되었습니다 ,,!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;322&quot; data-start=&quot;302&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;322&quot; data-start=&quot;302&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일단, 2개의 호출 시점이 중요했습니다 !&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-end=&quot;345&quot; data-start=&quot;329&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  호출 시점의 차이&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;345&quot; data-start=&quot;329&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;onCreateViewHolder()&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;rarr; 새로운 뷰 홀더가 필요할 때 &lt;b&gt;한 번만 호출&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;345&quot; data-start=&quot;329&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  onBindViewHolder()&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;rarr; 뷰 홀더가 &lt;b&gt;데이터와 바인딩될 때마다 반복적으로 호출&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;⚠️&amp;nbsp; 스크롤할 때마다 계속 호출됨&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;onBindViewHolder에서 리스너를 생성하게 된다면,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스크롤할 때마다 새로운 리스너 객체가 만들어지기 때문에 메모리 사용량이 증가되고 GC가 생성됩니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 재사용성을 향상시키고 메모리를 절약하기 위해선 &lt;b&gt;onCreateViewHolder()&lt;/b&gt;에서 해주는 게 좋다고 합니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;소스를 예로 보자면,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744029239967&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyAdapter(
    private val items: List&amp;lt;String&amp;gt;,
    private val listener: OnClickListener
) : RecyclerView.Adapter&amp;lt;MyAdapter.MyViewHolder&amp;gt;() {

    inner class MyViewHolder(private val binding: ItemLayoutBinding) :
        RecyclerView.ViewHolder(binding.root) {

        init {
            // 클릭 리스너는 한 번만 설정
            binding.root.setOnClickListener {
                val position = bindingAdapterPosition
                if (position != RecyclerView.NO_POSITION) {
                    listener.onClick(position)
                }
            }
        }

        
        fun bind(item: String) {
            binding.textView.text = item
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val binding = ItemLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return MyViewHolder(binding) 
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.bind(items[position]) 
    }

    override fun getItemCount() = items.size

    interface OnClickListener {
        fun onClick(position: Int)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2134&quot; data-start=&quot;2089&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;❓inner class 없이&amp;nbsp; onCreateViewHolder에서 바로 리스너를 설정하면 안 될까?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;2307&quot; data-start=&quot;2136&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이론적으로는 가능하지만이 시점에서는 아직 &lt;b&gt;뷰홀더가 어떤 데이터를 바인딩받을지 결정되지 않았기 때문에&lt;/b&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;position 값이 &lt;b&gt;유효하지 않을 수 있고&lt;/b&gt; , 재사용 시 &lt;b&gt;잘못된 position을 참조할 위험&lt;/b&gt;이 있습니다 ..!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;2307&quot; data-start=&quot;2136&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;즉, 예시 코드처럼 inner class의 ViewHolder에서 init {} 블록 안에서 &lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;bindingAdapterPosition을 사용하는 방식이&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;항상 최신 position을 가져오기 때문에 더 안전한 방법입니다&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>  Android</category>
      <category>Android RecyclerView</category>
      <category>android recyclerview listener</category>
      <category>onbindviewholder listener</category>
      <category>oncreateviewholder listener</category>
      <category>recyclerview holder</category>
      <category>recyclerview listener</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/156</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Android-RecyclerView-onCreateViewHolder-vs-onBindViewHolder-%ED%81%B4%EB%A6%AD-%EB%A6%AC%EC%8A%A4%EB%84%88%EB%8A%94-%EC%96%B4%EB%94%94%EC%97%90-%EB%91%98%EA%B9%8C#entry156comment</comments>
      <pubDate>Mon, 7 Apr 2025 21:42:32 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] ifEmpty</title>
      <link>https://ddusi-dod.tistory.com/entry/Kotlin-ifEmpty</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;아주 편한 인라인 함수가 있어서 부랴부랴 포스팅합니다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kotlin 버전 1.3 이상부터 사용가능해요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ifEmpty&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1743429498176&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;inline fun &amp;lt;C : Array&amp;lt;*&amp;gt;, R, R&amp;gt; C.ifEmpty(defaultValue: () -&amp;gt; R): R&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1743429592439&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;inline fun &amp;lt;C : Collection&amp;lt;*&amp;gt;, R, R&amp;gt; C.ifEmpty(defaultValue: () -&amp;gt; R): R&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1743429601364&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;inline fun &amp;lt;M : Map&amp;lt;*, *&amp;gt;, R, R&amp;gt; M.ifEmpty(defaultValue: () -&amp;gt; R): R&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;만약 이 배열/컬렉션/맵이 비어 있지 않으면 이 배열/컬렉션/맵 을 반환하고, 비어 있으면 defaultValue 함수를 호출한 결과를 반환&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1743429890237&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val myArray: Array&amp;lt;Int&amp;gt; = arrayOf() 

 
    val defaultValue = listOf(42)

    val result = myArray.toList().ifEmpty { defaultValue }

    println(result) // 42가 출력됩니다 ~~ 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;if else 안 써도 되니까 편한 거 같아요..! 또 괜찮은 함수를 발견하면 줍줍해오겠습니다&amp;nbsp;&lt;/p&gt;</description>
      <category>  Kotlin</category>
      <category>kotlin ifempty</category>
      <category>kotlin inline function</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/155</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Kotlin-ifEmpty#entry155comment</comments>
      <pubDate>Mon, 31 Mar 2025 23:30:57 +0900</pubDate>
    </item>
    <item>
      <title>[Android] LiveData - observeForever</title>
      <link>https://ddusi-dod.tistory.com/entry/Android-LiveData-observeForever</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안녕하세요 오늘은 liveData의 observeForever에 대해 알아보겠습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;observeForever&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;LiveData의 메서드로 LifecycleOwner가 아닐 때도 데이터를 관찰할 수 있도록 합니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;하지만 lifecycleOwner와 관계없이 데이터를 관찰할 수 있기에, 수동으로 해제하지 않으면 메모리 누수 위험성이 있습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해제해줄 땐 꼭 removeObserver를 호출해야 합니다 !&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;완전한 코드는 아니지만 대충 뉘앙스만 보자면,, 아래와 같은 형식으로요&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1742518302617&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)

        // Observer 초기화
        observer = Observer { data -&amp;gt;
            // UI 업데이트
            updateUI(data)
        }

        // LiveData 관찰
        viewModel.data.observeForever(observer)
    }

    override fun onDestroy() {
        super.onDestroy()
        // Observer 해제
        viewModel.data.removeObserver(observer)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;LiveData의 본래 장점인 생명주기 관리와는 거리가 먼 거 같은데,, 전역으로 쓰이는 경우에 사용될 수 있다고 하네요 ,, ?&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정확히 어떤 경우에 사용해야 좋은지는 잘 모르겠습니다 ..!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;라이프사이클에 맞춰서 관리되는 observe를 사용하는 게 좋을 거 같은데&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;observeForever를 언제 사용하면 좋은지 의견이 있으신 분들은 알려주시면 감사하겠습니다 ..&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Android</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/153</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Android-LiveData-observeForever#entry153comment</comments>
      <pubDate>Fri, 21 Mar 2025 21:53:19 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Compose의 성능 최적화 기법 - 상태 읽기 연기</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Compose%EC%9D%98-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B8%B0%EB%B2%95-%EC%83%81%ED%83%9C-%EC%9D%BD%EA%B8%B0-%EC%97%B0%EA%B8%B0</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;상태 읽기 연기&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 상태를 직접적으로 읽지 않고, 대신 lambda 같이 상태를 간접적으로 참조하는 것을 의미&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-23 오후 6.23.55.png&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lSf63/btsMtGm1Wp5/pmScNiTxeZKJUacHcbkDCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lSf63/btsMtGm1Wp5/pmScNiTxeZKJUacHcbkDCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lSf63/btsMtGm1Wp5/pmScNiTxeZKJUacHcbkDCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlSf63%2FbtsMtGm1Wp5%2FpmScNiTxeZKJUacHcbkDCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1582&quot; height=&quot;498&quot; data-filename=&quot;스크린샷 2025-02-23 오후 6.23.55.png&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 상태를 직접적으로 읽는다면 (param으로 값을 받게되면) 값이 변경될때마다 recomposition&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 즉 Composition -&amp;gt; Layout -&amp;gt; Drawing을 거치게 되는데,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;param대신 lambda를&amp;nbsp;사용하면&amp;nbsp;UI의&amp;nbsp;상태나&amp;nbsp;데이터를&amp;nbsp;직접적으로&amp;nbsp;참조하지&amp;nbsp;않고,&amp;nbsp;함수의&amp;nbsp;결과를&amp;nbsp;사용하기 때문에 &lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Layout -&amp;gt; Drawing 단계만 진행하게 된다!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;composition 단계에서 ui 트리를 만드는데 많은 리소스가 사용되는데 이 단계를 건너뛸 수 있다는 것만으로도 리소스 낭비를 막을 수 있는 셈!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <category>compose</category>
      <category>compose recomposition</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/152</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Compose%EC%9D%98-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B8%B0%EB%B2%95-%EC%83%81%ED%83%9C-%EC%9D%BD%EA%B8%B0-%EC%97%B0%EA%B8%B0#entry152comment</comments>
      <pubDate>Sun, 23 Feb 2025 18:27:56 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] 의존성 주입 (Dependency Injection) (ft.hilt)</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-Dependency-Injection-fthilt-1</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우선, 라이브러리 적용이 필요합니다 (dagger, hilt)&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;[versions]
//...
daggerVersion = &quot;2.48&quot;
hiltVersion = &quot;1.2.0&quot;
retrofitVersion = &quot;2.9.0&quot;
gsonVersion = &quot;2.10.1&quot;


[libraries]
//...
dagger-android = { group = &quot;com.google.dagger&quot;, name = &quot;hilt-android&quot;, version.ref = &quot;daggerVersion&quot; }
hilt-compose = { group = &quot;androidx.hilt&quot;, name = &quot;hilt-navigation-compose&quot;, version.ref = &quot;hiltVersion&quot; }
androidx-gson = { group = &quot;com.google.code.gson&quot;, name = &quot;gson&quot;, version.ref = &quot;gsonVersion&quot; }
androidx-retrofit = { group = &quot;com.squareup.retrofit2&quot;, name = &quot;retrofit&quot;, version.ref = &quot;retrofitVersion&quot; }
androidx-gson-converter = { group = &quot;com.squareup.retrofit2&quot;, name = &quot;converter-gson&quot;, version.ref = &quot;retrofitVersion&quot; }
hilt-compiler = { group = &quot;com.google.dagger&quot;, name = &quot;hilt-android-compiler&quot;, version.ref = &quot;daggerVersion&quot; }

[plugins]
android-application = { id = &quot;com.android.application&quot;, version.ref = &quot;agp&quot; }
kotlin-android = { id = &quot;org.jetbrains.kotlin.android&quot;, version.ref = &quot;kotlin&quot; }
kotlin-compose = { id = &quot;org.jetbrains.kotlin.plugin.compose&quot;, version.ref = &quot;kotlin&quot; }
kotlin-kapt = { id = &quot;org.jetbrains.kotlin.kapt&quot; }
hilt-android = { id = &quot;com.google.dagger.hilt.android&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;build.gradle&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;의존성주입을 통해 network를 설정하고 github api를 사용해봅시다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. hilt에서 제일 처음해줘야 할 Application을 설정합니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Manifest에 application을 생성한 클래스의 name으로 변경해줘야합니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@HiltAndroidApp
class MainApplication: Application() {

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. Module을 설정합니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Module : 의존성 주입을 하고 있는 provider가 있는 곳 즉, Hilt는 모듈을 통해 &quot;이 클래스에 필요한 것은 이거야!&quot;라고 알게 되고, 자동으로 필요한 객체를 주입&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@InstallIn(SingletonComponent::class)
@Module
class MainModule {
    @Singleton
    @Provides
    @Named(&quot;API_URI&quot;)
    fun provideApiUri(): String  = &quot;https://api.github.com/&quot;
    //provide명은 전혀 중요하지 않다, 같은 타입이 여러개 있을 수 있으니 @Named 셋팅

    @Singleton
    @Provides
    fun provideGson(): Gson = GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create()

    //gson, retrofit match
    @Singleton
    @Provides
    fun provideConverterFactory(): Converter.Factory = GsonConverterFactory.create()

    @Singleton
    @Provides
    fun provideRetrofit(@Named(&quot;API_URI&quot;) apiUri: String, converterFactory: Converter.Factory): Retrofit = 
        Retrofit.Builder().baseUrl(apiUri).addConverterFactory(converterFactory).build()
    
    @Singleton
    @Provides
    fun provideGithubService(retrofit: Retrofit): GithubService = 
        retrofit.create(GithubService::class.java)

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. viewModel을 생성합니다&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@HiltViewModel
class MainViewModel @Inject constructor(
    private val apiService: GithubService
): ViewModel() {

    val repositorys = mutableStateListOf&amp;lt;Repository&amp;gt;()

    fun getRepository() {
        repositorys.clear()
        viewModelScope.launch {
            repositorys.addAll(apiService.getUsersRepos(&quot;eunie9498&quot;))
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>android compose</category>
      <category>compose</category>
      <category>compose di</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/151</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-Dependency-Injection-fthilt-1#entry151comment</comments>
      <pubDate>Tue, 11 Feb 2025 19:48:17 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] 안정성 stability</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-%EC%95%88%EC%A0%95%EC%84%B1-stability</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안정성에 관해서도 가장 중요한 키워드는 Smart Recomposition 같아요&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Smart Recomposition&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- composition 함수에서 사용되는 데이터가 변경된 데이터의 함수만 재호출 즉 일부분의 ui만 업데이트&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 성능 최적화&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 때 compose는 필요한 recomposition임을 어떻게 구분할까 ?&amp;nbsp; -&amp;gt; &lt;i&gt;&lt;b&gt;&lt;u&gt;paramater로 구분&lt;/u&gt;&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;크게 3가지로 나눠본다면&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Stable parameters&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 변경 시 컴포지션에서 추적이 가능하기 때문에 smart recomposition이 가능&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 즉, 변경 사항이 없다면 compose는 skip&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Unstable parameters&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 변경 시 컴포지션에서 추적이 불가 -&amp;gt; 데이터의 변경 사항의 유무와 관계 없이 무조건 Recomposition&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 퍼포먼스에 안 좋은 영향&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Immutable parameters&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 변경 불가 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- compose에선 당연히 변경 사항이 없을 거라 생각해서 skip&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739083604968&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class Contact(val name: String, val number: String)

@Composable
fun ContactRow(contact: Contact, modifier: Modifier = Modifier) {
   var selected by remember { mutableStateOf(false) }

   Row(modifier) {
      ContactDetails(contact)
      ToggleButton(selected, onToggled = { selected = !selected })
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- contact의 paramater는 val로 지정 : immutable&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- contactDetails는 recomposition에서 제외 (smart recomposition)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;근데 만약, data class에서 val이 아니라 var로 선언된다면&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739083762158&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class Contact(var name: String, var number: String)

@Composable
fun ContactRow(contact: Contact, modifier: Modifier = Modifier) {
   var selected by remember { mutableStateOf(false) }

   Row(modifier) {
      ContactDetails(contact)
      ToggleButton(selected, onToggled = { selected = !selected })
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;mutable이기 때문에 contactDetail이 무조건 recomposition 된다&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Compose를 Smart하게 사용하려면 Immutable이나, stable paramater를 사용하는 것이 좋다 .. !&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(all primitive types and string are&amp;nbsp;&amp;nbsp;immutable )&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;compose compiler report로 확인해보기 (compose가 함수를 어떻게 판단하는지)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빌드 전 build.gradle에 아래와 같이 추가&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739086292007&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  android { ... }

  composeCompiler {
    reportsDestination = layout.buildDirectory.dir(&quot;compose_compiler&quot;)
    metricsDestination = layout.buildDirectory.dir(&quot;compose_compiler&quot;)
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;싱크 후 빌드하면 프로젝트 경로/app/build/compose_compiler 폴더에 파일들이 생성되어있습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이런 코드를 빌드하고 리포트를 생성했는데, &amp;lt;modulename&amp;gt;-composables.txt 파일을 확인하면&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Text(
        text = &quot;Hello $name!&quot;,
        modifier = modifier
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래와 같이 생성되어 있습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739086472476&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;restartable skippable scheme(&quot;[androidx.compose.ui.UiComposable]&quot;) fun Greeting(
  stable name: String
  stable modifier: Modifier? = @static Companion
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;stable / unstable : 위에서 살펴봤던 개념과 동일합니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;restartable&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- composable이 recomposition의 진입점 (어느 부분부터 recomposition이 가능한지)을 나타냅니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;skippable&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;composable이 recomposition일 때 값이 변경되지 않았다면 recomposition을 skip할 수 있음을 나타냅니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;composable 함수의 모든 매개변수가 안정해야지만 붙을 수 skippable이 붙게됩니다&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위의 예제를 아래와 같이 변경해본다면&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;data class NotStable(
    var name: String
)

@Composable
fun Greeting(dto: NotStable, modifier: Modifier = Modifier) {
    Text(
        text = &quot;Hello ${dto.name}!&quot;,
        modifier = modifier
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;compiler report에서 skippable이 빠진 걸 알 수 있습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739087011954&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;restartable scheme(&quot;[androidx.compose.ui.UiComposable]&quot;) fun Greeting(
  unstable dto: NotStable
  stable modifier: Modifier? = @static Companion
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Annotation으로 immutable, stable을 지정할 수 있다&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739270743584&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Immutable
data class TestCollection(
 val dto : List&amp;lt;DTO&amp;gt;
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이런식으로 미리 annotation으로 지정할 수 있다&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>android compose</category>
      <category>compose</category>
      <category>compose compiler</category>
      <category>compose immutable</category>
      <category>compose report</category>
      <category>compose smart recomposition</category>
      <category>compose stability</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/150</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-%EC%95%88%EC%A0%95%EC%84%B1-stability#entry150comment</comments>
      <pubDate>Sun, 9 Feb 2025 16:44:11 +0900</pubDate>
    </item>
    <item>
      <title>[Android] ViewPager2 감도 조절하기</title>
      <link>https://ddusi-dod.tistory.com/entry/Android-ViewPager2-%EA%B0%90%EB%8F%84-%EC%A1%B0%EC%A0%88%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;viewpager2를 카드뷰처럼 넘기고 싶은데 너무 휙휙 돌아가지는 거 같아서 서치하다 찾은 방법이다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ViewPager2&lt;/b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;는 ViewPager와 다르게 RecyclerView를 기반이라 recyclerview내에 스크롤 감도를 조절하는 mTouchSlop을 변경하면 된다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732589020799&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun ViewPager2.reduceDragSensitivity() {
    val recyclerViewField = ViewPager2::class.java.getDeclaredField(&quot;mRecyclerView&quot;)
    recyclerViewField.isAccessible = true
    val recyclerView = recyclerViewField.get(this) as RecyclerView

    val touchSlopField = RecyclerView::class.java.getDeclaredField(&quot;mTouchSlop&quot;)
    touchSlopField.isAccessible = true
    val touchSlop = touchSlopField.get(recyclerView) as Int
    touchSlopField.set(recyclerView, touchSlop * 8) // 원하는 숫자     
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;touchSlop 값은 유저가 스와이프할 때 인식되는 최소 거리라, 값을 높일수록 드래그 감도가 낮아진다고 생각하면 된다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 방법은 내부 필드에 접근 하는 방법이라 업데이트가 된다면..! 안 먹히는 방법이 될 수도 있다 ..!&amp;nbsp;&lt;/p&gt;</description>
      <category>  Android</category>
      <category>recyclerview</category>
      <category>touchslop</category>
      <category>ViewPager2</category>
      <category>viewpager2 scroll</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/149</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Android-ViewPager2-%EA%B0%90%EB%8F%84-%EC%A1%B0%EC%A0%88%ED%95%98%EA%B8%B0#entry149comment</comments>
      <pubDate>Sat, 8 Feb 2025 19:15:52 +0900</pubDate>
    </item>
    <item>
      <title>[Android] AAC의 LiveData, ViewModel의 LiveData</title>
      <link>https://ddusi-dod.tistory.com/entry/Android-AAC%EC%9D%98-LiveData-ViewModel%EC%9D%98-LiveData</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AAC&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 안드로이드 아키텍처 컴포넌트(Android Architecture Components)로 앱 개발을 더 쉽고 유지보수 가능하게 해주는 &lt;b&gt;구성요소들의 모음&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;LiveData는 AAC의 일부로 아래와 같은 특성을 가진다&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Observer 패턴 기반의 데이터 홀더&lt;/b&gt;로, 데이터를 감싸고 UI와 연결할 수 있음.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;LifecycleOwner(Activity, Fragment 등)를 인식&lt;/b&gt;해, UI Controller가 종료되면 자동으로 Observer를 제거해 메모리 누수를 방지함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ViewModel에서 쓰느냐 Activity, Fragment에서 쓰느냐의 차이는 바로 lifecycle&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 ViewModel에서 사용하면 ViewModel의 Lifecycle을 따르기 때문에, 화면 회전 시에도 데이터가 유지&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;즉 , ViewModel은 &lt;b&gt;Activity보다 수명이 길고, Activity가 재생성되어도 같은 ViewModel 인스턴스를 유지하기 때문&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-04 오후 8.28.15.png&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;776&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lE7js/btsL7iNxM9S/H3R1djf2QqWQovvQg8OKK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lE7js/btsL7iNxM9S/H3R1djf2QqWQovvQg8OKK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lE7js/btsL7iNxM9S/H3R1djf2QqWQovvQg8OKK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlE7js%2FbtsL7iNxM9S%2FH3R1djf2QqWQovvQg8OKK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;375&quot; height=&quot;776&quot; data-filename=&quot;스크린샷 2025-02-04 오후 8.28.15.png&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;776&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;근데 만약 Fragment나 Activity에서 LiveData를 선언해서 쓰게 된다면, 화면 회전 시 Activity/Fragment가 재생성되면서 LiveData도 새로 만들어지고 기존 데이터는 날아감&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그러므로 LiveData는 ViewModel에서 선언해서 쓰는 것이 좋다 &lt;/span&gt;&lt;/p&gt;</description>
      <category>  Android</category>
      <category>aac livedata</category>
      <category>android aac</category>
      <category>Android LiveData</category>
      <category>android livedata aac livedata</category>
      <category>android viewmodel</category>
      <category>ViewModel lifecycle</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/148</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Android-AAC%EC%9D%98-LiveData-ViewModel%EC%9D%98-LiveData#entry148comment</comments>
      <pubDate>Tue, 4 Feb 2025 20:32:37 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Rendering</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Rendering</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-31 오후 4.28.17.png&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRL63l/btsL3ycbM3h/g6qccLJYjTNY0cJpbRB1y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRL63l/btsL3ycbM3h/g6qccLJYjTNY0cJpbRB1y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRL63l/btsL3ycbM3h/g6qccLJYjTNY0cJpbRB1y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRL63l%2FbtsL3ycbM3h%2Fg6qccLJYjTNY0cJpbRB1y0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1574&quot; height=&quot;174&quot; data-filename=&quot;스크린샷 2025-01-31 오후 4.28.17.png&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Compose는 크게 보자면 Data를 UI로 변환하는 과정인데, 아래의 과정들을 거칩니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Composition : &lt;b&gt;what&lt;/b&gt;&amp;nbsp; (무엇을 그릴지)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;layout : &lt;b&gt;where&lt;/b&gt; (UI를 어디에 배치할지)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Drawing: &lt;b&gt;How&lt;/b&gt; (어떻게 UI를 그릴지)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Composition&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;code를 UI Tree화 시킵니다 즉, 데이터와&amp;nbsp;UI&amp;nbsp;요소&amp;nbsp;간의&amp;nbsp;관계가&amp;nbsp;정의됩니다&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-31 오후 4.31.14.png&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPBIN8/btsL40k4WwY/QFDkxrG7z1qdjaggEreUy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPBIN8/btsL40k4WwY/QFDkxrG7z1qdjaggEreUy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPBIN8/btsL40k4WwY/QFDkxrG7z1qdjaggEreUy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPBIN8%2FbtsL40k4WwY%2FQFDkxrG7z1qdjaggEreUy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;713&quot; height=&quot;354&quot; data-filename=&quot;스크린샷 2025-01-31 오후 4.31.14.png&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;/span&gt;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Layout&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 아래의 과정으로 UI 트리를 탐색을 하면서, 각 레이아웃을 측정하고 배치됩니다&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #202124; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;하위 요소 측정&lt;/b&gt;: 노드가 하위 요소(있는 경우)를 측정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;자체 크기 결정&lt;/b&gt;: 이러한 측정치를 기반으로 노드가 자체 크기를 결정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;하위 요소 배치&lt;/b&gt;: 각 하위 노드는 노드의 자체 위치를 기준으로 배치&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 , 모든 노드는 단 한번씩만 방문하기 때문에&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;Compose 런타임은 모든 노드를 측정하고 배치하는 데 UI 트리를 한 번만 통과하면 되므로 성능이 향상됩니다!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 단계를 거치면 할당된 높이, 너비, 그리고 그려야하는 x,y 좌표를 알게됩니다&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-31 오후 4.33.10.png&quot; data-origin-width=&quot;2426&quot; data-origin-height=&quot;1220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biJuFG/btsL2fYV3tg/DnUQMcB5TSTBk6plPlDHCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biJuFG/btsL2fYV3tg/DnUQMcB5TSTBk6plPlDHCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biJuFG/btsL2fYV3tg/DnUQMcB5TSTBk6plPlDHCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiJuFG%2FbtsL2fYV3tg%2FDnUQMcB5TSTBk6plPlDHCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2426&quot; height=&quot;1220&quot; data-filename=&quot;스크린샷 2025-01-31 오후 4.33.10.png&quot; data-origin-width=&quot;2426&quot; data-origin-height=&quot;1220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt; Draw&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; UI Tree를 위에서 아래로 한번씩만 방문하면서 그리게 됩니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-31 오후 4.35.43.png&quot; data-origin-width=&quot;2308&quot; data-origin-height=&quot;1232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cy7VUz/btsL47RVRDv/KnDP70IkReOrgXzXTXe3X1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cy7VUz/btsL47RVRDv/KnDP70IkReOrgXzXTXe3X1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cy7VUz/btsL47RVRDv/KnDP70IkReOrgXzXTXe3X1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcy7VUz%2FbtsL47RVRDv%2FKnDP70IkReOrgXzXTXe3X1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2308&quot; height=&quot;1232&quot; data-filename=&quot;스크린샷 2025-01-31 오후 4.35.43.png&quot; data-origin-width=&quot;2308&quot; data-origin-height=&quot;1232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>android compose rendering</category>
      <category>compose</category>
      <category>compose composition</category>
      <category>compose draw</category>
      <category>compose layout</category>
      <category>compose rendering</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/147</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Rendering#entry147comment</comments>
      <pubDate>Fri, 31 Jan 2025 16:46:05 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Compose의 LifeCycle</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Compose%EC%9D%98-LifeCycle</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;⭐️ Smart Recomposition&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- composition 함수에서 사용되는 데이터가 변경된 데이터의 함수만 재호출 즉 일부분의 ui만 업데이트&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 성능 최적화&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Compose의 Lifecycle&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-27 오후 6.51.30.png&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OQOQm/btsL01F63Nr/mfk0NeqRH44aqblpIlUj41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OQOQm/btsL01F63Nr/mfk0NeqRH44aqblpIlUj41/img.png&quot; data-alt=&quot;https://developer.android.com/develop/ui/compose/lifecycle?hl=ko&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OQOQm/btsL01F63Nr/mfk0NeqRH44aqblpIlUj41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOQOQm%2FbtsL01F63Nr%2Fmfk0NeqRH44aqblpIlUj41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;598&quot; height=&quot;265&quot; data-filename=&quot;스크린샷 2025-01-27 오후 6.51.30.png&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://developer.android.com/develop/ui/compose/lifecycle?hl=ko&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- &lt;b&gt;composition&lt;/b&gt; : 컴포저블 함수가 호출되어 UI 트리를 구성하는 과정으로 여러 개의 컴포저블이 결합되어 최종 UI 생성, 즉 초기 UI를 설정하는 단계&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- &lt;b&gt;recomposition&lt;/b&gt; : 상태가 변경되어 UI가 다시 그려지는 과정, 상태 변화를 감지하고 영향을 받는 부분만 업데이트 (최적화 하는 것이 제일 중요)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- &lt;b&gt;disposal&lt;/b&gt; : composable이 소멸, 리소스 해제&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;recomposition의 최적화를 위해서 생명주기를 아는 것이 중요합니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 아래와 같이 코드를 사용한다고 하면,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737972455239&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MovieOverview(movie: Movie) {
    Column {
       //...
        val image = loadNetworkImage(movie.url)
        MovieHeader(image)

        /* ... */
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1737972470149&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MoviesScreen(movies: List&amp;lt;Movie&amp;gt;) {
    Column {
        for (movie in movies) {
            // MovieOverview composables are placed in Composition given its
            // index position in the for loop
            MovieOverview(movie)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 맨 하단에 아이템을 추가한다면, 별다른 문제 없이 잘 추가가 될 것입니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-27 오후 7.08.06.png&quot; data-origin-width=&quot;1524&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/owz8J/btsL1tCG7xz/jztkG80C30YD7588Myuft0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/owz8J/btsL1tCG7xz/jztkG80C30YD7588Myuft0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/owz8J/btsL1tCG7xz/jztkG80C30YD7588Myuft0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fowz8J%2FbtsL1tCG7xz%2FjztkG80C30YD7588Myuft0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1524&quot; height=&quot;506&quot; data-filename=&quot;스크린샷 2025-01-27 오후 7.08.06.png&quot; data-origin-width=&quot;1524&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 목록의 상단 혹은 중간에 아이템을 추가하면 모든 MovieOverview 호출들이 recomposition이 되어버립니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 네트워크를 통해 데이터를 가져오고 있는 중이였다면&amp;nbsp; 네트워크 요청이 중복 실행되거나 이전 요청이 취소되는 상황이 발생합니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이런 문제를 해결하기 위해서는&lt;/p&gt;
&lt;pre id=&quot;code_1737972887409&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MoviesScreenWithKey(movies: List&amp;lt;Movie&amp;gt;) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt; 트리의 특정 부분(&lt;/span&gt;&lt;a style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary?hl=ko#key(kotlin.Array,kotlin.Function0)&quot;&gt;key&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;컴포저블)을 식별하는 데 사용할 값을 지정하면 됩니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 key&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;값은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;전체적으로&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;고유하지 않아도 되며 호출 사이트에서의 컴포저블 호출 간에만 고유하면 됩니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위와 같이 특정 식별값을 준다면 어디에 추가된다고 해도&amp;nbsp; 전체적으로 Recomposition 하지 않고&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;compose는 개별 MovieOverview를 인식하고 재사용할 수 있습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-27 오후 7.16.20.png&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNsOIh/btsL2Jxvq83/9SkiEIF2WbLjXg2Mqp8T4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNsOIh/btsL2Jxvq83/9SkiEIF2WbLjXg2Mqp8T4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNsOIh/btsL2Jxvq83/9SkiEIF2WbLjXg2Mqp8T4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNsOIh%2FbtsL2Jxvq83%2F9SkiEIF2WbLjXg2Mqp8T4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1530&quot; height=&quot;486&quot; data-filename=&quot;스크린샷 2025-01-27 오후 7.16.20.png&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>android compose</category>
      <category>android lifecycle</category>
      <category>compose</category>
      <category>compose composable</category>
      <category>compose key</category>
      <category>compose lifecycle</category>
      <category>compose recomposition</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/146</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Compose%EC%9D%98-LifeCycle#entry146comment</comments>
      <pubDate>Mon, 27 Jan 2025 19:18:38 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] 단방향 데이터 흐름</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-%EB%8B%A8%EB%B0%A9%ED%96%A5-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%9D%90%EB%A6%84</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-23 오후 5.43.02.png&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qggbu/btsLZdrABGs/xPCnADMKX0sU5egfYd6g51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qggbu/btsLZdrABGs/xPCnADMKX0sU5egfYd6g51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qggbu/btsLZdrABGs/xPCnADMKX0sU5egfYd6g51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQggbu%2FbtsLZdrABGs%2FxPCnADMKX0sU5egfYd6g51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;373&quot; height=&quot;424&quot; data-filename=&quot;스크린샷 2025-01-23 오후 5.43.02.png&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;State :&amp;nbsp; 위에서 아래로만 내려간다&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- &lt;span style=&quot;text-align: start;&quot;&gt;일반적으로&amp;nbsp;상위&amp;nbsp;컴포넌트(부모)에서&amp;nbsp;하위&amp;nbsp;컴포넌트(자식)로&amp;nbsp;전달&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;- UI 상태를 한 곳 (보통 부모 컴포넌트) 에서 관리해서&amp;nbsp;&lt;span style=&quot;text-align: start;&quot;&gt; UI 컴포넌트(자식)는 그 상태에 대한 직접적인 수정이 불가능 -&amp;gt; &lt;i&gt;&lt;u&gt;캡슐화&lt;/u&gt;&lt;/i&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Event :&amp;nbsp; 아래에서 위로 올라간다&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- UI 일부에서 이벤트를 생성해 위로 전달&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- &lt;span style=&quot;text-align: start;&quot;&gt;그&amp;nbsp;이벤트를&amp;nbsp;부모에게&amp;nbsp;알려주고&amp;nbsp;부모가&amp;nbsp;해당&amp;nbsp;이벤트에&amp;nbsp;대한&amp;nbsp;처리&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;- 이벤트 발생 -&amp;gt; 이벤트 핸들러가 상태 변경(업데이트) -&amp;gt; 상태가 변경되면 state holder에서 변경된 상태 UI 표시&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- &lt;span style=&quot;text-align: start;&quot;&gt;부모는 이벤트를 처리하고 상태를 변경 후, 새로운 상태를 다시 자식에게 전달. 자식은 상태 변경에 대한 영향을 받지만 직접적으로 상태를 수정불가&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트 하기 용이하고, ui에 일관성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ex)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;mutableStatOf -&amp;gt; Compose가 관측할 수 있는 MutableState 생성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;값 변경 -&amp;gt; 값을 읽는 모든 Composition의 Recomposition 예약&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;remember&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- remember는 Compose가 상태를 유지, 컴포넌트가 다시 그려질 때, 이전에 저장했던 값을 기억&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 만약 해당 컴포넌트가 UI에서 제거 -&amp;gt; remember로 저장된 객체는 자동으로 제거(메모리 효율)&lt;/span&gt;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>compose mutablestateof</category>
      <category>compose recomposition</category>
      <category>compose remember</category>
      <category>compose state</category>
      <category>compose state ui</category>
      <category>compose UI</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/145</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-%EB%8B%A8%EB%B0%A9%ED%96%A5-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%9D%90%EB%A6%84#entry145comment</comments>
      <pubDate>Thu, 23 Jan 2025 17:50:59 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Navigation</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Navigation</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;라이브러리 추가가 필요합니다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;[libs.version.toml]&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;[versions]
//...
navigationVersion = &quot;2.8.5&quot;

[libraries]
//...
androidx-navigation-compose = { group = &quot;androidx.navigation&quot;, name = &quot;navigation-compose&quot;, version.ref = &quot;navigationVersion&quot; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;[build.gradle]&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;implementation(libs.androidx.navigation.compose)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Navigation&lt;/b&gt;은 화면 간 전환을 관리하는 중요한 요소입니다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제를 통해 각각 필요한 개념을 익혀보겠습니다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;NavController&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 현재 Navigation 상태 관리, 화면 간 이동을 처리&lt;br /&gt;- 각 스크린에서 NavController를 인자로 받아 사용 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;navController를 받는 Composable 함수를 생성합니다&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun NavTest(
    modifier: Modifier = Modifier,
    navController : NavHostController = rememberNavController()
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;NavHost를 생성합니다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- Navigation 그래프를 정의하는 컨테이너, startDestination을 지정하여 처음 표시될 화면 설정&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;    NavHost(
        navController = navController,
        startDestination = &quot;Korea&quot;,
        modifier = modifier
    ) {
        composable(&quot;Korea&quot;) {

            Column {
                Text(&quot;한국이에요  &quot;)
                Spacer(modifier = Modifier.size(10.dp))
                Button(onClick = {
                    navController.navigate(&quot;Korea&quot;) {
                        launchSingleTop = true
                    }
                }) {
                    Text(text = &quot;한국으로 이동&quot;)
                }

                Button(onClick = {
                    navController.navigate(&quot;America&quot;)
                }) {
                    Text(text = &quot;미국으로 이동&quot;)
                }
                Button(onClick = {
                    navController.navigate(&quot;Japan&quot;)
                }) {
                    Text(text = &quot;일본으로 이동&quot;)
                }

                Button(onClick = {
                    navController.navigate(&quot;Argument/Mexico&quot;)
                }) {
                    Text(text = &quot;멕시코로 이동&quot;)
                }
            }
        }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;startDestination을 설정하는 NavHost를 만들었고, NavHost 내부 즉 NavBackStackEntry에 composable을 추가합니다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;각 화면은 composable 함수로 정의&lt;/u&gt;되고 compose은&amp;nbsp;NavHostBuilder의 composable로 아래의 paramater들을 가질 수 있습니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-22 오후 5.56.47.png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlKayI/btsLVfSfrua/IenIjkuLEIgyok5HIbcS30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlKayI/btsLVfSfrua/IenIjkuLEIgyok5HIbcS30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlKayI/btsLVfSfrua/IenIjkuLEIgyok5HIbcS30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlKayI%2FbtsLVfSfrua%2FIenIjkuLEIgyok5HIbcS30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;661&quot; height=&quot;359&quot; data-filename=&quot;스크린샷 2025-01-22 오후 5.56.47.png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위의 예제에선 route를 &quot;Korea&quot;으로 설정합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;여러 화면을 왔다갔다 하는 예제를 보면 아래와 같습니다&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;@Composable
fun NavTest(
    modifier: Modifier = Modifier,
    navController : NavHostController = rememberNavController()
) {
    NavHost(
        navController = navController,
        startDestination = &quot;Korea&quot;,
        modifier = modifier
    ) {
        composable(&quot;Korea&quot;) {

            Column {
                Text(&quot;한국이에요  &quot;)
                Spacer(modifier = Modifier.size(10.dp))
                Button(onClick = {
                    navController.navigate(&quot;Korea&quot;) {
                        launchSingleTop = true
                    }
                }) {
                    Text(text = &quot;한국으로 이동&quot;)
                }

                Button(onClick = {
                    navController.navigate(&quot;America&quot;)
                }) {
                    Text(text = &quot;미국으로 이동&quot;)
                }
                Button(onClick = {
                    navController.navigate(&quot;Japan&quot;)
                }) {
                    Text(text = &quot;일본으로 이동&quot;)
                }

                Button(onClick = {
                    navController.navigate(&quot;Argument/Mexico&quot;)
                }) {
                    Text(text = &quot;멕시코로 이동&quot;)
                }
            }
        }

        composable(&quot;America&quot;) {
            Column {
                Text(&quot;미국입니다   &quot;)
                Button(onClick = {
                    navController.navigate(&quot;Korea&quot;)
                }) {
                    Text(text = &quot;한국으로 이동&quot;)
                }
                Button(onClick = {
                    navController.navigate(&quot;Japan&quot;)
                }) {
                    Text(text = &quot;일본으로 이동&quot;)
                }
            }
        }


        composable(&quot;Japan&quot;) {
            Column {
                Text(&quot;일본입니다  &quot;)
                Button(onClick = {
                    navController.navigate(&quot;Korea&quot;)
                }) {
                    Text(text = &quot;한국으로 이동&quot;)
                }
            }
        }

        composable(&quot;Argument/{countryName}&quot;) { navBackStackEntry -&amp;gt;
            val countryName = navBackStackEntry.arguments?.getString(&quot;countryName&quot;)
            Text(&quot;Argument/$countryName&quot;)
            Button({
                navController.navigate(&quot;Argument/Mexico&quot;)
            }) {
                Text(&quot;Argument/Mexico로 이동&quot;)
            }
        }

        composable(&quot;Argument/Mexico&quot;) {
            Text(&quot;멕시코입니다  &quot;)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;navigate의 인자로는 다양하게 들어갈 수 있어요&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;destination (String) : ex) &quot;Korea&quot;, &quot;America&quot;&lt;br /&gt;args (Bundle?) : ex) navController.navigate(&quot;Argument/$countryName&quot;)와 같이 사용&lt;br /&gt;options (NavOptions?) : NavOptions 객체를 통해 애니메이션, 화면 전환 방식 등을 설정 가능&lt;/p&gt;
&lt;pre id=&quot;code_1737538581140&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val navOptions = NavOptions.Builder()
                .setEnterAnim(R.anim.slide_in_right)  // 진입 애니메이션
                .setExitAnim(R.anim.slide_out_left)    // 퇴장 애니메이션
                .setPopEnterAnim(R.anim.slide_in_left)  // 뒤로 가기 시 진입 애니메이션
                .setPopExitAnim(R.anim.slide_out_right)  // 뒤로 가기 시 퇴장 애니메이션
                .build()
                
navController.navigate(&quot;detail&quot;, navOptions)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;builder (NavOptionsBuilder.() -&amp;gt; Unit)&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737538807648&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; Button(onClick = {
            navController.navigate(&quot;Japan&quot;) {
                // NavOptionsBuilder를 사용하여 네비게이션 옵션 설정
                launchSingleTop = true // 이미 존재하는 화면으로 이동할 때 중복 생성 방지
                restoreState = true // 이전 상태 복원
                popUpTo(&quot;Korea&quot;) {
                    inclusive = true 
                }
            }
        }) {
            Text(&quot;한국으로 이동&quot;)
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 다양한 옵션을 설정할 수 있습니다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;launchSingleTop&lt;/b&gt;은 기존 안드로이드에서의 개념과 똑같아요&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;popUpTo&lt;/b&gt;는 현재 스택에서 지정한 목적지(destination)까지의 모든 화면을 제거&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;만약 Korea -&amp;gt; Japan -&amp;gt; Mexico -&amp;gt; Korea의 스택일 때 popUpTo(&quot;Korea&quot;)를 하면 빈 스택이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;inclusive&lt;/b&gt;는 popUpTo와 함께 사용되는데, popUpTo에서 화면을 제거할 때 특정 목적지 화면을 포함할지 여부를 결정&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;inclusive가&amp;nbsp;true로&amp;nbsp;설정되면,&amp;nbsp;지정한&amp;nbsp;목적지&amp;nbsp;화면도&amp;nbsp;스택에서&amp;nbsp;제거됩니다.&amp;nbsp;기본값은&amp;nbsp;false이구요&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;만약 Korea -&amp;gt; Japan -&amp;gt; Mexico -&amp;gt; Korea의 스택일 때 popUpTo(&quot;Korea&quot;) { inclusive = false } 를 하게 되면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&quot;Korea&quot;까지의 모든 화면이 제거되지만, &quot;Korea&quot; 자체는 포함되지 않으므로 남게 됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;궁금하신 점이나 의견이 있으시면 댓글 부탁드립니다 감사합니다  &lt;/span&gt;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>compose</category>
      <category>compose inclusive</category>
      <category>compose navigate</category>
      <category>compose navigate option</category>
      <category>compose navigation</category>
      <category>compose navigation popupto</category>
      <category>compose navigation stack</category>
      <category>compose popupto</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/144</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Navigation#entry144comment</comments>
      <pubDate>Wed, 22 Jan 2025 18:47:18 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Theme</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Theme</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로젝트를 생성하면 기본으로 프로젝트명Theme가 생성되어있습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTestTheme  {

            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;ComposeTestTheme으로 이동하면&amp;nbsp;아래와 같이 선언되어있습니다&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;package com.kong.composetest.ui.theme

import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext

private val DarkColorScheme = darkColorScheme(
    primary = Purple80,
    secondary = PurpleGrey80,
    tertiary = Pink80,
    surface = Color.Black,
    onSurface = Color.White
)

private val LightColorScheme = lightColorScheme(
    primary = Color.Green,
    secondary = PurpleGrey40,
    tertiary = Pink40,
    surface = Color.Black

    /* Other default colors to override
    background = Color(0xFFFFFBFE),
    surface = Color(0xFFFFFBFE),
    onPrimary = Color.White,
    onSecondary = Color.White,
    onTertiary = Color.White,
    onBackground = Color(0xFF1C1B1F),
    onSurface = Color(0xFF1C1B1F),
    */
)

@Composable
fun ComposeTestTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    dynamicColor: Boolean = true,
    content: @Composable () -&amp;gt; Unit
) {
    val colorScheme = LightColorScheme

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;몇개만 알아보자면, 원래 안드로이드 테마와 비슷합니다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- darkColorScheme / lightColorScheme: 다크모드 / 라이트모드 에서 적용할 색상들&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- primary, secondary, tertiary: 애플리케이션에서 사용되는 주요 색상, ex) primary는 주요 버튼이나 강조되는 component에 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 보통 primary, onPrimary / surface, onSurface처럼 XXX, onXXX를 짝을 이뤄서 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예를 들어, primary: 주로 버튼, 강조된 텍스트 등에서 사용되는 기본 색상 / onPrimary: primary 색상 위에 놓이는 텍스트나 아이콘의 색상&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이런식으로 일관성을 유지하게 짝으로 사용된다고 합니다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아래처럼 프로젝트의 ui.theme에서도 확인 가능합니다&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-22 오후 5.36.50.png&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKpAWm/btsLVPZJIUk/KDxP58k07RqlImuux0bVgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKpAWm/btsLVPZJIUk/KDxP58k07RqlImuux0bVgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKpAWm/btsLVPZJIUk/KDxP58k07RqlImuux0bVgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKpAWm%2FbtsLVPZJIUk%2FKDxP58k07RqlImuux0bVgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;321&quot; height=&quot;226&quot; data-filename=&quot;스크린샷 2025-01-22 오후 5.36.50.png&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;</description>
      <category>  Compose</category>
      <category>android compose</category>
      <category>android compose theme</category>
      <category>compose</category>
      <category>compose theme</category>
      <category>compose ui theme</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/143</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Theme#entry143comment</comments>
      <pubDate>Wed, 22 Jan 2025 17:47:24 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] compositionLocal</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-compositionLocal</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CompositionLocal을 통해 전역 상태를 선언, 이를 CompositionLocalProvider를 사용해 하위 Composable에 전달할 수 있다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특정 값을 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;하위 Composable에&lt;/span&gt;주입하는 데 유용함&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737445923151&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val LocalExampleValue = compositionLocalOf { &quot;기본 값&quot; }

@Composable
fun ParentComposable() {
    CompositionLocalProvider(LocalExampleValue provides &quot;제공된 값&quot;) {
        ChildComposable()
    }
}

@Composable
fun ChildComposable() {
    val value = LocalExampleValue.current
    Text(text = value) // &quot;제공된 값&quot;이 표시됨..! 
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;제공된 값이 표시되는 이유는 current에서 가장 가까운 곳에서 설정한 값을 가져오기 때문에 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;전역으로 설정하기엔 좋을 거 같은데 추적이 힘들 거 같다&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;찾아보니, CompositionLocalProvider는 전역 상태 공유, 의존성 주입, UI 컨텍스트 제공 등의 상황에서 주로 사용된다고 한다&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>compose compositionlocalprovider</category>
      <category>compose local</category>
      <category>compositionlocalprovider</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/142</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-compositionLocal#entry142comment</comments>
      <pubDate>Tue, 21 Jan 2025 16:54:49 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] viewModel , LiveData</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-viewModel</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우선 라이브러리를 추가합니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[libs.version.toml]&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;[versions]
//...
viewModelVersion = &quot;2.8.7&quot;
composeVersion = &quot;1.7.2&quot; // 현재 사용 중인 compose 버전

[libraries]
//...
androidx-viewmodel-compose = { group = &quot;androidx.lifecycle&quot;, name = &quot;lifecycle-viewmodel-compose&quot;, version.ref=&quot;viewModelVersion&quot;}
androidx-livedata-compose = { group = &quot;androidx.compose.runtime&quot;, name = &quot;runtime-livedata&quot;, version.ref = &quot;composeVersion&quot; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[build.gradle] app&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;implementation(libs.androidx.viewmodel.compose)
implementation(libs.androidx.livedata.compose)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;viewModel을 상속받는 클래스를 하나 만들어줍니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;class ToDoViewModel: ViewModel() {
    val (text, setText) =
        androidx.compose.runtime.mutableStateOf(&quot;&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;근데 여기서 val (text, setText)에 레드라인이 생기고 아래와 같은 메시지가 떠요&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-21 오후 3.13.41.png&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uXHEv/btsLVlwKSQH/B3uq8PMYWzQEzX9viqyjdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uXHEv/btsLVlwKSQH/B3uq8PMYWzQEzX9viqyjdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uXHEv/btsLVlwKSQH/B3uq8PMYWzQEzX9viqyjdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuXHEv%2FbtsLVlwKSQH%2FB3uq8PMYWzQEzX9viqyjdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;898&quot; height=&quot;56&quot; data-filename=&quot;스크린샷 2025-01-21 오후 3.13.41.png&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구조 분해 선언은 지역 변수나 지역 값에 대해서만 사용할 수 있어요 즉 , 구조 분해 선언(destructuring declarations)은 한 클래스 내에 종속될 수 없기 때문에 위와 같은 에러가 뜹니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 이렇게 바꿔줍니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;class ToDoViewModel: ViewModel() {
    val text = mutableStateOf(&quot;&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ViewModel을 param으로 받는 composable func에서 viewModel의 값을 가져오거나, 변경해야한다면 아래와 같이 쓸 수 있습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun Test(viewModel: ToDoViewModel = viewModel()) {
	//...
    	ToDoInput(
                text = viewModel.text.value,
                onTextChange = {
                    viewModel.text.value = it
                },
                onSubmit = onSubmit
            )
            
}


@Composable
fun ToDoInput(
    text: String,
    onTextChange: (String) -&amp;gt; Unit,
    onSubmit: (String) -&amp;gt; Unit
) {
    Row(modifier = Modifier.padding(8.dp)) {
        OutlinedTextField(
            value = text,
            onValueChange = onTextChange,
            modifier = Modifier.weight(1f)
        )
        Spacer(modifier = Modifier.size(8.dp))
        Button(onClick = {
            onSubmit(text)
        }) {
            Text(&quot;입력&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;viewModel로 상태를 옮길 수 있고, 상태를 옮기는 경우에는 remember를 쓰지 않습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(remember는 composable 함수의 생명주기에 맞춰 동작하기 때문에)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고, ViewModel을 사용하여 상태를 관리하면 상태가 생명주기와 더 잘 분리되고, 당연히 구성 변경시에도 상태를 유지합니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제 위에서 했던 예제들을 LiveData로 변경해보겠습니다 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저, viewmodel을 상속받은 곳에서 아래와 같이 변경을 해줍니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;텍스트는 liveData화 시키고,&amp;nbsp; 텍스트의 값을 변경하는 것을 viewModel로 가져옵니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;    val text = MutableLiveData(&quot;&quot;)
    val setText : (String) -&amp;gt; Unit = {
        text.value = it
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 composable 함수에서 text의 값을 liveData를 쓰는 게 아니라, 그 값을 &lt;b&gt;상태로 변환해서 가져옵니다&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737442779897&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun Test(viewModel: ToDoViewModel = viewModel()) {
	//...
    	ToDoInput(
                text = viewModel.text.observeAsState(&quot;&quot;).value,
                onTextChange = {
                    viewModel.setText
                },
                onSubmit = onSubmit
            )
            
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;상태로 변환해서 사용하는 이유 ? -&amp;gt; Compose의&amp;nbsp;핵심&amp;nbsp;기능이&amp;nbsp;&lt;b&gt;상태&amp;nbsp;기반&amp;nbsp;프레임워크&lt;/b&gt;이기 때문에, Compose 내의 값들은 당연히 상태를 기반으로 해야 Compose를 사용하는 의미가 있다..!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 LiveData를 사용할 때 주의할 점인 viewModel 외부에서&lt;i&gt;&lt;b&gt; MutableLiveData에 직접 접근해서 수정하지 못하도록&lt;/b&gt;&lt;/i&gt; 해야하기 때문에 위의 viewModel을 다시 수정합니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737443225264&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    val _text = MutableLiveData(&quot;&quot;)
    val text: LiveData&amp;lt;String&amp;gt; get() = _text
    
    val setText : (String) -&amp;gt; Unit = {
        _text.value = it
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 ViewModel에서 리스트를 관리하고 싶다면 Livedata로 선언하고 이걸 state로 변환하는 것은 추천하지 않는 방법이라고 합니다 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 이유는, observeAsState의 원형을 보면 DisposableEffect에서 this(LiveData)를 받아서 호출하게 되는데&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 때 LiveData의 Observer는 리스트의 참조가 변경이 되어야 이 observer가 호출되기 때문에 매번 리스트 전체를 갱신해줘야 호출이 된다 -&amp;gt; 아주   ..&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Composable
fun &amp;lt;R, T : R&amp;gt; LiveData&amp;lt;T&amp;gt;.observeAsState(initial: R): State&amp;lt;R&amp;gt; {
    val lifecycleOwner = LocalLifecycleOwner.current
    val state = remember {
        @Suppress(&quot;UNCHECKED_CAST&quot;) /* Initialized values of a LiveData&amp;lt;T&amp;gt; must be a T */
        mutableStateOf(if (isInitialized) value as T else initial)
    }
    DisposableEffect(this, lifecycleOwner) {
        val observer = Observer&amp;lt;T&amp;gt; { state.value = it }
        observe(lifecycleOwner, observer)
        onDispose { removeObserver(observer) }
    }
    return state
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 compose에서는 mutableStateListOf를 많이 사용한다고 합니다&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>android compose</category>
      <category>android compose viewmodel</category>
      <category>android viewmodel</category>
      <category>compose</category>
      <category>compose android</category>
      <category>compose livedata</category>
      <category>compose mutablestatelistof</category>
      <category>compose viewmodel</category>
      <category>compose viewmodel example</category>
      <category>compose viewmodel livedata</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/141</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-viewModel#entry141comment</comments>
      <pubDate>Tue, 21 Jan 2025 15:30:09 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스 / Kotlin ] 귤 고르기</title>
      <link>https://ddusi-dod.tistory.com/entry/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Kotlin-%EA%B7%A4-%EA%B3%A0%EB%A5%B4%EA%B8%B0</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-20 오후 8.17.18.png&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;790&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWolDN/btsLSMoHG2b/ohuO5H7yiCMeuBxuJoFYPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWolDN/btsLSMoHG2b/ohuO5H7yiCMeuBxuJoFYPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWolDN/btsLSMoHG2b/ohuO5H7yiCMeuBxuJoFYPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWolDN%2FbtsLSMoHG2b%2FohuO5H7yiCMeuBxuJoFYPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;666&quot; height=&quot;790&quot; data-filename=&quot;스크린샷 2025-01-20 오후 8.17.18.png&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;790&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-20 오후 8.17.32.png&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pGK7q/btsLS3KtVH6/PBQJMb14KOZ6kKmHfE2C6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pGK7q/btsLS3KtVH6/PBQJMb14KOZ6kKmHfE2C6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pGK7q/btsLS3KtVH6/PBQJMb14KOZ6kKmHfE2C6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpGK7q%2FbtsLS3KtVH6%2FPBQJMb14KOZ6kKmHfE2C6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;466&quot; height=&quot;390&quot; data-filename=&quot;스크린샷 2025-01-20 오후 8.17.32.png&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;k개를 고를 때 크기가 &lt;b&gt;서로 다른 종류&lt;/b&gt;가 총 몇개가 되는지에 대한 문제&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;처음엔 서로 다른 종류 중 가장 적은 무게를 구하는 줄 알았는데 그게 아니라 단순히 서로 다른 종류 수의 최솟값이다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 우선 빈도수 별로 배열을 정리해야겠다고 생각 -&amp;gt; Map&amp;lt;무게, 빈도수&amp;gt; 로 바꾸기&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;var map = tangerine.toList().groupingBy{ it }.eachCount()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;k가 2일 때 [1,1,1,1,2,2,2,3]은 이렇게 정렬된다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #000000;&quot;&gt;{1=4,&amp;nbsp;2=3,&amp;nbsp;3=1}&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 빈도수가 높은 순으로 내림차순 정렬&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;it.value를 더해서 k보다 같거나 크면 끝이니까..!&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;val sortedEntries = map.entries.sortedByDescending { it.value }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. 서로 다른 종류가 몇 종류가 들어가야하는지 계산&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;k가 2일 때를 기준으로 하면 무게가 1인 귤 4개로만 해도 충분하니 더 이상 계산할 필요가 없다고 생각했다&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;var count = 0

for (entry in sortedEntries) {
    count += entry.value
    answer += 1

    if (count &amp;gt;= k) {
        break
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;만약, k가 4일 경우에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;{2 = 2, 3 = 2, 5 = 2, 1=1, 4=1} 이렇게 정렬될텐데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;구하고자 하는 게 가장 적은 무게의 조합도 아니고, 그냥 서로 다른 종류가 몇개가 되는 게 최소한인지 구하는 거니까&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;정렬하고 entry의 value들을 더해가면서(카운팅 시작) 그 합이 K보다 크거나 같아지면 break 하면 된다고 생각했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 푼 건지는 모르겠다..!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스스로 풀어냈음에 의의를 두..기..&amp;nbsp;&lt;/p&gt;</description>
      <category>  알고리즘/⏳ 알고리즘 풀이</category>
      <category>귤 고르기</category>
      <category>귤고르기 kotlin</category>
      <category>프로그래머스</category>
      <category>프로그래머스 kotlin</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/140</guid>
      <comments>https://ddusi-dod.tistory.com/entry/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Kotlin-%EA%B7%A4-%EA%B3%A0%EB%A5%B4%EA%B8%B0#entry140comment</comments>
      <pubDate>Mon, 20 Jan 2025 20:32:35 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Compose 주의점</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Compose-%EC%A3%BC%EC%9D%98%EC%A0%90</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. Scaffold 사용 시 padding&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예전엔 Scaffold의 content에 직접 Column을 배치해도 문제가 없었는데, 최근 Studio 버전에서는 Lint 검사가 강화되어서 Scaffold의 content에 padding을 적용해야 경고, 오류가 안 뜬다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-17 오후 1.19.09.png&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmjWVg/btsLPxrZ2rM/KIVD8538LL14A9nuBLPs51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmjWVg/btsLPxrZ2rM/KIVD8538LL14A9nuBLPs51/img.png&quot; data-alt=&quot;❌&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmjWVg/btsLPxrZ2rM/KIVD8538LL14A9nuBLPs51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmjWVg%2FbtsLPxrZ2rM%2FKIVD8538LL14A9nuBLPs51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;278&quot; height=&quot;185&quot; data-filename=&quot;스크린샷 2025-01-17 오후 1.19.09.png&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;❌&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-17 오후 1.20.26.png&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwHqG6/btsLQC6Z804/K9bFZVjWPSJ5nBWb0IeeuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwHqG6/btsLQC6Z804/K9bFZVjWPSJ5nBWb0IeeuK/img.png&quot; data-alt=&quot;⭕️&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwHqG6/btsLQC6Z804/K9bFZVjWPSJ5nBWb0IeeuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwHqG6%2FbtsLQC6Z804%2FK9bFZVjWPSJ5nBWb0IeeuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;538&quot; height=&quot;250&quot; data-filename=&quot;스크린샷 2025-01-17 오후 1.20.26.png&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;⭕️&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. Card의 elevation&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;예전에는 Card의 elevation 속성에 직접 Dp 값 사용가능&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Material Design 3로 업데이트 되면서 &lt;b&gt;CardElevation&lt;/b&gt; 객체를 사용하도록 변경됨&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-17 오후 1.24.51.png&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L78ZC/btsLRFhoyAl/Qx09VXaNKjXK7INckdrQWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L78ZC/btsLRFhoyAl/Qx09VXaNKjXK7INckdrQWk/img.png&quot; data-alt=&quot;❌&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L78ZC/btsLRFhoyAl/Qx09VXaNKjXK7INckdrQWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL78ZC%2FbtsLRFhoyAl%2FQx09VXaNKjXK7INckdrQWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;464&quot; height=&quot;175&quot; data-filename=&quot;스크린샷 2025-01-17 오후 1.24.51.png&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;❌&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-17 오후 1.25.45.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R1MbG/btsLQOMUxMc/GRCJuu6b8umwuHNrn9NMI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R1MbG/btsLQOMUxMc/GRCJuu6b8umwuHNrn9NMI0/img.png&quot; data-alt=&quot;⭕️&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R1MbG/btsLQOMUxMc/GRCJuu6b8umwuHNrn9NMI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR1MbG%2FbtsLQOMUxMc%2FGRCJuu6b8umwuHNrn9NMI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;557&quot; height=&quot;274&quot; data-filename=&quot;스크린샷 2025-01-17 오후 1.25.45.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;⭕️&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. LazyColumn items&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- LazyColumn의 items 함수에 List&amp;lt;T&amp;gt;를 직접 전달해 아이템 표시가 가능했는데 Compose 1.2.0 버전부터는 items 함수가 변경되어 더 이상 List&amp;lt;T&amp;gt;를 직접 전달할 수 없다&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- Compose 1.2.0부터 LazyColumn의 &lt;b&gt;items 함수는 count와 itemContent 파라미터를 사용하도록 변경&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-17 오후 1.51.29.png&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bshQDY/btsLRpzakAy/qQDc1s1wZS1UqkwEItNRsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bshQDY/btsLRpzakAy/qQDc1s1wZS1UqkwEItNRsK/img.png&quot; data-alt=&quot;❌&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bshQDY/btsLRpzakAy/qQDc1s1wZS1UqkwEItNRsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbshQDY%2FbtsLRpzakAy%2FqQDc1s1wZS1UqkwEItNRsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;363&quot; height=&quot;139&quot; data-filename=&quot;스크린샷 2025-01-17 오후 1.51.29.png&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;❌&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-17 오후 1.52.02.png&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brJXhW/btsLRPc8uCe/EUGW4TSNB9jE7KpdJwDqc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brJXhW/btsLRPc8uCe/EUGW4TSNB9jE7KpdJwDqc0/img.png&quot; data-alt=&quot;⭕️&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brJXhW/btsLRPc8uCe/EUGW4TSNB9jE7KpdJwDqc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrJXhW%2FbtsLRPc8uCe%2FEUGW4TSNB9jE7KpdJwDqc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;513&quot; height=&quot;161&quot; data-filename=&quot;스크린샷 2025-01-17 오후 1.52.02.png&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;⭕️&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>compose card elevation</category>
      <category>compose carddefaults</category>
      <category>compose cardelevation</category>
      <category>compose scaffold padding</category>
      <category>compose scaffold paddingvalues</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/139</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Compose-%EC%A3%BC%EC%9D%98%EC%A0%90#entry139comment</comments>
      <pubDate>Fri, 17 Jan 2025 13:37:29 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] SideEffect</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-SideEffect</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SideEffect&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Composable 함수는 입력 값이 변경될 때마다 재실행되며, UI를 업데이트. 하지만, UI 업데이트와 직접적인 관련이 없고, Composable 함수의 재실행 시마다 반복적으로 실행될 필요가 없는 작업이 side-effect&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 즉,&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;상태 값과 직접적으로 관련 없는 UI 작업들이 Composable 함수에서 불필요하게 재호출되는 것을 방지하기 위해 사용&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;LaunchedEffect&lt;/b&gt; : Composable 안에서 suspend func 실행&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 하나 이상의 key를 paramater로 받아야 한다 (ex. error.value)&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- key가 바뀌지 않는 이상 같은 코루틴을 사용하고, key가 바뀌면 기존 코루틴을 취소하고 다시 코루틴을 만들어 실행&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun test(
    flavors: State&amp;lt;List&amp;lt;Flavor&amp;gt;&amp;gt;,
    error: State&amp;lt;Throwable?&amp;gt;,
    scaffoldState: ScaffoldState = rememberScaffoldState()
) {
    LaunchedEffect(error.value) {
        val errorMessage = error.value?.message ?: return@LaunchedEffect
        scaffoldState.snackbarHostState.showSnackbar(
            message = errorMessage,
            actionLabel = &quot;show toast&quot;
        )
    }

    // ... flavors를 사용하여 UI를 구성 ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;rememberCoroutineScope&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Composable 내에서 코루틴을 시작하기 위한&amp;nbsp; 방법을 제공하여 더 이상 필요하지 않을 때 적절하게 관리, 취소됨&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun MyComposable() {
    val scope = rememberCoroutineScope()
    val snackbarHostState = remember { SnackbarHostState() }

    Button(onClick = {
        scope.launch {
            snackbarHostState.showSnackbar(&quot;Hello from coroutine!&quot;)
        }
    }) {
        Text(&quot;Show Snackbar&quot;)
    }

    SnackbarHost(hostState = snackbarHostState)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;rememberUpdateState&lt;/b&gt;:&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 값이 변경되더라도 LaunchedEffect가 다시 시작되지 않도록 하면서 최신 값에 접근해야 할 때 사용 즉,&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;LaunchedEffect 내부에서 사용되는 값이 변경되더라도 LaunchedEffect가 다시 시작되지 않아야 하는 경우 (ex. 타이머, 지속되어야하는 애니메이션 같은 경우)에 LaunchedEffect를 다시 시작하지 않고도 최신 값에 접근&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- rememberUpdatedState는 값이 변경될 때마다 내부적으로 최신 값을 업데이트하지만, LaunchedEffect의 key로 사용되지 않기 때문에 LaunchedEffect에는 영향 X&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun MyScreen(text: String) {
    val currentText = rememberUpdatedState(text) // currentText는 MutableState&amp;lt;String&amp;gt; 타입

    LaunchedEffect(Unit) {
        while (true) {
            delay(1000)
            val currentTextValue = currentText.value // currentText.value를 통해 최신 값에 접근
            println(&quot;The latest text is: $currentTextValue&quot;) 
        }
    }

    Text(text = text)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-16 오후 4.13.44.png&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ThBpq/btsLQtBCJSo/OAI6hHRN3cz8qsKlDENkQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ThBpq/btsLQtBCJSo/OAI6hHRN3cz8qsKlDENkQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ThBpq/btsLQtBCJSo/OAI6hHRN3cz8qsKlDENkQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FThBpq%2FbtsLQtBCJSo%2FOAI6hHRN3cz8qsKlDENkQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1020&quot; height=&quot;188&quot; data-filename=&quot;스크린샷 2025-01-16 오후 4.13.44.png&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;값을 새 값으로 변경해주는 것 밖에 없음..!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;DisposableEffect&lt;/b&gt;: &amp;nbsp;Composable&amp;nbsp;함수가&amp;nbsp;Composition에서&amp;nbsp;제거될&amp;nbsp;때&amp;nbsp;리소스를&amp;nbsp;해제하기&amp;nbsp;위해&amp;nbsp;사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- lifecycleOwner를 key로 받음&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 리소스 관리에 용이&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun MyScreen(lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current) {
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event -&amp;gt;
            if (event == Lifecycle.Event.ON_RESUME) {
                // 리소스 초기화 또는 시작
            } else if (event == Lifecycle.Event.ON_PAUSE) {
                // 리소스 해제 또는 중지
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;SideEffect&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 매 recomposition마다 호출됨 (이게 헷갈릴 수 있다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;- Composable 함수가 처음 Composition에 추가될 때, SideEffect 내부의 코드가 실행 O&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;- Composable 함수가 재구성될 때, SideEffect 내부의 코드는 다시 실행 X&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;- Composable 함수가 Composition에서 제거될 때, SideEffect 내부의 코드는 실행 X&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun MyScreen(count: Int) {
    SideEffect {
        println(&quot;SideEffect called!&quot;) 
    }

    Text(&quot;Count: $count&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;SideEffect called!는 한번만 찍힌다..! &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 매 Recomposition마다 호출은 되지만 SideEffect { } 내부가 실행되진 않는다!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 로그를 남기거나, analytics 이벤트를 전송하는 작업들이 SideEffect에 넣기 좋다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;productState&lt;/b&gt;: 비 compose 상태를 compose 상태로 변환&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 비동기적로 상태를 업데이트하고, 그 결과를 State&amp;lt;T&amp;gt;로 반환하는 함수&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun MyScreen() {
    val counterState = produceState(initialValue = 0) {
        while (true) {
            delay(1000)
            value++ 
        }
    }

    Text(&quot;Counter: ${counterState.value}&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ProduceStateScope를 통해 상태를 업데이트..!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-16 오후 5.13.11.png&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvOz1A/btsLQRINfGW/kNwhqbsyen3CNe9yG7sOXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvOz1A/btsLQRINfGW/kNwhqbsyen3CNe9yG7sOXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvOz1A/btsLQRINfGW/kNwhqbsyen3CNe9yG7sOXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvOz1A%2FbtsLQRINfGW%2FkNwhqbsyen3CNe9yG7sOXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;484&quot; data-filename=&quot;스크린샷 2025-01-16 오후 5.13.11.png&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- produceState는 내부적으로 remember를 사용하여 mutableStateOf(initialValue)를 생성&lt;br /&gt;- LaunchedEffect(Unit)을 사용하여 비동기 작업을 시작&lt;br /&gt;- producer lambda 내부에서 ProduceStateScope를 통해 상태를 업데이트하면, mutableStateOf의 값이 변경 (예제에선 value ++)&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;derivedStateOf&lt;/b&gt;: Compose에서&amp;nbsp;다른&amp;nbsp;상태&amp;nbsp;값을&amp;nbsp;기반으로&amp;nbsp;새로운&amp;nbsp;상태&amp;nbsp;값을&amp;nbsp;계산하여&amp;nbsp;생성하는&amp;nbsp;함수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 상태의 값이 바뀌지 않으면 그 값을 유지 (derivedStateOf는 내부적으로 계산된 값을 캐싱하고 있기 때문에, 값이 같다면 value 변경 X)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 계산된 값이 바뀔 때만 state의 value가 바뀜&amp;nbsp; -&amp;gt; 불필요한 재구성 방지: derivedStateOf는 필요한 경우에만 상태 값을 다시 계산하여 업데이트&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun MyScreen() {
    var counter by remember { mutableStateOf(0) }
    val doubledCounter by remember { derivedStateOf { counter * 2 } }

    Column {
        Button(onClick = { counter++ }) { Text(&quot;Increment Counter&quot;) }
        Text(&quot;Counter: $counter&quot;)
        Text(&quot;Doubled Counter: $doubledCounter&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;snapshotFlow&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Compose State를 Flow로 변환하여 State 값의 변경 사항을 비동기적으로 collect하도록 하는 함수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 이전에 방출한 값과 다를 경우에만 방출 즉, State 값이 변경되면 snapshotFlow는 새로운 값을 Flow로 방출&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;puppet&quot;&gt;&lt;code&gt;var counter by remember { mutableStateOf(0) }

LaunchedEffect(Unit) {
    snapshotFlow { counter }
        .collect { newCounter -&amp;gt;
            println(&quot;Counter updated: $newCounter&quot;)
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Counter updated: 0 만 찍히고 그 후엔 안 찍힘&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>compose</category>
      <category>compose disposableeffect</category>
      <category>compose launchedeffect</category>
      <category>compose remembercoroutinescope</category>
      <category>compose rememberupdatestate</category>
      <category>compose sideeffect</category>
      <category>compose snapshotflow</category>
      <category>LaunchedEffect</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/138</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-SideEffect#entry138comment</comments>
      <pubDate>Thu, 16 Jan 2025 17:25:46 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Animation</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Animation</link>
      <description>&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;AnimatedVisibility&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- visibility에 Animated 적용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Transition이 enter, exit 따로 나눠져있다&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-08 오후 7.52.53.png&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/enzUWN/btsLH7ZLzUp/PLqNdBXVgFY0MO39Akd1a1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/enzUWN/btsLH7ZLzUp/PLqNdBXVgFY0MO39Akd1a1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/enzUWN/btsLH7ZLzUp/PLqNdBXVgFY0MO39Akd1a1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FenzUWN%2FbtsLH7ZLzUp%2FPLqNdBXVgFY0MO39Akd1a1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;547&quot; height=&quot;183&quot; data-filename=&quot;스크린샷 2025-01-08 오후 7.52.53.png&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- FadeTransition:&amp;nbsp; 나타날 때, 사라질 때 투명도를 조절해 부드럽게 변하는 효과&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- SlideTransition:&amp;nbsp; 화면의 한 쪽에서 다른 쪽으로 이동하며 나타나고 사라지는 효과&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- ExpandTransition:&amp;nbsp; 펼쳐지거나 축소되며 나타나고 사라지는 효과, 주로 패널이나 리스트 항목에 사용&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- ScaleTransition:&amp;nbsp; 크기를 확대하거나 축소하면서 나타나거나 사라지는 효과&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이런 식으로 사용 ..! textVisible은 아래와 같이 선언되어있다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;var textVisible by remember { mutableStateOf(false) }
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;AnimatedVisibility(
    visible = textVisible,
    enter = expandHorizontally(),
    exit = shrinkHorizontally()
) {
    Text(&quot;Animation Test&quot;)
}


Row(
    modifier = Modifier.selectable(
        selected = textVisible,
        onClick = {
            textVisible = true
        }
    ),
    verticalAlignment = Alignment.CenterVertically
) {
    RadioButton(
        selected = textVisible,
        onClick = {
            textVisible = true
        }
    )

    Text(text = &quot;본다&quot;)
}

Row(
    modifier = Modifier.selectable(
        selected = !textVisible,
        onClick = {
            textVisible = false
        }
    ),
    verticalAlignment = Alignment.CenterVertically
) {
    RadioButton(
        selected = !textVisible,
        onClick = {
            textVisible = false
        }
    )

    Text(text = &quot;안 본다&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약, 컬러를 변경시키고 싶다면&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;val bgColor by animateColorAsState(
    targetValue = if(isDefault) Color(0xffcecece) else Color(0xff0094ff), 
    label = &quot;&quot;
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위와 비슷한 두 개의 row를 눌러가며 테스트 해볼 수 있다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;Row(
    modifier = Modifier.selectable(
        selected = isDefault,
        onClick = {
            isDefault = true
        }
    ),
    verticalAlignment = Alignment.CenterVertically
) {
    RadioButton(
        selected = isDefault,
        onClick = {
            isDefault = true
        }
    )

    Text(text = &quot;default&quot;)
}

Row(
    modifier = Modifier.selectable(
        selected = !isDefault,
        onClick = {
            isDefault = false
        }
    ),
    verticalAlignment = Alignment.CenterVertically
) {
    RadioButton(
        selected = !isDefault,
        onClick = {
            isDefault = false
        }
    )

    Text(text = &quot;blue&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Crossfade&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Crossfade는 두 개의 UI 요소 간에 부드러운 전환 효과, 주로 상태에 따라 서로 다른 UI를 표시할 때 사용된다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;좀 더 다이나믹한 UI 애니메이션 표시할 때 주로 사용&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun MyCrossfadeExample() {
    var isFirstState by remember { mutableStateOf(true) }

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        // Crossfade를 사용하여 상태에 따라 UI 전환
        Crossfade(
            targetState = isFirstState,
            label = &quot;text crossfade&quot;) 
        { state -&amp;gt;
            if (state) {
                Text(&quot;첫 번째 상태&quot;, modifier = Modifier.padding(16.dp))
            } else {
                Text(&quot;두 번째 상태&quot;, modifier = Modifier.padding(16.dp))
            }
        }

        // 상태 변경 버튼
        Button(onClick = { isFirstState = !isFirstState }) {
            Text(&quot;상태 변경&quot;)
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>android compose</category>
      <category>compose</category>
      <category>compose animate</category>
      <category>compose animatecolor</category>
      <category>compose animated</category>
      <category>compose animation</category>
      <category>compose crossfade</category>
      <category>compose crossfade example</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/137</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Animation#entry137comment</comments>
      <pubDate>Wed, 8 Jan 2025 20:04:16 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] State, StateHoisting</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-State-StateHoisting</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;상태가 전달되는 범위는 최대한 좁혀야한다 -&amp;gt; StateHoisting (상태를 위로 끌어올린다)&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun StateTest() {
    var downCase by rememberSaveable { mutableStateOf(&quot;aa&quot;) }
    var upperCase by rememberSaveable { mutableStateOf(&quot;AA&quot;) }


    Column(modifier = Modifier.padding(16.dp)) {
        OutlinedTextField(
            value = downCase,
            onValueChange = {
                if(it.isBlank()) {
                    downCase = &quot;&quot;
                    upperCase = &quot;&quot;
                    return@OutlinedTextField
                }
                downCase = it
                upperCase = downCase.uppercase()
            },
            label = {
                Text(&quot;소문자&quot;)
            }
        )

        OutlinedTextField(
            value = upperCase,
            onValueChange =  {
                upperCase = it
            },
            label = {
                Text(&quot;대문자&quot;)
            }
        )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-07 오후 8.30.35.png&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k9Iyb/btsLHCyeDQD/oxREMJ5vxM93ITUKOBCqk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k9Iyb/btsLHCyeDQD/oxREMJ5vxM93ITUKOBCqk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k9Iyb/btsLHCyeDQD/oxREMJ5vxM93ITUKOBCqk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk9Iyb%2FbtsLHCyeDQD%2FoxREMJ5vxM93ITUKOBCqk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;228&quot; data-filename=&quot;스크린샷 2025-01-07 오후 8.30.35.png&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 잘 나오지만, 하위 컴포넌트는 상태에 대한 의존성을 갖게 되므로 이걸 최대한 분리하는 게 좋다고 한다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;상위 컴포넌트가 상태를 가질 수 있게끔 -&amp;gt; 테스트가 쉬워지고, 상태를 한 곳에만 몰아둘 수 있다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun StateTest() {
    var downCase by rememberSaveable { mutableStateOf(&quot;aa&quot;) }
    var upperCase by rememberSaveable { mutableStateOf(&quot;AA&quot;) }

    DownToUpperStateless(
        downCase,
        upperCase) {
        if(it.isBlank()) {
            downCase = &quot;&quot;
            upperCase = &quot;&quot;
            return@DownToUpperStateless
        }
        downCase = it
        upperCase = downCase.uppercase()
    }

}

//상태를 모르고 전혀 개입하지 X
@Composable
fun DownToUpperStateless(
    downCase: String,
    upperCase: String,
    onDownChanged: (String) -&amp;gt; Unit
) {
    Column(modifier = Modifier.padding(16.dp)) {
        OutlinedTextField(
            value = downCase,
            onValueChange = onDownChanged,
            label = {
                Text(&quot;소문자&quot;)
            }
        )

        OutlinedTextField(
            value = upperCase,
            onValueChange = onDownChanged,
            label = {
                Text(&quot;대문자&quot;)
            }
        )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, UI와 상태가 분리되고, 재사용성, 테스트 용이성도 좋아진다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;후에 state를 ViewModel에서 관리하게 할 때도 쉬울 거 같아&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>android state hoisting</category>
      <category>compose hoisting</category>
      <category>compose state</category>
      <category>compose state hoisting</category>
      <category>compose stateless</category>
      <category>compose 상태 관리</category>
      <category>state hoisting</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/136</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-State-StateHoisting#entry136comment</comments>
      <pubDate>Tue, 7 Jan 2025 20:41:42 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] BottomAppBar</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-BottomAppBar</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-07 오후 7.57.10.png&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;874&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2znpY/btsLFAIGoZB/mkImqZZMq1p2kKcbgDW1DK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2znpY/btsLFAIGoZB/mkImqZZMq1p2kKcbgDW1DK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2znpY/btsLFAIGoZB/mkImqZZMq1p2kKcbgDW1DK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2znpY%2FbtsLFAIGoZB%2FmkImqZZMq1p2kKcbgDW1DK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;669&quot; height=&quot;510&quot; data-filename=&quot;스크린샷 2025-01-07 오후 7.57.10.png&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;874&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원하는 ref들은 content를 사용해 추가해야함을 알 수 있다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보통 scaffold와 함께 사용한다, 자주 사용하는 bottombar의 형식인 5가지 버튼을 공부했던 내용들을 사용해 생성해보자&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;@SuppressLint(&quot;UnusedMaterial3ScaffoldPaddingParameter&quot;)
@Composable
fun WholeTest() {
    Scaffold(
        bottomBar = { MyBottomAppBar() }
    ) {  innerPadding -&amp;gt;

    }
}

@Composable
fun MyBottomAppBar() {
    BottomAppBar {
        ConstraintLayout(
            modifier = Modifier.fillMaxWidth()
        ) {

            val (button1, button2, button3, button4, button5) = createRefs()
            
            IconButton(
                onClick = {  },
                modifier = Modifier.constrainAs(button1) {
                    start.linkTo(parent.start)
                    end.linkTo(button2.start)
                    width = Dimension.wrapContent
                }
            ) {
                Icon(Icons.Filled.Home, contentDescription = &quot;Home&quot;)
            }
            
            IconButton(
                onClick = {  },
                modifier = Modifier.constrainAs(button2) {
                    start.linkTo(button1.end)
                    end.linkTo(button3.start)
                    width = Dimension.wrapContent
                }
            ) {
                Icon(Icons.Filled.Favorite, contentDescription = &quot;Favorite&quot;)
            }
            
            IconButton(
                onClick = {  },
                modifier = Modifier.constrainAs(button3) {
                    start.linkTo(button2.end)
                    end.linkTo(button4.start)
                    width = Dimension.wrapContent
                }
            ) {
                Icon(Icons.Filled.Search, contentDescription = &quot;Search&quot;)
            }
            
            IconButton(
                onClick = {  },
                modifier = Modifier.constrainAs(button4) {
                    start.linkTo(button3.end)
                    end.linkTo(button5.start)
                    width = Dimension.wrapContent
                }
            ) {
                Icon(Icons.Filled.Notifications, contentDescription = &quot;Notifications&quot;)
            }
            
            IconButton(
                onClick = {  },
                modifier = Modifier.constrainAs(button5) {
                    start.linkTo(button4.end)
                    end.linkTo(parent.end)
                    width = Dimension.wrapContent
                }
            ) {
                Icon(Icons.Filled.Settings, contentDescription = &quot;Settings&quot;)
            }

            // 각 버튼 사이에 균등한 간격을 주기 위해 chain을 설정
            createHorizontalChain(button1, button2, button3, button4, button5)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-07 오후 8.09.24.png&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;1042&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c90BCe/btsLFXKmhOb/aQwWAlENi631uBIyEIPvwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c90BCe/btsLFXKmhOb/aQwWAlENi631uBIyEIPvwK/img.png&quot; data-alt=&quot;화면엔 이렇게 보인ㄷㅏ ~&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c90BCe/btsLFXKmhOb/aQwWAlENi631uBIyEIPvwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc90BCe%2FbtsLFXKmhOb%2FaQwWAlENi631uBIyEIPvwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;436&quot; height=&quot;676&quot; data-filename=&quot;스크린샷 2025-01-07 오후 8.09.24.png&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;1042&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;화면엔 이렇게 보인ㄷㅏ ~&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>android bottombar</category>
      <category>android compose bottombar</category>
      <category>compose appbar</category>
      <category>compose bottomappbar</category>
      <category>compose bottombar</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/135</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-BottomAppBar#entry135comment</comments>
      <pubDate>Tue, 7 Jan 2025 20:11:37 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Snackbar</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Snackbar</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;SnackbarHost&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;- Snackbar는 직접 쓰지 않고 SnackbarHost를 사용해야한다&amp;nbsp; -&amp;gt; S&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;nackbarHostState를 사용하여 Snackbar의 상태를 관리&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-07 오후 7.30.32.png&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;1062&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SXWgJ/btsLHOFekRt/SuzpOGsw7nVJHcJj0FWu6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SXWgJ/btsLHOFekRt/SuzpOGsw7nVJHcJj0FWu6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SXWgJ/btsLHOFekRt/SuzpOGsw7nVJHcJj0FWu6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSXWgJ%2FbtsLHOFekRt%2FSuzpOGsw7nVJHcJj0FWu6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;1062&quot; data-filename=&quot;스크린샷 2025-01-07 오후 7.30.32.png&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;1062&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun SnackBarTest() {
    var counter by remember { mutableStateOf(0) }

    val snackbarHostState = remember { SnackbarHostState() }
    val coroutineScope = rememberCoroutineScope()


    Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) },) { padding -&amp;gt;
        Button(onClick = {
            counter++
            coroutineScope.launch {
                snackbarHostState.showSnackbar(
                    message = &quot;Hello, Snackbar! count = $counter&quot;,
                    actionLabel = &quot;close&quot;,
                    duration = SnackbarDuration.Short)
            }
        }, modifier = Modifier.padding(padding)) {
            Text(&quot;더하기&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- SnackbarHostState: remember { SnackbarHostState() }를 사용하여 Snackbar의 상태 관리&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Scaffold: Scaffold를 사용하여 기본 레이아웃을 설정하고, snackbarHost 매개변수에 SnackbarHost(snackbarHostState)를 전달&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;CoroutineScope내에서 실행하는 이유&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;: Snackbar의 showSnackbar 메서드는 suspend 함수로 정의됨 -&amp;gt; Snackbar는 CoroutineScope 내에서 실행되어야 한다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-07 오후 7.35.16.png&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcGtws/btsLFKj9DW2/BXvPcRzSYbddm3Px8zvUhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcGtws/btsLFKj9DW2/BXvPcRzSYbddm3Px8zvUhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcGtws/btsLFKj9DW2/BXvPcRzSYbddm3Px8zvUhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcGtws%2FbtsLFKj9DW2%2FBXvPcRzSYbddm3Px8zvUhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1440&quot; height=&quot;398&quot; data-filename=&quot;스크린샷 2025-01-07 오후 7.35.16.png&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>android snackbar</category>
      <category>compose</category>
      <category>compose snackbar</category>
      <category>compose snackbar coroutine</category>
      <category>compose snackbar show</category>
      <category>compose toastbar</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/134</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Snackbar#entry134comment</comments>
      <pubDate>Tue, 7 Jan 2025 19:36:40 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] ConstraintLayout, ConstraintSet</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Constraint-layout</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;library 추가 필요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[libs.version.toml]&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;[versions]
//...
constaraintLayoutVersion = &quot;1.1.0&quot;

[libraries]
//...
androidx-constraint-layout = { group = &quot;androidx.constraintlayout&quot;, name = &quot;constraintlayout-compose&quot;, version.ref = &quot;constaraintLayoutVersion&quot; }

[plugins]
//...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;createRefs()&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- constraintLayout&amp;nbsp;안에&amp;nbsp;있는&amp;nbsp;여러&amp;nbsp;UI&amp;nbsp;요소(예:&amp;nbsp;버튼,&amp;nbsp;텍스트&amp;nbsp;등)를&amp;nbsp;서로&amp;nbsp;연결하고&amp;nbsp;배치할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;참조를&amp;nbsp;생성&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Stable
fun createRefs(): ConstraintLayoutScope.ConstrainedLayoutReferences =
    referencesObject ?: ConstrainedLayoutReferences().also { referencesObject = it }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ConstrainedLayoutReferences를 보면 총 16개까지의 제약 조건을 설정가능,&amp;nbsp; 16개 이상이 필요하면 여러번 호출하면 된다&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;inner class ConstrainedLayoutReferences internal constructor() {
    operator fun component1(): ConstrainedLayoutReference = createRef()

    operator fun component2(): ConstrainedLayoutReference = createRef()

    operator fun component3(): ConstrainedLayoutReference = createRef()

    operator fun component4(): ConstrainedLayoutReference = createRef()

    operator fun component5(): ConstrainedLayoutReference = createRef()

    operator fun component6(): ConstrainedLayoutReference = createRef()

    operator fun component7(): ConstrainedLayoutReference = createRef()

    operator fun component8(): ConstrainedLayoutReference = createRef()

    operator fun component9(): ConstrainedLayoutReference = createRef()

    operator fun component10(): ConstrainedLayoutReference = createRef()

    operator fun component11(): ConstrainedLayoutReference = createRef()

    operator fun component12(): ConstrainedLayoutReference = createRef()

    operator fun component13(): ConstrainedLayoutReference = createRef()

    operator fun component14(): ConstrainedLayoutReference = createRef()

    operator fun component15(): ConstrainedLayoutReference = createRef()

    operator fun component16(): ConstrainedLayoutReference = createRef()
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;val (first, second, third) = createRefs()

Box(
    modifier = Modifier.size(40.dp)
        .background(Color.Red)
        .constrainAs(first) {//box가 어디에 위치해야할지 랜더링
            bottom.linkTo(parent.bottom)
        }
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시로 본다면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Box는 first가 되며 box의 bottom은 parent의 bottom에 연결된다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;기존의 constraintBottomToBottom=&quot;parent&quot;의 의미&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun linkTo(
    anchor: ConstraintLayoutBaseScope.HorizontalAnchor,
    margin: Dp = 0.dp,
    goneMargin: Dp = 0.dp
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;linkTo는 위와 같이 margin, goneMargin 값도 설정 가능하다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;ConstraintLayout( modifier = Modifier.fillMaxSize()) {
     // 여러개의 reference return
    val (first, second, third) = createRefs()

    Box(
        modifier = Modifier.size(40.dp)
            .background(Color.Red)
            .constrainAs(first) {//box가 어디에 위치해야할지 랜더링
                top.linkTo(parent.top, margin = 8.dp)
            }
    )

    Text(
        text = &quot;second Text&quot;,
        modifier = Modifier.constrainAs(second) {
            top.linkTo(first.bottom)
        }
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;linkTo는 꼭 parent에 붙는게 아니다 (xml에서의 constraint를 생각할 것)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-07 오후 2.45.29.png&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dSSbbA/btsLGPq9Q1h/nmxgUT00GeQRVwqbKTwYeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dSSbbA/btsLGPq9Q1h/nmxgUT00GeQRVwqbKTwYeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dSSbbA/btsLGPq9Q1h/nmxgUT00GeQRVwqbKTwYeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdSSbbA%2FbtsLGPq9Q1h%2FnmxgUT00GeQRVwqbKTwYeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;636&quot; height=&quot;138&quot; data-filename=&quot;스크린샷 2025-01-07 오후 2.45.29.png&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가운데 정렬은 기존의 constraint에서와 달리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;centerTo(ref), centerVerticallyTo(ref), centerHorizontallyTo(ref)로 사용 가능&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;Text(
    text = &quot;second Text&quot;,
    modifier = Modifier.constrainAs(second) {
        centerTo(parent)
    }
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Chain&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 3개의 ref를 선언한 후&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;val (first, second, third) = createRefs()

Box(
    modifier = Modifier.size(40.dp)
        .background(Color.Red)
        .constrainAs(first) {

        }
)

Box(
    modifier = Modifier.size(40.dp)
        .background(Color.Cyan)
        .constrainAs(second) {

        }
)

Box(
    modifier = Modifier.size(40.dp)
        .background(Color.Black)
        .constrainAs(third) {

        }
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;createVerticalChain(first, second, third)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;수직으로 3가지 ref의 체인을 생성하면 동일한 간격으로 수평 체인이 생성됩니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-07 오후 3.34.34.png&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;970&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dv0Xzr/btsLGbOPoSC/FKrYKz56JwrxvBU8e555uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dv0Xzr/btsLGbOPoSC/FKrYKz56JwrxvBU8e555uk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dv0Xzr/btsLGbOPoSC/FKrYKz56JwrxvBU8e555uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdv0Xzr%2FbtsLGbOPoSC%2FFKrYKz56JwrxvBU8e555uk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;338&quot; height=&quot;686&quot; data-filename=&quot;스크린샷 2025-01-07 오후 3.34.34.png&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;970&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;param으로 ChainStyle을 지정할 수 있는데 (Packed, Spread(default), SpreadInside)&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;createVerticalChain(first, second, third, chainStyle = ChainStyle.Packed)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rHzwp/btsLGcfWf8R/rOZFsfvEF41PUgVNoeWvNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rHzwp/btsLGcfWf8R/rOZFsfvEF41PUgVNoeWvNK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;612&quot; data-filename=&quot;edited_스크린샷 2025-01-07 오후 3.36.43.png&quot; width=&quot;225&quot; data-widthpercent=&quot;49.92&quot; style=&quot;width: 49.3377%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rHzwp/btsLGcfWf8R/rOZFsfvEF41PUgVNoeWvNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrHzwp%2FbtsLGcfWf8R%2FrOZFsfvEF41PUgVNoeWvNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmXrv7/btsLFQElZ7c/lgFwZex3St2Yi3yFHIKPs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmXrv7/btsLFQElZ7c/lgFwZex3St2Yi3yFHIKPs0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;610&quot; data-filename=&quot;edited_스크린샷 2025-01-07 오후 3.37.14.png&quot; width=&quot;320&quot; height=&quot;651&quot; data-widthpercent=&quot;50.08&quot; style=&quot;width: 49.4995%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmXrv7/btsLFQElZ7c/lgFwZex3St2Yi3yFHIKPs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmXrv7%2FbtsLFQElZ7c%2FlgFwZex3St2Yi3yFHIKPs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;610&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;packed / spreadInside&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Packed&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 뷰들이 서로 가까이 모여서 배치&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 체인 내의 뷰들은 중앙에 위치하며, 남는 공간은 양쪽에 균등하게 분배&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spread&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 뷰들이 체인 내에서 가능한 한 멀리 떨어져 배치&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 체인 내의 뷰들은 양쪽 끝에 위치하고(끝 사이에 공백있음), 남는 공간은 뷰들 사이에 균등하게 분배&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SpreadInside&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 이 스타일은 Spread와 유사하지만, 체인의 양 끝에 있는 뷰는 끝에 고정되고, 나머지 뷰들 사이에만 간격이 균등하게 분배&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 즉, 체인 내의 첫 번째와 마지막 뷰는 끝에 위치하고, 중간 뷰들 사이에만 간격 생김&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Barrier&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 여러 뷰의 경계를 기준으로 제약 조건 설정&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Barrier는 수평 또는 수직 방향으로 설정 가능&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-07 오후 3.50.13.png&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3g3Oj/btsLGN1hq8L/GQ9otTzr8eMEhh7WzpUUxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3g3Oj/btsLGN1hq8L/GQ9otTzr8eMEhh7WzpUUxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3g3Oj/btsLGN1hq8L/GQ9otTzr8eMEhh7WzpUUxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3g3Oj%2FbtsLGN1hq8L%2FGQ9otTzr8eMEhh7WzpUUxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1110&quot; height=&quot;204&quot; data-filename=&quot;스크린샷 2025-01-07 오후 3.50.13.png&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;204&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;val (first, second, third, txt) = createRefs()

Box(
    modifier = Modifier.size(40.dp)
        .background(Color.Red)
        .constrainAs(first) {
            top.linkTo(parent.top)
        }
)

Box(
    modifier = Modifier.size(40.dp)
        .background(Color.Cyan)
        .constrainAs(second) {
            top.linkTo(parent.top)
        }
)

Box(
    modifier = Modifier.size(40.dp)
        .background(Color.Black)
        .constrainAs(third) {
            top.linkTo(parent.top, margin = 32.dp)
        }

)

createHorizontalChain(first, second, third, chainStyle = ChainStyle.SpreadInside)

val barrier = createBottomBarrier(first, second, third)

Text(&quot; barrier 위치를 잘 보세요&quot;,
    modifier = Modifier.constrainAs(txt) {
        top.linkTo(barrier)
        end.linkTo(parent.end)
    }
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 한다면 margin이 적용된 third의 bottom을 기준으로 barrier가 생긴다&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-07 오후 3.49.36.png&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccsr0D/btsLG5N2DU5/7G6SwJaCFs36ahzrvQU4GK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccsr0D/btsLG5N2DU5/7G6SwJaCFs36ahzrvQU4GK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccsr0D/btsLG5N2DU5/7G6SwJaCFs36ahzrvQU4GK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fccsr0D%2FbtsLG5N2DU5%2F7G6SwJaCFs36ahzrvQU4GK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;490&quot; height=&quot;142&quot; data-filename=&quot;스크린샷 2025-01-07 오후 3.49.36.png&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ConstraintSet&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 제약 조건을 modifier에서 선언하지 않고 ConstraintSet을 선언해 밖으로 빼내는 방법&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;    val constraintSet = ConstraintSet { //여기서 제약 설정
        val box = createRefFor(&quot;box&quot;)
        val txt = createRefFor(&quot;txt&quot;)

        constrain(box) {
            top.linkTo(parent.top, margin = 8.dp)
        }

        constrain(txt) {
            centerTo(parent)
        }
    }

    ConstraintLayout(modifier = Modifier.fillMaxSize(), constraintSet = constraintSet) {
        Box(
            modifier = Modifier.size(40.dp)
                .background(Color.Red)
                .layoutId(&quot;box&quot;)
//                .constrainAs(first) {
//                    top.linkTo(parent.top, margin = 8.dp)
//                }
        )

        Text(
            text = &quot;second Text&quot;,
            modifier = Modifier
                .layoutId(&quot;txt&quot;)
//            modifier = Modifier.constrainAs(second) {
//                centerTo(parent)
//            }
        )
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;1️⃣ C&lt;/span&gt;onstraintSet 선언 createRefFor(id)를 통해 식별자 생성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;2️⃣ Constrain(원하는 ref) { 원하는 제약 조건 설정 }&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3️⃣ ConstraintLayout에 param으로 ConstraintSet 선언&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;4️⃣ ConstraintLayout에서 원하는 제약 조건이 필요한 곳에 Modifier의 layoutId에 ConstraintSet에서 설정한 id를 설정&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;레이아웃 구조가 복잡해지면 ConstraintSet으로 빼내는 게 더 좋을 거 같다 &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>android constraintlayout</category>
      <category>android constraintset</category>
      <category>compose chainstyle</category>
      <category>compose constrainas</category>
      <category>compose constraint</category>
      <category>compose constraint chain</category>
      <category>compose constraintlayout</category>
      <category>compose constraintlayout linkto</category>
      <category>compose constraintset</category>
      <category>compose linkto</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/132</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Constraint-layout#entry132comment</comments>
      <pubDate>Tue, 7 Jan 2025 14:47:15 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Recomposition</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Recomposition</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-06 오후 8.12.06.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QA2SX/btsLF1LJ085/mYDUciMylZuWEuUMc0x3dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QA2SX/btsLF1LJ085/mYDUciMylZuWEuUMc0x3dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QA2SX/btsLF1LJ085/mYDUciMylZuWEuUMc0x3dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQA2SX%2FbtsLF1LJ085%2FmYDUciMylZuWEuUMc0x3dk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;492&quot; height=&quot;210&quot; data-filename=&quot;스크린샷 2025-01-06 오후 8.12.06.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;376&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Composition&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- composable을 호출해 만들어지는 것으로 ui를 기술하는 composable의 트리 구조&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-06 오후 8.30.28.png&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;1020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Smaml/btsLGlQKqKB/LX1HIm1UEiKp0v7m0Mukqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Smaml/btsLGlQKqKB/LX1HIm1UEiKp0v7m0Mukqk/img.png&quot; data-alt=&quot;compose의 단계&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Smaml/btsLGlQKqKB/LX1HIm1UEiKp0v7m0Mukqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSmaml%2FbtsLGlQKqKB%2FLX1HIm1UEiKp0v7m0Mukqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;701&quot; height=&quot;446&quot; data-filename=&quot;스크린샷 2025-01-06 오후 8.30.28.png&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;1020&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;compose의 단계&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Composition&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- composable fun을 실행하고 ui를 표시&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Layout&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- ui를 배치&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Draw&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- ui를 랜더링 즉, 캔버스에 그림&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;⭐️ Recomposition&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- state가 바뀌었을 때 변경된 state를 반영하기 위해 Composition 업데이트&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- ui를 변경하기 위해선 state가 변경되어야 함&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Recomposition을 조절하려면 ?&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Stable이 없어도 compose complier가 안정적으로 간주하는 타입 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 모든 Primitive 타입, 문자열, 모든 함수 타입&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위의 타입들이 아니라면 안정적이라고 간주할 수 없을 때 (ex.&amp;nbsp;리스트, 맵, 사용자 정의 객체 등) @Stable 표기해야하면 된다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;@Stable&lt;/b&gt;을&amp;nbsp;사용해야&amp;nbsp;하는&amp;nbsp;이유&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;성능 최적화: Compose는 해당 타입의 인스턴스가 변경되지 않는다고 가정하고, 불필요한 재조정을 방지 -&amp;gt; 성능 향상&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⚠️&amp;nbsp; 리스트와 같은&amp;nbsp; UI 요소를 다룰 때, 각 아이템에 &lt;b&gt;고유한 키를 부여하는 것이 중요&lt;/b&gt;합니다. 키가 없거나 잘못된 경우, Compose는 아이템을 올바르게 추적하지 못해 불필요한 recomposition이 발생할 수 있습니다&lt;/span&gt;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>@stable</category>
      <category>android recomposition</category>
      <category>compose</category>
      <category>compose @stable</category>
      <category>compose recomposition</category>
      <category>compose stable</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/131</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Recomposition#entry131comment</comments>
      <pubDate>Mon, 6 Jan 2025 20:37:57 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] scaffold</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-scaffold</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;scaffold&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- slot api의 확장&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- paramater로 여러 슬롯을 제공하여 다양한 UI(ex : topBar, bottomBar, floatingActionButton, drawerContent, content)을 쉽게 정의하고 구성 가능&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 기본틀로 사용하면 체계적으로 개발하기 좋다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Scaffold는 기본적으로 content 슬롯에 패딩을 적용해야하고 아니면 아래와 같은 에러가 뜬다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Content&amp;nbsp;padding&amp;nbsp;parameter&amp;nbsp;it&amp;nbsp;is&amp;nbsp;not&amp;nbsp;used&amp;nbsp;More&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ScaffoldTest() {
    var checked by remember { mutableStateOf(false) }

    Scaffold(
        topBar = {
            TopAppBar(
                navigationIcon =  {
                    IconButton(onClick = { }) {
                        Image(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
                    }
                },
                title = { Text(&quot;Scaffold TopBar&quot;) }
            )
        }
    ) { paddingValues -&amp;gt;
        Surface(modifier = Modifier.padding(paddingValues)) {
            CheckBoxText(checked, onCheckedChanged = {
                checked = !checked
            }) {
                Text(&quot;check box&quot;)
            }
        }
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Composable
fun CheckBoxText(checked: Boolean,
                 onCheckedChanged: () -&amp;gt; Unit,
                 content: @Composable RowScope.() -&amp;gt; Unit) {
    Row(verticalAlignment = Alignment.CenterVertically,
        modifier = Modifier.clickable {
            onCheckedChanged()
        }) {
        Checkbox(checked = checked,
            onCheckedChange = {
                onCheckedChanged()
            })
        content()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>android compose</category>
      <category>android compose scaffold</category>
      <category>android scaffold</category>
      <category>compose scaffold</category>
      <category>compose slot</category>
      <category>compose slot api</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/130</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-scaffold#entry130comment</comments>
      <pubDate>Mon, 6 Jan 2025 16:46:24 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Slot API</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Slot-API</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Composable 내에서 다른 Composable을 사용할 수 있도록 하는 패턴&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736147745176&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MyCustomCard(
    title: String,
    content: @Composable () -&amp;gt; Unit // Slot API를 사용하여 Composable을 매개변수로 받음
) {
    Card {
        Column {
            Text(text = title)
            content() // 전달된 Composable을 호출
        }
    }
}

@Composable
fun MyScreen() {
    MyCustomCard(title = &quot;Hello&quot;) {
        Text(&quot;This is the content of the card.&quot;) // Slot에 전달된 Composable
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보통 content를 마지막 인자로 받아서 사용하는 경우가 많다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;➡️ 고정된 ui를 가지지 않기에 유연성 , 재사용성에서 좋다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 content를 받지 않는다면 Text는 고정이 되므로, 좋지 않은 방식이다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1736147910790&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MyCustomCard(title: String) {
    Card {
        Column {
            Text(text = title)
            Text(&quot;This is the fixed content of the card.&quot;) // 고정된 내용
        }
    }
}

@Composable
fun MyScreen() {
    MyCustomCard(title = &quot;Hello&quot;) // 이제 content를 전달할 필요 없음
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 특정 Scope에서만 동작하는 composable함수라면&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1736148306834&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MyCustomCard(
    title: String,
    content: @Composable RowScope.() -&amp;gt; Unit // RowScope를 사용하여 Composable을 매개변수로 받음
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위와 같이 content에 RowScope 혹은 ColumnScope를 지정할 수도 있다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;재사용성이 높아지도록 위임할 수 있는 것들은 최대한 위임해주는 게 좋다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>android compose slot</category>
      <category>android compose slot api</category>
      <category>compose</category>
      <category>compose slot</category>
      <category>compose slot api</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/129</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Slot-API#entry129comment</comments>
      <pubDate>Mon, 6 Jan 2025 16:26:55 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Component 알아보기 - TopAppBar</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Component-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-TopAppBar</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;Column {
    TopAppBar(
        title = { Text(text = &quot;보통의 앱 바&quot;) },
        navigationIcon = {
            IconButton(onClick = {}) {
                Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack,
                    contentDescription = null)
            }
        },
        actions = { // row Scope
            IconButton(onClick = {}) {
                Icon(imageVector = Icons.Filled.Edit,
                    contentDescription = null)
            }

            IconButton(onClick = {}) {
                Icon(imageVector = Icons.Filled.Check,
                    contentDescription = null)
            }
        }
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-04 오후 1.48.43.png&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpVUok/btsLDVrOHvi/CH7No83AOyMojvq4UcJY2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpVUok/btsLDVrOHvi/CH7No83AOyMojvq4UcJY2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpVUok/btsLDVrOHvi/CH7No83AOyMojvq4UcJY2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpVUok%2FbtsLDVrOHvi%2FCH7No83AOyMojvq4UcJY2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;540&quot; height=&quot;208&quot; data-filename=&quot;스크린샷 2025-01-04 오후 1.48.43.png&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이런 식으로 생성된다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;조절할 일은 아이콘 설정, 클릭 시 동작 이 정도일 거 같긴 하다 ..!&amp;nbsp;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>android compose</category>
      <category>compose</category>
      <category>compose appbar</category>
      <category>compose toolbar</category>
      <category>compose topappbar</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/128</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Component-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-TopAppBar#entry128comment</comments>
      <pubDate>Sat, 4 Jan 2025 13:50:11 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Component 알아보기 - TextField</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Component-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-TextField</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;TextField = EditText&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;var name by remember { mutableStateOf(&quot;Myday&quot;) }
Column(modifier = Modifier.padding(4.dp)) {
    TextField(
        value = name,
        label = {
            Text(text = &quot;name&quot;)
        },
        onValueChange = {
            name = it
        })
    
    Text(text = &quot;입력값 $name&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-04 오후 1.35.32.png&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uTSi3/btsLEotIFpM/jGeeTDpqimkJkpG8SG2QVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uTSi3/btsLEotIFpM/jGeeTDpqimkJkpG8SG2QVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uTSi3/btsLEotIFpM/jGeeTDpqimkJkpG8SG2QVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuTSi3%2FbtsLEotIFpM%2FjGeeTDpqimkJkpG8SG2QVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;364&quot; height=&quot;119&quot; data-filename=&quot;스크린샷 2025-01-04 오후 1.35.32.png&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Outline을 설정하고 싶으면&amp;nbsp; OutlinedTextField를 사용하면 된다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;var name by remember { mutableStateOf(&quot;Myday&quot;) }
Column(modifier = Modifier.padding(4.dp)) {
    OutlinedTextField(
        value = name,
        label = {
            Text(text = &quot;name&quot;)
        },
        onValueChange = {
            name = it
        })

    Text(text = &quot;입력값 $name&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-04 오후 1.35.58.png&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmCxGn/btsLEeq8Q3D/M3knOK6f7pRiLmHGMFEVR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmCxGn/btsLEeq8Q3D/M3knOK6f7pRiLmHGMFEVR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmCxGn/btsLEeq8Q3D/M3knOK6f7pRiLmHGMFEVR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmCxGn%2FbtsLEeq8Q3D%2FM3knOK6f7pRiLmHGMFEVR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;363&quot; height=&quot;196&quot; data-filename=&quot;스크린샷 2025-01-04 오후 1.35.58.png&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;196&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설정할 수 있는 값은 정말 많다..!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;디자인에 따라서 여러개 조합해보면 재밌을거 같다 &lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Composable
fun TextField(
    value: String,
    onValueChange: (String) -&amp;gt; Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    readOnly: Boolean = false,
    textStyle: TextStyle = LocalTextStyle.current,
    label: @Composable (() -&amp;gt; Unit)? = null,
    placeholder: @Composable (() -&amp;gt; Unit)? = null,
    leadingIcon: @Composable (() -&amp;gt; Unit)? = null,
    trailingIcon: @Composable (() -&amp;gt; Unit)? = null,
    prefix: @Composable (() -&amp;gt; Unit)? = null,
    suffix: @Composable (() -&amp;gt; Unit)? = null,
    supportingText: @Composable (() -&amp;gt; Unit)? = null,
    isError: Boolean = false,
    visualTransformation: VisualTransformation = VisualTransformation.None,
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
    keyboardActions: KeyboardActions = KeyboardActions.Default,
    singleLine: Boolean = false,
    maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
    minLines: Int = 1,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    shape: Shape = TextFieldDefaults.shape,
    colors: TextFieldColors = TextFieldDefaults.colors()
) &lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>  Compose</category>
      <category>android compose</category>
      <category>android compose edittext</category>
      <category>android textfield</category>
      <category>compose edittext</category>
      <category>compose textfield</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/127</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Component-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-TextField#entry127comment</comments>
      <pubDate>Sat, 4 Jan 2025 13:38:22 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Component 알아보기 - CheckBox</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Component-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-CheckBox</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CheckBox&lt;/span&gt;&lt;/h4&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;Row(verticalAlignment = Alignment.CenterVertically) {
    Checkbox(checked = false, onCheckedChange = {

    })
    Text(text = &quot;checkbox를 테스트해보자&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 기존 안드로이드 view와는 달리 체크박스를 눌러도 ui상에 아무 변화가 없음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- checked 값이 바뀌어야만 ui가 갱신된다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그럼 이렇게 하면 될까 ?&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;❌ 변경되지 않는다 (primitive값만 바꾼다고 반영이 되는 게 아님)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;compose에서는 Composable 함수 즉, recomposition을 시켜야만 반영된다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;var checked = false
Checkbox(checked = checked, onCheckedChange = {
    checked = !checked
})
Text(text = &quot;checkbox를 테스트해보자&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그럼 상태를 변경시켜보자&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;주의해야 할 점 아래와 같이 사용하면&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;var checked = mutableStateOf(false)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-04 오후 12.45.53.png&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTfEb4/btsLDMWatow/bHFveWxhYLmlvFUTGmVHY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTfEb4/btsLDMWatow/bHFveWxhYLmlvFUTGmVHY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTfEb4/btsLDMWatow/bHFveWxhYLmlvFUTGmVHY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTfEb4%2FbtsLDMWatow%2FbHFveWxhYLmlvFUTGmVHY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1194&quot; height=&quot;202&quot; data-filename=&quot;스크린샷 2025-01-04 오후 12.45.53.png&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같은 에러가 뜬다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;Creating a state object during composition without using remember&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Composable 함수 내에서 상태값을 생성하는데 remember를 쓰고 있지 않다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;발생원인 : Composable 내에서 ui가 그려질 때 조금 그려지다가 다시 그려질 수 있는데(recomposition이 언제 발생할지 알 수 없다), remember가 없다면 상태값이 날아가버린다&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;i&gt;즉, Composable은 언제든 다시 그려질 수 있다(recomposition) 그래서 상태값 주의해야한다&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다시 본문으로 돌아가 체크박스의 상태를 변화하려면 아래와 같이 사용해야한다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;Row(verticalAlignment = Alignment.CenterVertically) {

    var checked = remember { mutableStateOf(false) }

    Checkbox(checked = checked.value, onCheckedChange = {
        checked.value = !checked.value
    })
    Text(text = &quot;checkbox를 테스트해보자&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 mutableStateOf의 값을 사용할 때 일일이 .value를 하기 번거롭다면&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;var checked by remember { mutableStateOf(false) }
Checkbox(checked = checked, onCheckedChange = {
    checked = !checked
})

Text(text = &quot;checkbox를 테스트해보자&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 delegated 형식으로 사용하면 된다 아래의 방식을 사용하는 거라고 보면 된다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;/**
 * Permits property delegation of `var`s using `by` for [MutableState].
 *
 * @sample androidx.compose.runtime.samples.DelegatedStateSample
 */
@Suppress(&quot;NOTHING_TO_INLINE&quot;)
inline operator fun &amp;lt;T&amp;gt; MutableState&amp;lt;T&amp;gt;.setValue(thisObj: Any?, property: KProperty&amp;lt;*&amp;gt;, value: T) {
    this.value = value
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구조분해 사용하기&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;MutableState를 살펴보자면&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 값을 나타내는 value&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 값을 반환하는 component1 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;-값을 설정하는 component2&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@Stable
interface MutableState&amp;lt;T&amp;gt; : State&amp;lt;T&amp;gt; {
    override var value: T
    operator fun component1(): T
    operator fun component2(): (T) -&amp;gt; Unit
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;여기서 구조분해를 한다면 componentN에 해당하는 component1(getter), component2(setter)가 구조분해된다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그래서 아래와 같은 형태로도 쓸 수 있다&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;val (checked, setChecked) = remember { mutableStateOf(false) }

Checkbox(checked = checked, onCheckedChange = {
    //체크됐을 때 호출되므로 it 자체가 변경된 checked 값
    setChecked(it)
})

Text(text = &quot;checkbox를 테스트해보자&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>  Compose</category>
      <category>compose checkbox</category>
      <category>compose delegate</category>
      <category>compose mutablestateof</category>
      <category>compose recomposition</category>
      <category>compose remember</category>
      <category>compose state</category>
      <category>recomposition</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/126</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Component-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-CheckBox#entry126comment</comments>
      <pubDate>Sat, 4 Jan 2025 13:15:22 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Component 알아보기 - Image, Coil</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Component-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-Image-Coil</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Image&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;네트워크 없이 간단한 Image를 설정하는 3가지 방법이 있다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. painter = painterResource 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. imageVector = 벡터 이미지 사용&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. bitmap 사용&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;    Column {
        Image(
            painter = painterResource(R.drawable.simpson),
            contentDescription = &quot;simpson&quot;)

        Image(
            imageVector = Icons.Filled.Face,
            contentDescription = &quot;vector&quot;
        )

        //bitmap은 순수 Composable 함수에선 사용할 수 없고
        //context가 있는 곳에서 주입받아야 한다
//        Image(
//            bitmap = ,
//            contentDescription = null
//        )
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-03 오후 8.34.33.png&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;994&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9NRhx/btsLDJdVaJ5/CqHopOiCfP3c4ycplNAc8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9NRhx/btsLDJdVaJ5/CqHopOiCfP3c4ycplNAc8K/img.png&quot; data-alt=&quot;기여운 심슨..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9NRhx/btsLDJdVaJ5/CqHopOiCfP3c4ycplNAc8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9NRhx%2FbtsLDJdVaJ5%2FCqHopOiCfP3c4ycplNAc8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;336&quot; height=&quot;994&quot; data-filename=&quot;스크린샷 2025-01-03 오후 8.34.33.png&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;994&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;기여운 심슨..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Coil 라이브러리 사용해서 이미지 가져오기&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;coil 라이브러리 추가하기&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. libs.version.toml&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;[versions]
coilVersion = &quot;2.2.2&quot;
coilComposeVersion = &quot;2.2.2&quot;

[libraries]
androidx-coil = { group = &quot;io.coil-kt&quot;, name = &quot;coil&quot;, version.ref = &quot;coilVersion&quot; }
androidx-coil-compose = { group = &quot;io.coil-kt&quot;, name = &quot;coil-compose&quot;, version.ref = &quot;coilComposeVersion&quot; }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. build.gradle (app)&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;implementation(libs.androidx.coil)
implementation(libs.androidx.coil.compose)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;rememberImagePainter&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(deprecated됨..)&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;val painter = rememberImagePainter(
    data = &quot;https://media.contentapi.ea.com/content/dam/eacom/images/2023/07/simpsons-hero-md-homepage-7x2-xl.jpg.adapt.crop7x2.1920w.jpg&quot;

)
Column {
    Image(
        painter = painter,
        contentDescription = null)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;AsyncImage&lt;/h4&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;AsyncImage(
    model = &quot;https://media.contentapi.ea.com/content/dam/eacom/images/2023/07/simpsons-hero-md-homepage-7x2-xl.jpg.adapt.crop7x2.1920w.jpg&quot;,
    contentDescription = null
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>coil</category>
      <category>compose coil</category>
      <category>compose coil image</category>
      <category>compose coil implementation</category>
      <category>compose image</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/125</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Component-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-Image-Coil#entry125comment</comments>
      <pubDate>Fri, 3 Jan 2025 21:13:29 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Component 알아보기 - BoxWithConstraints</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Component-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-BoxWithConstraints</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;BoxWithConstraints&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 부모의 제약 조건을 기반으로 크기를 조정&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 부모의 최대, 최소 크기의 정보를 제공&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun BoxWithConstraintsTest(modifier: Modifier = Modifier) {
    BoxWithConstraints(
        modifier = modifier
    ) { //box scope
        Text(&quot;maxW: $maxWidth minW: $minWidth&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;선언된 BoxWithConstraintsTest를 사용해서 값을 modifier에 값을 넣어주면 전달된 값이 찍힌다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun Greeting() {
    Column {
        BoxWithConstraintsTest(Modifier.widthIn(min = 100.dp))
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-03 오후 8.20.44.png&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btyUyp/btsLDnWuqHX/7fg9kEHFHfLDI0vqgY2Ysk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btyUyp/btsLDnWuqHX/7fg9kEHFHfLDI0vqgY2Ysk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btyUyp/btsLDnWuqHX/7fg9kEHFHfLDI0vqgY2Ysk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtyUyp%2FbtsLDnWuqHX%2F7fg9kEHFHfLDI0vqgY2Ysk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;368&quot; height=&quot;160&quot; data-filename=&quot;스크린샷 2025-01-03 오후 8.20.44.png&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>android compose</category>
      <category>boxwithconstraints</category>
      <category>compose boxwithconstraints</category>
      <category>compose constraint</category>
      <category>compose maxheight</category>
      <category>compose maxwidth</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/124</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Component-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-BoxWithConstraints#entry124comment</comments>
      <pubDate>Fri, 3 Jan 2025 20:27:30 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Component 알아보기 - Box, Row,  Column</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Component-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-Box-Row-Column</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Box&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; Box 자체로 쓰거나 FrameLayout처럼 중첩시킬 때 사용된다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;BoxScope내의 확장함수 2개 align, matchParentSize&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-03 오후 5.27.07.png&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfNZqv/btsLC3X8MyD/k55koLbe6lVw41XzzWwULK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfNZqv/btsLC3X8MyD/k55koLbe6lVw41XzzWwULK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfNZqv/btsLC3X8MyD/k55koLbe6lVw41XzzWwULK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfNZqv%2FbtsLC3X8MyD%2Fk55koLbe6lVw41XzzWwULK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;754&quot; height=&quot;448&quot; data-filename=&quot;스크린샷 2025-01-03 오후 5.27.07.png&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Modifier.align&lt;/span&gt;&lt;/h4&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Box(modifier = Modifier.size(100.dp)) { // Box Scope
    Box(modifier = Modifier.size(70.dp).background(Color.Black).align(Alignment.CenterStart))
    Box(modifier = Modifier.size(70.dp).background(Color.Magenta).align(Alignment.BottomEnd))
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-03 오후 5.24.09.png&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;898&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IO3kc/btsLD1FjGSq/sddlDRJEiKvY1KGtNlEipK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IO3kc/btsLD1FjGSq/sddlDRJEiKvY1KGtNlEipK/img.png&quot; data-alt=&quot;중첩되는 걸 볼 수 있따&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IO3kc/btsLD1FjGSq/sddlDRJEiKvY1KGtNlEipK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIO3kc%2FbtsLD1FjGSq%2FsddlDRJEiKvY1KGtNlEipK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;258&quot; height=&quot;275&quot; data-filename=&quot;스크린샷 2025-01-03 오후 5.24.09.png&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;898&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;중첩되는 걸 볼 수 있따&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Modifier.matchParentSize()&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 자식&amp;nbsp;컴포넌트&amp;nbsp;중&amp;nbsp;가장&amp;nbsp;큰&amp;nbsp;크기에&amp;nbsp;의해&amp;nbsp;결정&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 두번째 box의 사이즈 80만큼만 생성된다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;Box { // Box Scope
    Box(modifier = Modifier.matchParentSize().background(Color.Black).align(Alignment.CenterStart))
    Box(modifier = Modifier.size(80.dp).background(Color.Magenta).align(Alignment.Center))
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-03 오후 5.29.30.png&quot; data-origin-width=&quot;282&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zlKVg/btsLDeyzH4M/1MFYkBsIHKSdTMpDoMFRL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zlKVg/btsLDeyzH4M/1MFYkBsIHKSdTMpDoMFRL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zlKVg/btsLDeyzH4M/1MFYkBsIHKSdTMpDoMFRL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzlKVg%2FbtsLDeyzH4M%2F1MFYkBsIHKSdTMpDoMFRL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;282&quot; height=&quot;194&quot; data-filename=&quot;스크린샷 2025-01-03 오후 5.29.30.png&quot; data-origin-width=&quot;282&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fillMaxSize를 사용하면 그릴 수 있는 최대 영역만큼 그려지게 된다&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Box { // Box Scope
    Box(modifier = Modifier.fillMaxSize().background(Color.Black).align(Alignment.CenterStart))
    Box(modifier = Modifier.size(80.dp).background(Color.Magenta).align(Alignment.Center))
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-03 오후 5.31.23.png&quot; data-origin-width=&quot;474&quot; data-origin-height=&quot;1014&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqUdbS/btsLDHtypQF/kb3bHfkO5SfBa0Okfa48J0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqUdbS/btsLDHtypQF/kb3bHfkO5SfBa0Okfa48J0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqUdbS/btsLDHtypQF/kb3bHfkO5SfBa0Okfa48J0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqUdbS%2FbtsLDHtypQF%2Fkb3bHfkO5SfBa0Okfa48J0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;297&quot; height=&quot;635&quot; data-filename=&quot;스크린샷 2025-01-03 오후 5.31.23.png&quot; data-origin-width=&quot;474&quot; data-origin-height=&quot;1014&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Row&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 기본적으로 Horizontal 정렬 따라서&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;align은 항상 Vertical만 가능, arrangement는 row의 방향과 같이간다(Horizontal)&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;Row(modifier = Modifier.height(40.dp)) {
    Text(text= &quot;First&quot;, modifier = Modifier.align(Alignment.Top))
    Text(text= &quot;Second&quot;, modifier = Modifier.align(Alignment.CenterVertically))
    //Alignment.Center는 사용 불가 -&amp;gt; Center는 Horizontal, Vertical 둘 다 Center를 의미
    Text(text= &quot;Third&quot;, modifier = Modifier.align(Alignment.Bottom))
    //Alignment.BottomStart, BottomEnd도 같은 이유로 사용 불가 
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;❌ Alignment.Center 사용 불가 -&amp;gt; Center는 Horizontal, Vertical 둘 다 Center를 의미&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;❌ &lt;/span&gt;Alignment.BottomStart, Alignment.BottomEnd도 같은 이유로 사용 불가&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 Row 하위 요소들이 다 같은 방향의 Vertical Align을 가진다면 상위 Box에 Alignment 선언&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(몇몇 하위 요소들도 개별적으로 설정 가능)&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Row(
    verticalAlignment = Alignment.Bottom,
    modifier = Modifier.height(40.dp)
) {
    Text(text= &quot;First&quot;)
    Text(text= &quot;Second&quot;)
    Text(text= &quot;Third&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-03 오후 5.48.58.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQLVOU/btsLDe6v4sD/cG2xxLFIRupZfN6RUZj3vK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQLVOU/btsLDe6v4sD/cG2xxLFIRupZfN6RUZj3vK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQLVOU/btsLDe6v4sD/cG2xxLFIRupZfN6RUZj3vK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQLVOU%2FbtsLDe6v4sD%2FcG2xxLFIRupZfN6RUZj3vK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;325&quot; height=&quot;284&quot; data-filename=&quot;스크린샷 2025-01-03 오후 5.48.58.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  &lt;b&gt;horizontalArrangement&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 중에서 SpaceBetween, SpaceEvenly, SpaceArround만 비교해보자면&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;SpaceBetween : 자식 component들 사이에 간격&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-03 오후 6.00.07.png&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FLriK/btsLCDFHYdR/A4mpVh7NNO4KaxQxL8Ua5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FLriK/btsLCDFHYdR/A4mpVh7NNO4KaxQxL8Ua5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FLriK/btsLCDFHYdR/A4mpVh7NNO4KaxQxL8Ua5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFLriK%2FbtsLCDFHYdR%2FA4mpVh7NNO4KaxQxL8Ua5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;293&quot; height=&quot;240&quot; data-filename=&quot;스크린샷 2025-01-03 오후 6.00.07.png&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;SpaceEvenly&lt;/span&gt;&amp;nbsp;&lt;/span&gt;: 자식 component들 사이에 동일한 간격, 양 끝에도 동일한 간격 생성&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-03 오후 6.02.13.png&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBJXyN/btsLDM9yw8d/hXRgCKdbySVmR3KTYEnaJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBJXyN/btsLDM9yw8d/hXRgCKdbySVmR3KTYEnaJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBJXyN/btsLDM9yw8d/hXRgCKdbySVmR3KTYEnaJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBJXyN%2FbtsLDM9yw8d%2FhXRgCKdbySVmR3KTYEnaJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;282&quot; height=&quot;87&quot; data-filename=&quot;스크린샷 2025-01-03 오후 6.02.13.png&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;SpaceArround&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;: 자식 component들 사이에 동일한 간격, 양 끝에는 자식 component간 동일한 간격의 절반만큼 간격 생성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;|---&amp;nbsp;A&amp;nbsp;---|---&amp;nbsp;B&amp;nbsp;---|---&amp;nbsp;C&amp;nbsp;---|&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-03 오후 6.04.19.png&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK21r6/btsLDdGxTSQ/fgAl4mkD25WhL5SP2pEp2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK21r6/btsLDdGxTSQ/fgAl4mkD25WhL5SP2pEp2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK21r6/btsLDdGxTSQ/fgAl4mkD25WhL5SP2pEp2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK21r6%2FbtsLDdGxTSQ%2FfgAl4mkD25WhL5SP2pEp2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;303&quot; height=&quot;84&quot; data-filename=&quot;스크린샷 2025-01-03 오후 6.04.19.png&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Column&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 기본적으로 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Vertical&lt;/span&gt; 정렬 따라서 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;align은 항상 Horizontal만 가능, arrangement는 column의 방향과 같이간다(Vertical)&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Column(
    horizontalAlignment = Alignment.Start,
    verticalArrangement = Arrangement.Center,
    modifier = Modifier.size(100.dp)
) {
    Text(text = &quot;First&quot;, modifier = Modifier.background(Color.Blue))
    Text(text = &quot;Second&quot;, modifier = Modifier.background(Color.Cyan))
    Text(text = &quot;Third&quot;, modifier = Modifier.background(Color.Magenta))
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-03 오후 6.17.53.png&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bT60JP/btsLBYwxXBX/Boo84CjtC8oLS4d1KM0sf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bT60JP/btsLBYwxXBX/Boo84CjtC8oLS4d1KM0sf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bT60JP/btsLBYwxXBX/Boo84CjtC8oLS4d1KM0sf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbT60JP%2FbtsLBYwxXBX%2FBoo84CjtC8oLS4d1KM0sf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;293&quot; height=&quot;320&quot; data-filename=&quot;스크린샷 2025-01-03 오후 6.17.53.png&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Box는 2차원 alignment가 가능&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-03 오후 6.21.34.png&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oEa8N/btsLDfEkE51/Ip19uTP1kUly026rKxH681/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oEa8N/btsLDfEkE51/Ip19uTP1kUly026rKxH681/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oEa8N/btsLDfEkE51/Ip19uTP1kUly026rKxH681/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoEa8N%2FbtsLDfEkE51%2FIp19uTP1kUly026rKxH681%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1338&quot; height=&quot;868&quot; data-filename=&quot;스크린샷 2025-01-03 오후 6.21.34.png&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Column과 Row는 각 기본 정렬방향과 반대인 1차원 Alignment만 가능하다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-03 오후 6.22.57.png&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkuxXE/btsLC4COfK0/pVQFJY9u4dTTFDbytCVph1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkuxXE/btsLC4COfK0/pVQFJY9u4dTTFDbytCVph1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkuxXE/btsLC4COfK0/pVQFJY9u4dTTFDbytCVph1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkuxXE%2FbtsLC4COfK0%2FpVQFJY9u4dTTFDbytCVph1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1194&quot; height=&quot;676&quot; data-filename=&quot;스크린샷 2025-01-03 오후 6.22.57.png&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;676&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>compose align</category>
      <category>compose alignment</category>
      <category>compose arrangement</category>
      <category>compose box</category>
      <category>compose column</category>
      <category>compose row</category>
      <category>compose row column</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/123</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Component-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-Box-Row-Column#entry123comment</comments>
      <pubDate>Fri, 3 Jan 2025 18:23:33 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Component 알아보기 - Surface</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Surface</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;surface&lt;/b&gt; : &lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 가장 기본적으로 UI를 구현하는 기반&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-03 오후 5.02.11.png&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;994&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k6cDe/btsLDMVXqi0/ySUKgvLhrOkfECCKON8daK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k6cDe/btsLDMVXqi0/ySUKgvLhrOkfECCKON8daK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k6cDe/btsLDMVXqi0/ySUKgvLhrOkfECCKON8daK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk6cDe%2FbtsLDMVXqi0%2FySUKgvLhrOkfECCKON8daK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1358&quot; height=&quot;994&quot; data-filename=&quot;스크린샷 2025-01-03 오후 5.02.11.png&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;994&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;elevation, border, shape, color 등 설정이 가능하다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;contentColor를 설정하지 않으면 color에 따라서 contentColor가 자동으로 설정된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;(contentColor가 설정되지 않았다면, 이 Surface는 배경 색상을 테마의 ColorScheme에 정의된 색상에 맞는 ContentColor를 반환한다. 예를 들어, 이 Surface의 색상이 ColorScheme.surface라면, contentColor는 ColorScheme.onSurface로 설정된다)&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt; Surface(
        modifier = Modifier.padding(10.dp),
        shadowElevation = 10.dp,
        border = BorderStroke(2.dp, color = Color.Cyan),
        shape = CircleShape,
        color = MaterialTheme.colorScheme.background
    ) {
        Text(&quot;Test&quot;,
            modifier = Modifier.padding(8.dp))
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-03 오후 5.12.55.png&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBXDe3/btsLEqkAg4O/9Pwa1P9I84d5ivbBbPo190/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBXDe3/btsLEqkAg4O/9Pwa1P9I84d5ivbBbPo190/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBXDe3/btsLEqkAg4O/9Pwa1P9I84d5ivbBbPo190/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBXDe3%2FbtsLEqkAg4O%2F9Pwa1P9I84d5ivbBbPo190%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;269&quot; height=&quot;558&quot; data-filename=&quot;스크린샷 2025-01-03 오후 5.12.55.png&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;compose는 margin이 있는게 아니라 padding이 존재한다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약, Surface내에 속한 Text component가 margin을 가져야한다면 Text에 padding을 주는게 아니라 surface에 패딩을 준다&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;Surface(
    modifier = Modifier.padding(5.dp)
) {
    Button({

    }) {
        Text(&quot;Test&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>android compose</category>
      <category>android compose padding</category>
      <category>android compose surface</category>
      <category>compose margin</category>
      <category>compose surface</category>
      <category>compose surface color</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/122</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Surface#entry122comment</comments>
      <pubDate>Fri, 3 Jan 2025 17:15:26 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Component 알아보기 - Text, Button</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-%EC%9C%84%EC%A0%AF-%EC%95%8C%EC%95%84%EA%B0%80%EA%B8%B0-Text</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;Component들이 아직 손에 익지 않아서 테스트해보면서 몇가지만 기록해보려고 합니다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Text에서 색상, 폰트 크기, 폰트 굵기 변경 등 테스트해보겠습니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가장 기본형태를 아래와 같다는 가정입니다 :)&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;커스텀하려면 parameter로 원하는 걸 추가하면 되는데 아직 손에 익지 않아서 하나씩 해보는 중입니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735725914776&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Text(
      text = &quot;Hello $name!&quot;,
      modifier = modifier
)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;폰트 컬러 변경 &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735726064238&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Text(
        color = Color(0xff0094ff), //혹은 color = Color.Green 의 형태도 가능
        text = &quot;Hello $name!&quot;,
        modifier = modifier
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;폰트 크기 변경&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;Text(
    color = Color(0xff0094ff),
    fontSize = 30.sp,
    text = &quot;Hello $name!&quot;,
    modifier = modifier
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;폰트 weight 변경&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;Text(
    color = Color(0xff0094ff),
    fontSize = 30.sp,
    fontWeight = FontWeight.Bold,
    text = &quot;Hello $name!&quot;,
    modifier = modifier
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;폰트 글꼴 변경&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;Text(
    color = Color(0xff0094ff),
    fontSize = 30.sp,
    fontWeight = FontWeight.Bold,
    fontFamily = FontFamily.Cursive,
    text = &quot;Hello $name!&quot;,
    modifier = modifier
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;폰트 decoration 설정&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;Text(
    color = Color(0xff0094ff),
    fontSize = 30.sp,
    fontWeight = FontWeight.Bold,
    fontFamily = FontFamily.Cursive,
    text = &quot;Hello $name!&quot;,
    letterSpacing = 3.sp,
    maxLines = 4,
    textDecoration = TextDecoration.LineThrough,
    modifier = modifier
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 외에도 Text 원형을 보고 원하는 parameter를 사용하면 될 거 같아요&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-01 오후 7.18.11.png&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;890&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V26P9/btsLAzcnpsB/8yY2sp6ITX385L1GFPaHAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V26P9/btsLAzcnpsB/8yY2sp6ITX385L1GFPaHAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V26P9/btsLAzcnpsB/8yY2sp6ITX385L1GFPaHAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV26P9%2FbtsLAzcnpsB%2F8yY2sp6ITX385L1GFPaHAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1026&quot; height=&quot;890&quot; data-filename=&quot;스크린샷 2025-01-01 오후 7.18.11.png&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;890&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Button은 onClick이 필수이고&lt;span&gt;&amp;nbsp;&lt;/span&gt;기본적으로 RowScope입니다&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun Button(
    onClick: () -&amp;gt; Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    shape: Shape = ButtonDefaults.shape,
    colors: ButtonColors = ButtonDefaults.buttonColors(),
    elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
    border: BorderStroke? = null,
    contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    content: @Composable RowScope.() -&amp;gt; Unit
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;버튼 클릭 시 Toast 출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composable 함수에서 paramater로 unit 설정 후 Activity의 setcontent 부분에서 Toast 출력 시켰습니당&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun Greeting(onClicked: () -&amp;gt; Unit) {
    Button(onClick = onClicked) {
        Text(text = &quot;button&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Button에 다른 ui들 추가해보기&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
fun Greeting(onClicked: () -&amp;gt; Unit) {
    Button(
        onClick = onClicked,
        enabled = false,
        border = BorderStroke(1.dp, Color.Black), // border width, color 설정 
        shape = RoundedCornerShape(10.dp), // paramater로 radius 조절
        contentPadding = PaddingValues(16.dp) // 공통, 개별 설정 가능
    )
    { // 버튼 내의 ui
        Icon(
            imageVector = Icons.Filled.Edit,
            contentDescription = &quot;&quot;
        )
        Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) // 여백 설정
        Text(text = &quot;button&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Button의 색상 변경하기&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;button의 색상은 modifier로 변경이 안됩니다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;button의 colors를 사용해야합니다&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;Button(
    colors = ButtonDefaults.buttonColors(
        containerColor = Color.Magenta, // 버튼 배경의 컬러
        contentColor = Color.Black // 버튼 content 컬러 
    ),
    onClick = onClicked,
    modifier = Modifier
        .size(width = 200.dp, height = 100.dp)
        .background(Color.Gray) //button에서는 modifier로 버튼의 색상을 변경할 수 없다 
)
{
    Icon(
        imageVector = Icons.Filled.Edit,
        contentDescription = null
    )
    Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing))
    Text(text = &quot;button&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용된 색상은 아래와 같습니다&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-01-01 오후 8.50.01.png&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CYz4A/btsLCJxRzOW/Ssr0ouMcFQ3Tt9EG2M80t1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CYz4A/btsLCJxRzOW/Ssr0ouMcFQ3Tt9EG2M80t1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CYz4A/btsLCJxRzOW/Ssr0ouMcFQ3Tt9EG2M80t1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCYz4A%2FbtsLCJxRzOW%2FSsr0ouMcFQ3Tt9EG2M80t1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;318&quot; height=&quot;181&quot; data-filename=&quot;스크린샷 2025-01-01 오후 8.50.01.png&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  Compose</category>
      <category>android Button</category>
      <category>android compose</category>
      <category>android compose button</category>
      <category>android compose text</category>
      <category>android text</category>
      <category>compose</category>
      <category>compose button</category>
      <category>compose component</category>
      <category>compose text</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/121</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-%EC%9C%84%EC%A0%AF-%EC%95%8C%EC%95%84%EA%B0%80%EA%B8%B0-Text#entry121comment</comments>
      <pubDate>Wed, 1 Jan 2025 19:19:37 +0900</pubDate>
    </item>
    <item>
      <title>[Compose] Compose 시작에 앞서</title>
      <link>https://ddusi-dod.tistory.com/entry/Compose-Compose-%EC%8B%9C%EC%9E%91%EC%97%90-%EC%95%9E%EC%84%9C</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;compose 구글에서 제공하던 캠프를 들은 적 있는데 다 까먹었어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 다시 공부합니다.. 시작하기 앞서 간단하게 끄적여봅니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기존에 사용하던 안드로이드 UI는 XML을 사용해 레이아웃을 정의하고, Java나 Kotlin 코드를 통해 UI를 조작하는 명령형 방식인데 &lt;span style=&quot;text-align: start;&quot;&gt;이와 달리 &lt;/span&gt;Compose는 &lt;b&gt;선언형 UI&lt;/b&gt;입니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;선언형 UI&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 모든 컴포넌트가 상태를 가지고 각 컴포넌트는 자신의 상태를 기반으로 UI를 구성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 상태가 변경되면 해당 컴포넌트가 자동으로 다시 그려져 최신 상태를 반영&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;compose를 시작하기 전 잘 이해해야할 부분들을 체크해가며 공부해야할 거 같아요&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 상태 관리에 대한 이해 필요(remember, mutableStateOf, State 등)&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 비동기&amp;nbsp;작업을&amp;nbsp;처리하는&amp;nbsp;방법에&amp;nbsp;대한&amp;nbsp;이해가&amp;nbsp;필요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>  Compose</category>
      <author>콩드로이드</author>
      <guid isPermaLink="true">https://ddusi-dod.tistory.com/120</guid>
      <comments>https://ddusi-dod.tistory.com/entry/Compose-Compose-%EC%8B%9C%EC%9E%91%EC%97%90-%EC%95%9E%EC%84%9C#entry120comment</comments>
      <pubDate>Wed, 1 Jan 2025 18:44:55 +0900</pubDate>
    </item>
  </channel>
</rss>