<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>  111_Commit</title>
    <link>https://devyo-111commit.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 18 May 2026 14:37:55 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>뎁요</managingEditor>
    <image>
      <title>  111_Commit</title>
      <url>https://tistory1.daumcdn.net/tistory/5270087/attach/0f722a6427e546cf8da080a4f98352cc</url>
      <link>https://devyo-111commit.tistory.com</link>
    </image>
    <item>
      <title>✈️ 블로그 이전 안내</title>
      <link>https://devyo-111commit.tistory.com/notice/44</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요!&lt;br /&gt;그동안 제 블로그를 찾아와주신 모든 분들께 감사드립니다  &lt;/p&gt;
&lt;p data-end=&quot;332&quot; data-start=&quot;248&quot; data-ke-size=&quot;size16&quot;&gt;최근 블로그 콘텐츠를 조금 더 깔끔하게 정리하고, 개발 관련 글을 보다 편하게 공유하기 위해&lt;br /&gt;기존 블로그를 &lt;b&gt;Velog&lt;/b&gt;로 이전하기로 했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-end=&quot;557&quot; data-start=&quot;533&quot; data-ke-size=&quot;size23&quot;&gt;  앞으로의 글은 여기서 만나보세요&lt;/h3&gt;
&lt;p data-end=&quot;632&quot; data-start=&quot;559&quot; data-ke-size=&quot;size16&quot;&gt;새로운 블로그 주소는 아래와 같습니다.&lt;br /&gt;  &lt;a href=&quot;https://velog.io/@aksghwoddl/posts&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@aksghwoddl/posts&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1761203620212&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;aksghwoddl (dev_yo) / 작성글 - velog&quot; data-og-description=&quot;어제보다 발전한 오늘이 되고픈  &amp;zwj; &quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@aksghwoddl/posts&quot; data-og-url=&quot;https://velog.io/@aksghwoddl/posts&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://velog.io/@aksghwoddl/posts&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@aksghwoddl/posts&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;aksghwoddl (dev_yo) / 작성글 - velog&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;어제보다 발전한 오늘이 되고픈  &amp;zwj; &lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-end=&quot;704&quot; data-start=&quot;634&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이후의 모든 새로운 포스팅은 Velog에서 업로드될 예정입니다! 감사합니다.  &amp;zwj;♂️&lt;/p&gt;</description>
      <author>뎁요</author>
      <guid isPermaLink="true">https://devyo-111commit.tistory.com/notice/44</guid>
      <pubDate>Thu, 23 Oct 2025 16:15:41 +0900</pubDate>
    </item>
    <item>
      <title>[Android] Android 빌드 로직(build-logic) 적용기</title>
      <link>https://devyo-111commit.tistory.com/43</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 여러분!&lt;br /&gt;오늘은 제가 최근 프로젝트에 빌드 로직(Build Logic)을 적용했던 경험에 대해서 공유하려고 합니다.&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;002&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/002.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/002.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;1. 빌드 로직(Build Logic)은 왜 필요했을까?  &lt;/h2&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;사실 프로젝트가 작을 때는 Gradle 설정하는 건 별로 어렵지 않습니다..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;하지만 모듈이 점차 늘어나고 플러그인이 많아 질때마다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;같은 작업을 반복하는건 정말 지옥이죠..☠️&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;이 문제를 해결 할 수 있는 방법이 없을까 생각하다가 빌드 로직을 적용하게 되었습니다!&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;2. 빌드 로직 (Build Logic) 적용기  &lt;/h2&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;1. build-logic 모듈 만들기&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; text-align: start;&quot;&gt;모듈은 Java or Kotlin Library로 만들어줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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-origin-width=&quot;1280&quot; data-origin-height=&quot;914&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p8Lao/btsO1jxbDHP/1f2BjlOKbzMqH3WQduYow1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p8Lao/btsO1jxbDHP/1f2BjlOKbzMqH3WQduYow1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p8Lao/btsO1jxbDHP/1f2BjlOKbzMqH3WQduYow1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp8Lao%2FbtsO1jxbDHP%2F1f2BjlOKbzMqH3WQduYow1%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;497&quot; height=&quot;355&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;914&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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-origin-width=&quot;758&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A6kcl/btsO2d35nsl/fLKHHkYGxevH7Kvi0WU09K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A6kcl/btsO2d35nsl/fLKHHkYGxevH7Kvi0WU09K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A6kcl/btsO2d35nsl/fLKHHkYGxevH7Kvi0WU09K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA6kcl%2FbtsO2d35nsl%2FfLKHHkYGxevH7Kvi0WU09K%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;369&quot; height=&quot;160&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;2. build-logic Gradle 세팅하기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;384&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNRymE/btsO1T5VMam/fKEbGD1BAaUubaU8aiGFR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNRymE/btsO1T5VMam/fKEbGD1BAaUubaU8aiGFR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNRymE/btsO1T5VMam/fKEbGD1BAaUubaU8aiGFR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNRymE%2FbtsO1T5VMam%2FfKEbGD1BAaUubaU8aiGFR0%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;274&quot; height=&quot;274&quot; data-origin-width=&quot;384&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;모듈 내부에 &lt;/span&gt;&lt;b&gt;build.gradle&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 과 &lt;/span&gt;&lt;b&gt;settings.gradle&lt;/b&gt;&lt;span style=&quot;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-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-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;// settings.gradle.kts

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
    versionCatalogs {
        create(&quot;libs&quot;) {
            from(files(&quot;../gradle/libs.versions.toml&quot;))
        }
    }
}

rootProject.name = &quot;build-logic&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// build.gradle.kts

plugins {
    `kotlin-dsl`
    alias(libs.plugins.kotlin.serialization)
}

dependencies {
    compileOnly(libs.kotlin.gradle.plugin)
    compileOnly(libs.android.build.gradle)
    compileOnly(libs.gradle.hilt)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;3. Convention Plugin 만들기&lt;/h3&gt;
&lt;pre style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;class AndroidLibraryConventionPlugin : Plugin&amp;lt;Project&amp;gt; {
    override fun apply(target: Project) = with(target) {
        pluginManager.apply {
            apply(&quot;com.android.library&quot;)
            apply(&quot;org.jetbrains.kotlin.android&quot;)
            apply(&quot;org.jetbrains.kotlin.plugin.serialization&quot;)
        }
        extensions.configure&amp;lt;LibraryExtension&amp;gt; {
            kotlinAndroidConfiguration(this)
            buildConfigConfiguration(this)
            defaultConfig {
                consumerProguardFiles(&quot;consumer-rules.pro&quot;)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;internal fun Project.kotlinAndroidConfiguration(
    commonExtension: CommonExtension&amp;lt;*, *, *, *, *, *&amp;gt;,
) = with(commonExtension) {
    compileSdk = 35

    defaultConfig {
        minSdk = 24
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_17.toString()
    }

    buildFeatures {
        buildConfig = true
    }
}

fun CommonExtension&amp;lt;*, *, *, *, *, *&amp;gt;.kotlinOptions(block: KotlinJvmOptions.() -&amp;gt; Unit) {
    (this as ExtensionAware).extensions.configure(&quot;kotlinOptions&quot;, block)
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun Project.buildConfigConfiguration(
    commonExtension: CommonExtension&amp;lt;*, *, *, *, *, *&amp;gt;,
) = with(commonExtension) {
    buildTypes {
        getByName(&quot;release&quot;) {
            buildConfigField(&quot;boolean&quot;, &quot;APP_DEBUG&quot;, false.toString())
        }
        getByName(&quot;debug&quot;) {
            buildConfigField(&quot;boolean&quot;, &quot;APP_DEBUG&quot;, true.toString())
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;4. build-logic Gradle에 plugin 추가하기&lt;/h3&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;gradlePlugin {
    plugins {
        register(&quot;androidLibrary&quot;) {
            id = &quot;com.android.library.convention&quot;
            implementationClass = &quot;AndroidLibraryConventionPlugin&quot;
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;5. 모듈에 적용하기&lt;/h3&gt;
&lt;pre style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;plugins {
    id(&quot;com.android.library.convention&quot;)
}

android {
    namespace = &quot;com.test.example.module&quot;
}

dependencies {
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;3. 적용기를 마치며...&lt;/h2&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Library 모듈에 대한 Plugin뿐만 아니라 Compose , Hilt 혹은 Application Convention 등에 대해서도 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;한번 적용해 놓으면 개발이 훨씬 편해지는 걸 느끼실 수 있을 겁니다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;아래 NowInAndroid Github 샘플 코드를 끝으로 Build Logic 적용기는 마치겠습니다.  &amp;zwj;♂️&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://github.com/android/nowinandroid/tree/main/build-logic&quot;&gt;NowInAndroid Convention Plugin 예시&lt;/a&gt;&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>Android</category>
      <category>build logic</category>
      <category>build-logic</category>
      <category>gradle</category>
      <category>멀티모듈</category>
      <category>빌드로직</category>
      <author>뎁요</author>
      <guid isPermaLink="true">https://devyo-111commit.tistory.com/43</guid>
      <comments>https://devyo-111commit.tistory.com/43#entry43comment</comments>
      <pubDate>Wed, 2 Jul 2025 23:31:09 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin][Android] Base RecyclerView Adapter 만들기 (feat. Android TV List 구현하기)</title>
      <link>https://devyo-111commit.tistory.com/42</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;너무 오랜만에 포스팅을 올리네요!!&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;한동안 회사에서 리팩토링 때문에 너무 일이 바빴네요..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이제 다시 꾸준히 포스팅 올려보도록 하겠습니다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;제가 진행하고 있는 프로젝트 특성상 리스트를 만들어야 할 경우가 많은데요!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결국, 해당 리스트들에 동일한 동작을 추가해야 하는 경우가 생겼고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;매번 동작들을 추가하다 보니 보일러 플레이트 코드가 너무 많이 발생하여 각 Adapter의 BaseClass를 만들었는데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;관련해서 공유해보는 시간을 가지려고 합니다!&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;010&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/010.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/010.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; 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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. BaseViewHolder 만들기&lt;/h4&gt;
&lt;pre id=&quot;code_1705027222237&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;abstract class BaseViewHolder&amp;lt;ITEM : Any, VB : ViewDataBinding&amp;gt;(binding: VB) :
    RecyclerView.ViewHolder(binding.root) {
    abstract val binding: VB
    abstract fun bind(data: ITEM)
}&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;먼저, ViewDataBinding과 Item을 받을 수 있는 제네릭을 정해준 후 ViewHolder가 데이터를 받아 Bind를 수행하는 추상 함수를 하나 만들어줍니다.&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;2. BaseAdapter 만들기&lt;/h4&gt;
&lt;pre id=&quot;code_1705027440831&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;abstract class BaseAdapter&amp;lt;VB : ViewDataBinding, ITEM : Any&amp;gt;(val layoutId: Int) 
    : RecyclerView.Adapter&amp;lt;BaseViewHolder&amp;lt;ITEM, VB&amp;gt;&amp;gt;() {
        private var itemList = emptyList&amp;lt;ITEM&amp;gt;()
        
        abstract fun createViewHolder(parent: ViewGroup): VB
        
        override fun onBindViewHolder(holder: BaseViewHolder&amp;lt;ITEM, VB&amp;gt;, position: Int) {
            holder.bind(itemList[position])
        }
        
        override fun getItemCount() = itemList.size
        
        fun setItemList(list: List&amp;lt;ITEM&amp;gt;) {
            itemList = list
            notifyItemRangeChanged(0 , itemCount -1)
        }
    
}&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;딱히 어려운 부분은 없는데요, 기본적인 ItemList와 BindViewHolder를 미리 정의하여 반복되는 코드를 줄였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createViewHolder() 함수를 통해 제네릭으로 지정한 ViewDataBinding을 반환하도록 구현하여 DataBinding을 생성할 때 타입을 지정해주어야 하는 번거로움을 줄였습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. Adapter에 적용하기&lt;/h4&gt;
&lt;pre id=&quot;code_1705027862235&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class VideoListAdapter : BaseAdapter&amp;lt;ViewHolderVideoBinding, Video&amp;gt;(R.layout.view_holder_video) {
    override fun createViewHolder(parent: ViewGroup): ViewHolderVideoBinding {
        return DataBindingUtil.inflate(LayoutInflater.from(parent.context), layoutId, parent, false)
    }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int,
    ): BaseViewHolder&amp;lt;Video, ViewHolderVideoBinding&amp;gt; {
        val binding = createViewHolder(parent)
        return VideoViewHolder(binding)
    }
}

class VideoViewHolder(
    override val binding: ViewHolderVideoBinding,
) : BaseViewHolder&amp;lt;Video, ViewHolderVideoBinding&amp;gt;(binding) {
    override fun bind(data: Video) {
        with(binding) {
            // binding 처리
        }
    }
}&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;BaseAdapter는 위에서 보이는 것과 같이 구현해주면 되는데요 이렇게 보면 굳이 왜 이렇게 만들어야 해?라고 생각이 들 수도 있는데요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 개발자들의 취향차이겠지만, 저같은 경우는 아래와 같은 이유로 BaseAdapter를 만들었습니다.&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-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위에서 언급했듯이 반복되는 코드를 줄여서 개발이 훨씬 편리해진다.&lt;/li&gt;
&lt;li&gt;반복되는 코드가 줄면서 BaseAdapter를 구현한 구현체들은 코드량이 줄어 코드를 보기 편해지고 유지보수가 편리해진다.&lt;/li&gt;
&lt;li&gt;마지막으로, 저같은 경우는 모든 Adapter에 공통 동작(Click , Key 등등 )에 대해서 미리 정의해두고 사용하기 위해 BaseAdapter를 정의해 두었습니다. (아래는 제가 프로젝트에 적용한 예시입니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705035002669&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;abstract class BaseAdapter&amp;lt;VB : ViewDataBinding, ITEM : Any&amp;gt;(
    private val tag: String,
    val layoutId: Int,
    private val recyclerView: RecyclerView,
) : RecyclerView.Adapter&amp;lt;BaseViewHolder&amp;lt;ITEM, VB&amp;gt;&amp;gt;() {

    private var itemList = emptyList&amp;lt;ITEM&amp;gt;()
    private var focusedPosition = 0
    private var itemKeyListener: OnItemKeyListener? = null

    abstract fun createViewHolder(parent: ViewGroup): VB

    override fun onBindViewHolder(holder: BaseViewHolder&amp;lt;ITEM, VB&amp;gt;, position: Int) {
        holder.bind(itemList[position])
        if (position == focusedPosition) {
            holder.itemView.requestFocus()
        }
    }

    override fun getItemCount() = itemList.size

    fun setOnItemKeyListener(listener: OnItemKeyListener) {
        itemKeyListener = listener
    }

    fun setItemList(list: List&amp;lt;ITEM&amp;gt;) {
        itemList = list
        notifyItemRangeChanged(0 , itemCount -1)
    }

    fun requestFocusItem() {
        notifyItemChanged(focusedPosition)
        val layoutManager =
            recyclerView.layoutManager as WrapLinearLayoutManager
        notifyItemChanged(focusedPosition)
        layoutManager.scrollToPositionWithOffset(focusedPosition, 0)
    }

    fun setOnKeyListener(binding: VB) {
        binding.root.setOnKeyListener keyListener@{ _, _, keyEvent -&amp;gt;

            itemKeyListener?.onKeyClick(
                keyEvent.keyCode,
                itemList[focusedPosition],
                focusedPosition,
            )

            when (keyEvent.keyCode) {
                KeyEvent.KEYCODE_DPAD_LEFT -&amp;gt; {
                    if (focusedPosition &amp;gt; 0) {
                        focusedPosition--
                        val layoutManager =
                            recyclerView.layoutManager as WrapLinearLayoutManager
                        notifyItemChanged(focusedPosition)
                        layoutManager.scrollToPositionWithOffset(focusedPosition, 0)
                    }
                    Log.d(tag, &quot;onCreateViewHolder: focusedPosition = $focusedPosition&quot;)
                    return@keyListener true
                }

                KeyEvent.KEYCODE_DPAD_RIGHT -&amp;gt; {
                    if (focusedPosition &amp;lt; itemCount - 1) {
                        focusedPosition++
                        val layoutManager =
                            recyclerView.layoutManager as WrapLinearLayoutManager
                        notifyItemChanged(focusedPosition)
                        layoutManager.scrollToPositionWithOffset(focusedPosition, 0)
                    }
                    Log.d(tag, &quot;onCreateViewHolder: focusedPosition = $focusedPosition&quot;)
                    return@keyListener true
                }

                KeyEvent.KEYCODE_DPAD_UP,
                KeyEvent.KEYCODE_DPAD_DOWN,
                KeyEvent.KEYCODE_DPAD_CENTER,
                -&amp;gt; {
                    return@keyListener true
                }
            }
            false
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드를 천천히 분석해 보겠습니다!&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;우선, 해당 동작은 일반적인 Android 모바일 기기를 고려한 코드가 아니고 Android TV 동작에 대한 부분이란걸 참고 부탁드립니다!&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;setOnItemKeyListener()&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RecyclerView에서 Key가 감지되었을 경우 호출할 Listener를 설정하는 함수입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705035288934&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun setOnItemKeyListener(listener: OnItemKeyListener) {
        itemKeyListener = listener
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1705035671492&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface OnItemKeyListener {
    fun onKeyClick(keyCode: Int, data: Any, index: 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;※ 사용예시&lt;/p&gt;
&lt;pre id=&quot;code_1705035974833&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;videoAdapter = VideoListAdapter(rvVideo).apply {
                setOnItemKeyListener(object : OnItemKeyListener {
                    override fun onKeyClick(keyCode: Int, data: Any, index: Int) {
                        when (keyCode) {
                            KeyEvent.KEYCODE_DPAD_DOWN -&amp;gt; {
                                chattingAdapter.requestFocusItem() // 채팅으로 포커스 이동
                            }

                            KeyEvent.KEYCODE_DPAD_CENTER -&amp;gt; {
                                if (data is Video) {
                                    mainViewModel.startNewVideo(data) // 비디오 재생
                                }
                            }

                            KeyEvent.KEYCODE_DPAD_LEFT -&amp;gt; {
                                if (index == 0) {
                                    mainViewModel.showNavigation() // 네비게이션 View 표시
                                }
                            }
                        }
                    }
                })
            }&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;requestFocusItem()&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 저장된 focusedPosition으로 Focus를 이동시키는 함수&lt;/p&gt;
&lt;pre id=&quot;code_1705036121748&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun requestFocusItem() {
        notifyItemChanged(focusedPosition)
        val layoutManager =
            recyclerView.layoutManager as WrapLinearLayoutManager
        notifyItemChanged(focusedPosition)
        layoutManager.scrollToPositionWithOffset(focusedPosition, 0)
    }&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;setOnKeyListener()&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 Adapter에 공통적으로 정의될 KeyListener를 구현한 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(BaseAdapter를 상속받아 사용할때 해당 이벤트를 등록하기를 원치 않는다면 onCreateViewHolder에서 함수를 호출하지 않으면 됨)&lt;/p&gt;
&lt;pre id=&quot;code_1705036382284&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun setOnKeyListener(binding: VB) {
        binding.root.setOnKeyListener keyListener@{ _, _, keyEvent -&amp;gt;
            // 모든 Key는 우선 Listener로 전달됨
            // 각 parent에서 처리할 키만 정의하여 사용
            itemKeyListener?.onKeyClick(
                keyEvent.keyCode,
                itemList[focusedPosition],
                focusedPosition,
            )

            when (keyEvent.keyCode) {
                KeyEvent.KEYCODE_DPAD_LEFT -&amp;gt; { // 리스트 왼쪽으로 포커스 이동
                    if (focusedPosition &amp;gt; 0) {
                        focusedPosition--
                        val layoutManager =
                            recyclerView.layoutManager as WrapLinearLayoutManager
                        notifyItemChanged(focusedPosition)
                        layoutManager.scrollToPositionWithOffset(focusedPosition, 0)
                    }
                    Log.d(tag, &quot;onCreateViewHolder: focusedPosition = $focusedPosition&quot;)
                    return@keyListener true
                }

                KeyEvent.KEYCODE_DPAD_RIGHT -&amp;gt; { // 리스트 오른쪽으로 포커스 이동
                    if (focusedPosition &amp;lt; itemCount - 1) {
                        focusedPosition++
                        val layoutManager =
                            recyclerView.layoutManager as WrapLinearLayoutManager
                        notifyItemChanged(focusedPosition)
                        layoutManager.scrollToPositionWithOffset(focusedPosition, 0)
                    }
                    Log.d(tag, &quot;onCreateViewHolder: focusedPosition = $focusedPosition&quot;)
                    return@keyListener true
                }

                KeyEvent.KEYCODE_DPAD_UP,
                KeyEvent.KEYCODE_DPAD_DOWN,
                KeyEvent.KEYCODE_DPAD_CENTER,
                -&amp;gt; { // UP , DOWN , CENTER는 Parent에서 재정의
                    return@keyListener true
                }
            }
            false
        }
    }&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 BaseAdapter를 구현한 내용에 대해서 포스팅해봤는데요!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다시 말씀드리지만 이건 개발자의 취향이기 때문에 정답은 없다는 점 알아주세요  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 포스팅이 여러분에게 도움이 되었으면 좋겠습니다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘도 즐코하세요 :)&lt;/p&gt;</description>
      <category>Android/Kotlin</category>
      <category>abstract</category>
      <category>Android</category>
      <category>AndroidTV</category>
      <category>BaseAdapter</category>
      <category>BaseRecyclerView</category>
      <category>recyclerview</category>
      <category>리싸이클러뷰</category>
      <category>안드로이드</category>
      <category>안드로이드TV</category>
      <category>안드로이드티비</category>
      <author>뎁요</author>
      <guid isPermaLink="true">https://devyo-111commit.tistory.com/42</guid>
      <comments>https://devyo-111commit.tistory.com/42#entry42comment</comments>
      <pubDate>Fri, 12 Jan 2024 14:27:39 +0900</pubDate>
    </item>
    <item>
      <title>[Android] 안드로이드 ExoPlayer2의 Deprecated와 Media3에 대해서</title>
      <link>https://devyo-111commit.tistory.com/41</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 ExoPlayer2가 Deprecated 된 것과&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그로 인해 업무중에 겪었던 여러 가지 일상(?)들에 대해 간단하게 적어보려 합니다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;009&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/009.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/009.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1.&amp;nbsp; 포스팅 하게된 이유  &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 회의에서 ExoPlayer의 버전이 너무 낮다는 얘기를 시작으로 프로젝트의 ExoPlayer의 버전을 높이자는 제안이 나오게 되었습니다. (제일 낮은 버전을 사용하던 프로젝트는 1.5.5를 사용하고 제가 담당한 프로젝트는 2.9.3을 사용하고 있었다..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;b&gt;2. ExoPlayer2의 Deprecated&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 사실에 대해서 알게 된 것은 build.gradle에서 ExoPlayer를 최신버전으로 올리면서 알게 되었는데, 올리고 나니 갑자기 모든 ExoPlayer관련 class가 deprecated 되었다고 나타나는 것.. &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;무슨 이유인지 알아보기 위해 release note를 확인해보았고 23.7.5 부로 ExoPlayer2 Project가 deprecated 되었다고 적혀있더군요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아울러, 모든 사용자들은 이제 Media3로 이전 하라고 적혀있었습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1689753307298&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - google/ExoPlayer: An extensible media player for Android&quot; data-og-description=&quot;An extensible media player for Android. Contribute to google/ExoPlayer development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/google/ExoPlayer&quot; data-og-url=&quot;https://github.com/google/ExoPlayer&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zz7Dr/hyTmwkiyu9/bQY4GxuzXBmPxd2TpcAdm0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/google/ExoPlayer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/google/ExoPlayer&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zz7Dr/hyTmwkiyu9/bQY4GxuzXBmPxd2TpcAdm0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - google/ExoPlayer: An extensible media player for Android&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An extensible media player for Android. Contribute to google/ExoPlayer development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3&lt;/b&gt;&lt;b&gt;. Media3란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Media3에 대해서 Android Developer는 아래와 같이 말하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면 여러 가지 미디어 라이브러리를 관리하는 Android의 새로운 라이브러리라고 할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;더 자세한 정보는 링크를 통해 확인해주세요 :)&lt;/b&gt;&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;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Jetpack Media3는 Android 앱이 풍부한 오디오 및 시각적 경험을 표시할 수 있도록 하는 미디어 라이브러리의 새로운 홈입니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;figure id=&quot;og_1689750951563&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Media3 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;Stay organized with collections Save and categorize content based on your preferences. Media3 overview Jetpack Media3 is the new home for media libraries that enables Android apps to display rich audio and visual experiences. It introduces a simpler archit&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/guide/topics/media/media3&quot; data-og-url=&quot;https://developer.android.com/guide/topics/media/media3&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dBQvlj/hyTmsoJqpe/H0LdvLP2QXNqDjHUg33Lm0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/guide/topics/media/media3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/guide/topics/media/media3&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dBQvlj/hyTmsoJqpe/H0LdvLP2QXNqDjHUg33Lm0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Media3 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Stay organized with collections Save and categorize content based on your preferences. Media3 overview Jetpack Media3 is the new home for media libraries that enables Android apps to display rich audio and visual experiences. It introduces a simpler archit&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;4. Media3를 적용하며(?)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엄밀히 말하면 아직 Media3을 적용하지는 않고 &lt;span style=&quot;color: #f3c000;&quot;&gt;&lt;b&gt;'출시 후보버전'&lt;/b&gt;&lt;/span&gt;을 테스트해본것 이지만&lt;span style=&quot;color: #f3c000;&quot;&gt;&lt;b&gt;(아직 정식버전이 나오지 않음)&lt;/b&gt;&lt;/span&gt; 이전 ExoPlayer에서 사용하는 API들을 전부 대체해주어 Media3로 이전하는데 큰 어려움은 없을 것으로 보입니다.(2.18.7 버전 기준입니다!)&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. 글을 마치며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 현재는 Media3를 프로젝트에 직접 적용하지는 않았습니다. (정식 출시버전이 나올 때 일괄적으로 적용할 것 같네요.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 이전에 사용하던 ExoPlayer의 버전은 현재 Media3에서 제공해주고 있는 API들과는 대체가 되지 않기에 해당 부분은 현재 release 되고 deprecated 되지 않은 가장 최신 버전(2.18.7)을 적용한 상태입니다. 여러분들도 후에 Media3을 사용하기 위해 ExoPlayer의 버전을 미리 업그레이드해보는 건 어떠신가요?? 마지막으로 2.9.3 -&amp;gt; 2.18.7로 버전을 변경하며 변경해 준 API와 class에 대해서 간단하게 정리하고 포스팅을 마치겠습니다  &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1689752424105&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 인스턴스 및 Listener
// private var player: SimpleExoPlayer? = null
// private var eventListener: Player.EventListener? = null

private var player: ExoPlayer? = null
private var eventListener: Player.Listener? = null

// 인스턴스 생성
// player = ExoPlayerFactory.newSimpleInstance(context)

player = ExoPlayer.Builder(context).build()

/**
* PlayerView , PlayerControlView는 Deprecated됨 (Media3에서는 다시 나오는걸로 보임)
* 대신 StyledPlayerView , StyledPlayerControlView를 사용하세요.
**/

// MediaResource 생성 및 prepare()
// player?.prepare(buildMediaSource(Uri.parse(uri)), resetPostion, resetState)

player?.run {
    setMediaSource(buildMediaSource(Uri.parse(uri)), resetPostion)
    prepare()
}

// MediaSource 만드는 class 관련
// ExtractorMediaSource.Factory()
ProgressiveMediaSource.Factory()

// DefaultHttpDataSourceFactory
DefaultHttpDataSource.Factory()

// createMediaSource(uri)
createMediaSource(MediaItem.fromUri(uri))&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 새롭게 알게된 사실들에 대해서 함께 공유해 보는 시간을 가졌습니다.  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이글이 여러분에게 많은 도움이 되었으면 좋겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘도 즐코하세요 :)&lt;/p&gt;</description>
      <category>Android</category>
      <category>Android</category>
      <category>android exoplayer</category>
      <category>Android ExoPlayer2</category>
      <category>android media3</category>
      <category>Android Media3 migration</category>
      <category>exoplayer</category>
      <category>exoplayer deprecated</category>
      <category>ExoPlayer2</category>
      <category>media3</category>
      <category>안드로이드</category>
      <author>뎁요</author>
      <guid isPermaLink="true">https://devyo-111commit.tistory.com/41</guid>
      <comments>https://devyo-111commit.tistory.com/41#entry41comment</comments>
      <pubDate>Wed, 19 Jul 2023 16:58:23 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin][Android] 안드로이드 ENUM에 대해서(IntDef , StringDef의 사용법)</title>
      <link>https://devyo-111commit.tistory.com/40</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 회사에서 업무를 하던 중 enum class 사용에 대한 피드백을 받으며&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;새롭게 공부하게 된 @IntDef , @StringDef에 대해서&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;간단하게 포스팅 해볼까 합니다.  &lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;010&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/010.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/010.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin 및 Java에서 고정된 값을 정의하여 사용할 때 주로 상수를 지정하여 많이 사용합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;해당 상수들을 타입으로 묶어서 사용하고 싶을때는 enum class를 지정하여 사용하는 경우가 많이들 있었을 겁니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 Android에서 enum class를 사용할 경우 앱의 크기가 커지고 퍼포먼스적으로 문제를 발생할 수 있어&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사용을 지양하고 있습니다. 특히, TV나 AVN같은 적은 메모리를 가지는 기기에서는 취약할 수 있습니다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(참고자료는 아래 링크를 통해 확인 부탁드립니다.)&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1688960447409&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Android Performance: Avoid using ENUM on Android&quot; data-og-description=&quot;What and why we use ENUM?&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/android-news/android-performance-avoid-using-enum-on-android-326be0794dc3&quot; data-og-url=&quot;https://medium.com/android-news/android-performance-avoid-using-enum-on-android-326be0794dc3&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/2htbH/hyTgPDo71t/sPrh1g4KjM2vzNVJ3V7Gy1/img.png?width=1120&amp;amp;height=700&amp;amp;face=0_0_1120_700,https://scrap.kakaocdn.net/dn/bb5q3k/hyTfEiXGpm/5ohgcTMByQqhkMt1zExy90/img.png?width=989&amp;amp;height=742&amp;amp;face=0_0_989_742,https://scrap.kakaocdn.net/dn/0jbwD/hyTfGHPCqx/zTvxiUflFG3KqweEzRP3X1/img.png?width=1038&amp;amp;height=576&amp;amp;face=0_0_1038_576&quot;&gt;&lt;a href=&quot;https://medium.com/android-news/android-performance-avoid-using-enum-on-android-326be0794dc3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/android-news/android-performance-avoid-using-enum-on-android-326be0794dc3&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/2htbH/hyTgPDo71t/sPrh1g4KjM2vzNVJ3V7Gy1/img.png?width=1120&amp;amp;height=700&amp;amp;face=0_0_1120_700,https://scrap.kakaocdn.net/dn/bb5q3k/hyTfEiXGpm/5ohgcTMByQqhkMt1zExy90/img.png?width=989&amp;amp;height=742&amp;amp;face=0_0_989_742,https://scrap.kakaocdn.net/dn/0jbwD/hyTfGHPCqx/zTvxiUflFG3KqweEzRP3X1/img.png?width=1038&amp;amp;height=576&amp;amp;face=0_0_1038_576');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Android Performance: Avoid using ENUM on Android&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;What and why we use ENUM?&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 enum은 사용하지 않고 무조건 상수로만 값을 지정해야 할까요??  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;enum을 대체할 방법은 바로 @IntDef , @StringDef를 통해 값을 정의해주는 겁니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사용방법을 아래와 같습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688957287163&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*enum class Type {
    INT, STRING, CHAR
}*/

/*enum class VALUES(value: Int) {
    VALUE1(1), VALUE2(2), VALUE(3)
}*/

private interface IType {
    companion object {
        const val INT = &quot;INT&quot;
        const val STRING = &quot;STRING&quot;
        const val CHAR = &quot;CHAR&quot;
    }
}

private interface IValue {
    companion object {
        const val VALUE1 = 1
        const val VALUE2 = 2
        const val VALUE3 = 3
    }
}

@Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY)
@StringDef(value = [IType.INT, IType.STRING, IType.CHAR])
private annotation class TYPE

@Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY)
@IntDef(value = [VALUE1, VALUE2, VALUE3])
private annotation class VALUE

class MainActivity : AppCompatActivity() {
    @TYPE
    var type: String = INT
    
    @VALUE
    var value: Int = VALUE1
    
    fun checkType() {
        when (type) {
            INT -&amp;gt; {}
            STRING -&amp;gt; {}
            CHAR -&amp;gt; {}
        }
    }
    
    fun checkValue() {
        when (value) {
            VALUE1 -&amp;gt; {}
            VALUE2 -&amp;gt; {}
            VALUE3 -&amp;gt; {}
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;IntDef(Value) , StringDef(Type) 각각 위에서 처럼 정의할 수 있으며annotation class에는 아래와 같은 옵션이 존재합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;필요에 따라 설정하여 사용하면됩니다!&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 146px;&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: 50%; text-align: center; height: 19px;&quot;&gt;이름&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 19px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 54px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 54px;&quot;&gt;&lt;b&gt;@Target&lt;/b&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: left; height: 54px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;어노테이션을 달 수 있는 구성 요소 선정(어떤것으로 사용할건지)&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;ex)&lt;br /&gt;@Target(AnnotationTarget.TYPE)&lt;br /&gt;var type: String&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 73px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 73px;&quot;&gt;&lt;b&gt;@Retention&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: left; height: 73px;&quot;&gt;&lt;b&gt;어노테이션이 남아있는 단계를 선정합니다.&lt;br /&gt;(소스(SOURCE), 컴파일 타임(BINARY), 런타임(RUNIME) 중)&lt;br /&gt;&lt;/b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;ex)&lt;br /&gt;&lt;/span&gt;@Retention(AnnotationRetention.RUNTIME)&lt;br /&gt;var type: String&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 간단한 포스팅으로 찾아뵙습니다.  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 포스팅이 여러분에게 많은 도움이 됐으면 좋겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘도 즐코하세요 :)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android/Kotlin</category>
      <category>Android</category>
      <category>enum class</category>
      <category>IntDef</category>
      <category>kotlin annotaion</category>
      <category>kotlin IntDef</category>
      <category>kotlin StringDef</category>
      <category>memory leak</category>
      <category>StringDef</category>
      <category>코틀린 Intdef</category>
      <category>코틀린 StringDef</category>
      <author>뎁요</author>
      <guid isPermaLink="true">https://devyo-111commit.tistory.com/40</guid>
      <comments>https://devyo-111commit.tistory.com/40#entry40comment</comments>
      <pubDate>Mon, 10 Jul 2023 12:46:11 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin][Android] SharedFlow와 StateFlow 활용하기 (feat. UIState 활용하여 데이터 표시하기)</title>
      <link>https://devyo-111commit.tistory.com/39</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이전 포스팅에서 LiveData를 Coroutine Flow로 변경하여 적용하는 방법을 알아보았는데요&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;관련 포스팅은 아래 포스팅을 참고해 주세요 :)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1688537473672&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Kotlin][Android] 안드로이드 Coroutine Flow 적용하기 (2) ( LiveData를 Flow로 변경하기 : LiveData 와 Flow 비교&quot; data-og-description=&quot;안녕하세요   오늘은 저번 포스팅에 이어 LiveData를 Flow로 이전하는 과정을 포스팅해보려 합니다. 뿐만 아니라, LiveData와 Flow의 차이점에 대해서도 함께 다뤄볼 예정입니다   이전 포스팅은 아&quot; data-og-host=&quot;devyo-111commit.tistory.com&quot; data-og-source-url=&quot;https://devyo-111commit.tistory.com/36&quot; data-og-url=&quot;https://devyo-111commit.tistory.com/36&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qFLHE/hyTeiyYiaM/HXq1ClkW2ob8qltwQefJ5k/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/5iG9N/hyTd9Wiz1z/cx5dZBbRF2KyKLeXNQSD5K/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/V7CV7/hyTd57rmgx/eYKPrLfefUijlve7ikkTAk/img.jpg?width=1200&amp;amp;height=1543&amp;amp;face=438_344_729_662&quot;&gt;&lt;a href=&quot;https://devyo-111commit.tistory.com/36&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://devyo-111commit.tistory.com/36&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qFLHE/hyTeiyYiaM/HXq1ClkW2ob8qltwQefJ5k/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/5iG9N/hyTd9Wiz1z/cx5dZBbRF2KyKLeXNQSD5K/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/V7CV7/hyTd57rmgx/eYKPrLfefUijlve7ikkTAk/img.jpg?width=1200&amp;amp;height=1543&amp;amp;face=438_344_729_662');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Kotlin][Android] 안드로이드 Coroutine Flow 적용하기 (2) ( LiveData를 Flow로 변경하기 : LiveData 와 Flow 비교&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요   오늘은 저번 포스팅에 이어 LiveData를 Flow로 이전하는 과정을 포스팅해보려 합니다. 뿐만 아니라, LiveData와 Flow의 차이점에 대해서도 함께 다뤄볼 예정입니다   이전 포스팅은 아&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;devyo-111commit.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 SharedFlow와 StateFlow를 활용하여 화면을 어떻게 구성하는지&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;UIState로 관리를 어떻게 하는지 한번 알아보도록 하겠습니다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;009&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/009.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/009.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 Data를 받아올때 결과에 대한 Sealed class를 하나 만들어줍니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688537668518&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sealed class DataResult&amp;lt;T&amp;gt; {
    data class Success&amp;lt;T&amp;gt;(val data: T) : DataResult&amp;lt;T&amp;gt;()
    data class Exception&amp;lt;T&amp;gt;(val message: String) : DataResult&amp;lt;T&amp;gt;()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이름 그대로 Success 때는 정보를 성공적으로 받아온 상태이며 Exception상태는 예외가 발생한 상태입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(상황에 따라 Fail , Loading 등을 추가하여 많이 사용하나 저는 간단한 예제이기에 예외처리에 대해서만 정의하였습니다.)&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;pre id=&quot;code_1688537820849&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MainRepositoryImpl @Inject constructor(
    private val userDao: UserDao,
) : MainRepository {
    override suspend fun insertUser(user: UserEntity) {
        userDao.addUser(user)
    }

    override suspend fun getAllUser() = flow&amp;lt;DataResult&amp;lt;List&amp;lt;UserEntity&amp;gt;&amp;gt;&amp;gt; {
        emit(DataResult.Success(userDao.getAllUser().toList()))
    }.catch { throwable -&amp;gt;
        emit(DataResult.Exception(throwable.message.toString()))
    }

    override suspend fun deleteAllUser() {
        userDao.deleteAllUser()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드에서 보는바와 같이 getAllUser()라는 함수로 Room에서 전체 저장된 값을 가져오는데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;가져올 때 DataResult에 담긴 Flow로 값을 return 해주도록 하였습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;pre id=&quot;code_1688537958250&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sealed interface UiState {
    data class Loading(val list: List&amp;lt;UserEntity&amp;gt; = emptyList()) : UiState
    data class EmptyListState(val list: List&amp;lt;UserEntity&amp;gt; = emptyList()) : UiState
    data class ShowListState(val list: List&amp;lt;UserEntity&amp;gt;) : UiState
    data class OnErrorState(val message: String) : UiState
}

sealed interface ErrorEvent {
    data class Error(val errorMessage: String) : ErrorEvent
}

@HiltViewModel
class MainViewModel @Inject constructor(
    private val mainRepository: MainRepository,
) : ViewModel() {
	...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다음으로는 ViewModel을 구성하는 방법입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같이 UIState라는 sealed interface를 각 UI상태에 맞게 정의를 해주었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;(해당 부분도 각 상황에 맞게 구성해 주면 되겠습니다 개발자 재량!!)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;ErrorEvent는 발생할 수 있는 여러 Error상황에 대해 정의해 놓은 sealed interface로&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;마찬가지로 각 상황에 맞게 구성 해주면 됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688538750479&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@HiltViewModel
class MainViewModel @Inject constructor(
    private val mainRepository: MainRepository,
) : ViewModel() {
    private val _uiState = MutableStateFlow&amp;lt;UiState&amp;gt;(UiState.Loading())
    val uiState: SharedFlow&amp;lt;UiState&amp;gt; = _uiState.asStateFlow()

    private val _errorEvent = MutableSharedFlow&amp;lt;ErrorEvent&amp;gt;()
    val errorEvent: SharedFlow&amp;lt;ErrorEvent&amp;gt; = _errorEvent.asSharedFlow()

    fun addUser(user: UserEntity) {
        viewModelScope.launch {
            mainRepository.insertUser(user)
        }
    }

    fun getAllUser() {
        viewModelScope.launch {
            mainRepository.getAllUser().collect { result -&amp;gt;
                when (result) {
                    is DataResult.Success -&amp;gt; {
                        if (result.data.isNotEmpty()) {
                            _uiState.value = UiState.ShowListState(result.data)
                        } else {
                            _uiState.value = UiState.EmptyListState()
                        }
                    }

                    is DataResult.Exception -&amp;gt; {
                        _uiState.value = UiState.OnErrorState(result.message)
                    }
                }
            }
        }
    }

    fun deleteAllUser() {
        viewModelScope.launch {
            mainRepository.deleteAllUser()
        }
    }

    /**
     * 에러 발생시에 해당 부분을 처리하는 함수로 여러가지 동작을 정희할수 있다.
     * **/
    fun onError(message: String) {
        viewModelScope.launch {
            _errorEvent.emit(ErrorEvent.Error(message))
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;ViewModeld에서는 UIState를 내보내는 StateFlow를 가지고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;데이터를 호출하여 각 상황에 맞는 UIState를 넘겨줍니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;pre id=&quot;code_1688538987835&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    ...
    private fun collectData() {
        viewModel.run {
            lifecycleScope.launch {
                repeatOnLifecycle(Lifecycle.State.STARTED) {
                    launch {
                        uiState.collect { state -&amp;gt;
                            when (state) {
                                is UiState.Loading -&amp;gt; {
                                    binding.loading.isVisible = true
                                }

                                is UiState.ShowListState -&amp;gt; {
                                    binding.run {
                                        loading.isVisible = false
                                        rvUser.isVisible = true
                                        tvNoItem.isVisible = false
                                    }
                                    userAdapter.submitList(state.list)
                                }

                                is UiState.EmptyListState -&amp;gt; {
                                    binding.run {
                                        loading.isVisible = false
                                        rvUser.isVisible = false
                                        tvNoItem.isVisible = true
                                    }
                                }

                                is UiState.OnErrorState -&amp;gt; {
                                    onError(state.message)
                                }
                            }
                        }
                    }

                    launch {
                        errorEvent.collect { event -&amp;gt;
                            when (event) {
                                is ErrorEvent.Error -&amp;gt; {
                                    Toast.makeText(
                                        this@MainActivity,
                                        event.errorMessage,
                                        Toast.LENGTH_SHORT,
                                    ).show()
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;각 Flow는 Activity에서 collect해줍니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;UIState는 상황에 맞게 UI를 업데이트 하고, SharedFlow는 전달받은 Event에 따라 처리해주면 됩니다.  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 알 수 있듯 StateFlow는 말그대로 상태를 저장하고 있는 Flow이고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;SharedFlow는 주로 이벤트를 전달할때 사용하는 Flow로 보면 좋을 것 같습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;ShredFlow의 경우 replay , extraBufferCapacity , OnBufferOverflow의 옵션이 존재하며 각 설명은 아래 표로 확인 부탁드립니다.&lt;/p&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 style=&quot;width: 33.3333%; text-align: center;&quot;&gt;이름&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;replay&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;기본값이 0으로 새로운 구독자들에게 &lt;br /&gt;이전 이벤트를 몇개를 방출할지 지정하는 옵션&lt;br /&gt;(0으로 되어있을 경우에는 가장 마지막에 저장된 이벤트가 방출됨 &lt;br /&gt;즉, StateFlow와 같은 동작이 된다.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;extraBufferCapacity&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;버퍼 저장 크기를 지정하는 옵션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;OnBufferOverflow&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;버퍼 저장크기를 초과시 할 동작&lt;br /&gt;&lt;br /&gt;&lt;b&gt;BufferOverflow.SUSPEND&amp;nbsp;&lt;/b&gt;&lt;br /&gt;버퍼가 가득 찬 경우 버퍼에 여유공간이 생길때 까지 suspend 한다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;BufferOverflow.DROP_LATEST&lt;br /&gt;&lt;/b&gt;버퍼가 가득 찬 경우 가장 최근 데이터를 drop 하고 새로운 데이터를 버퍼에 넣는다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;BufferOverflow.DROP_OLDEST&amp;nbsp;&lt;br /&gt;&lt;/b&gt;버퍼가 가득 찬 경우 가장 오래된 데이터를 drop하고 새로운 데이터를 버퍼에 넣는다.&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이상으로 StateFlow와 SharedFlow를 활용하는 방법에 대해 알아보았습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 포스팅이 여러분에게 많은 도움이 되었으면 좋겠네요!  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘도 즐코하세요 :)&lt;/p&gt;</description>
      <category>Android/Kotlin</category>
      <category>Android</category>
      <category>android state</category>
      <category>android UIState</category>
      <category>Clean Architecture</category>
      <category>flow</category>
      <category>mvvm</category>
      <category>SharedFlow</category>
      <category>StateFlow</category>
      <category>UIState</category>
      <category>안드로이드</category>
      <author>뎁요</author>
      <guid isPermaLink="true">https://devyo-111commit.tistory.com/39</guid>
      <comments>https://devyo-111commit.tistory.com/39#entry39comment</comments>
      <pubDate>Wed, 5 Jul 2023 15:59:45 +0900</pubDate>
    </item>
    <item>
      <title>[Java][Kotlin][Android] 안드로이드 자바와 코틀린 함께 사용하기 - 안드로이드 Legacy 코드 유지보수 하기 (2)</title>
      <link>https://devyo-111commit.tistory.com/38</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 저번 포스팅에 이어서 Legacy 코드 유지보수 하는 방법에 대해서 알아보려 합니다  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘 포스팅할 내용은 자바 코드를 코틀린으로 변환하는 방법에 대해서 포스팅해보려 합니다.&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;043&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/043.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/043.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;코틀린과 자바를 함께 사용하는 방법에 대해서는 이전 포스팅을 참고 부탁드립니다!&lt;/p&gt;
&lt;figure id=&quot;og_1685422972638&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Java][Kotlin][Android] 안드로이드 자바와 코틀린 함께 사용하기 - 안드로이드 Legacy 코드 유지보수 하&quot; data-og-description=&quot;안녕하세요   거의 한 달 만에 포스팅으로 찾아뵙는 것 같습니다. 여러 프로젝트들을 경험하다 보면 자바로 되어있는 코드를 코틀린으로 바꾸거나 레거시 코드들을 유지 보수하면서 코틀린을&quot; data-og-host=&quot;devyo-111commit.tistory.com&quot; data-og-source-url=&quot;https://devyo-111commit.tistory.com/37&quot; data-og-url=&quot;https://devyo-111commit.tistory.com/37&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dqKzgQ/hySNbVFfzl/Urx7mKDmzlKkCdaKuMGsx1/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/dm9E2F/hySPrCye5b/3Bkls9wucnlXlPGVPKtjE1/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/ep2zos/hySNdzbw9g/8mmUSu89u68Odtfu79cdqK/img.jpg?width=1200&amp;amp;height=1543&amp;amp;face=438_344_729_662&quot;&gt;&lt;a href=&quot;https://devyo-111commit.tistory.com/37&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://devyo-111commit.tistory.com/37&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dqKzgQ/hySNbVFfzl/Urx7mKDmzlKkCdaKuMGsx1/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/dm9E2F/hySPrCye5b/3Bkls9wucnlXlPGVPKtjE1/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/ep2zos/hySNdzbw9g/8mmUSu89u68Odtfu79cdqK/img.jpg?width=1200&amp;amp;height=1543&amp;amp;face=438_344_729_662');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Java][Kotlin][Android] 안드로이드 자바와 코틀린 함께 사용하기 - 안드로이드 Legacy 코드 유지보수 하&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요   거의 한 달 만에 포스팅으로 찾아뵙는 것 같습니다. 여러 프로젝트들을 경험하다 보면 자바로 되어있는 코드를 코틀린으로 바꾸거나 레거시 코드들을 유지 보수하면서 코틀린을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;devyo-111commit.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Java 파일 Kotlin으로 변환하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f8f8; color: #333333; text-align: center;&quot;&gt;아래와 같이 원하는 파일을 'Convert Java File to Kotlin File'을 통해 변환해 줍니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-05-30 오후 2.00.54.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;1372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ6iF7/btshE1ihiE5/g4fdvMG7qZDc8W21yQPc9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ6iF7/btshE1ihiE5/g4fdvMG7qZDc8W21yQPc9k/img.png&quot; data-alt=&quot;코틀린으로 변환하는 방법&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ6iF7/btshE1ihiE5/g4fdvMG7qZDc8W21yQPc9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ6iF7%2FbtshE1ihiE5%2Fg4fdvMG7qZDc8W21yQPc9k%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;408&quot; height=&quot;1372&quot; data-filename=&quot;스크린샷 2023-05-30 오후 2.00.54.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;1372&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;자동으로 변환이 완료된 후 아래와 같은 팝업이 나타나는데 그대로 진행시켜 줍니다. (한마디로 더 좋은 코드가 있는데 변한 시켜 줄까?)&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o4vXh/btshE9m1Qel/Z3XkSa7a02S0dNm2w1kgtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o4vXh/btshE9m1Qel/Z3XkSa7a02S0dNm2w1kgtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o4vXh/btshE9m1Qel/Z3XkSa7a02S0dNm2w1kgtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo4vXh%2FbtshE9m1Qel%2FZ3XkSa7a02S0dNm2w1kgtK%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;606&quot; height=&quot;217&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;314&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;이러면 간단하게 Java코드가 Kotlin코드로 변환되는 것을 보실 수 있습니다  &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;이렇게만 끝나면 재미없으니 RxJava로 되어있던 부분을 Coroutine으로 변환하는 작업까지 함께 진행해보려 합니다.&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;기존&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1685424534663&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// RestApi.kt
interface RestApi {
    @GET(Utils.GET_BEACH_CONGESTION_URL)
    fun getBeachCongestion() : Single&amp;lt;BeachListDTO&amp;gt;
}

// BeachRepository.kt
interface BeachRepository {
    /**
     * 해수욕장 혼잡도 가져오기
     * **/
    fun getBeachCongestion() : Single&amp;lt;BeachListDTO&amp;gt;
}

// BeachRepositoryImpl.kt
class BeachRepositoryImpl @Inject constructor(
    private val restApi: RestApi
) : BeachRepository {
    /**
     * 해수욕장 혼잡도 가져오기
     * **/
    override fun getBeachCongestion(): Single&amp;lt;BeachListDTO&amp;gt; {
        return restApi.getBeachCongestion()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;기존 코드는 Rx를 활용하기 위해 Single객체를 반환하도록 구성되어 있었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;해당 부분을 함께 바꿔보도록 하겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변경 후&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1685427319075&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// RestApi.kt
interface RestApi {
    @GET(Utils.GET_BEACH_CONGESTION_URL)
    suspend fun getBeachCongestion() : Response&amp;lt;BeachListDTO&amp;gt;
}

// BeachRepository.kt
interface BeachRepository {
    /**
     * 해수욕장 혼잡도 가져오기
     * **/
    suspend fun getBeachCongestion() : Flow&amp;lt;NetworkResult&amp;lt;BeachListDTO&amp;gt;&amp;gt;
}

// BeachRepositoryImpl.kt
class BeachRepositoryImpl @Inject constructor(
    private val restApi: RestApi
) : BeachRepository {
    /**
     * 해수욕장 혼잡도 가져오기
     * **/
    override suspend fun getBeachCongestion(): Flow&amp;lt;NetworkResult&amp;lt;BeachListDTO&amp;gt;&amp;gt; {
        val response = restApi.getBeachCongestion()
        return if(response.isSuccessful) {
            flow{
                emit(NetworkResult.Success(response.body()!!))
            }
        } else {
            flow{
                emit(NetworkResult.Failure(&quot;네트워크 통신에 실패했습니다!&quot;))
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위처럼 Single 객체를 걷어내고 Flow를 반환하도록 수정하였습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이어서 ViewModel과 Activity코드를 바꿔보도록 하겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기존&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1685427467739&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@HiltViewModel
public class MainViewModel extends ViewModel {
    private BeachRepository mBeachRepository;
    private CompositeDisposable mCompositeDisposable;

    private MutableLiveData&amp;lt;List&amp;lt;BeachDTO&amp;gt;&amp;gt; mBeachList = new MutableLiveData&amp;lt;&amp;gt;();
    public LiveData&amp;lt;List&amp;lt;BeachDTO&amp;gt;&amp;gt; getBeachList() {
        return mBeachList;
    }

    private MutableLiveData&amp;lt;String&amp;gt; mToastMessage = new MutableLiveData&amp;lt;&amp;gt;();
    public void setToastMessage(String message) {
        mToastMessage.setValue(message);
    }
    public MutableLiveData&amp;lt;String&amp;gt; getToastMessage() {
        return mToastMessage;
    }

    @Inject
    MainViewModel(BeachRepository repository){
        mBeachRepository = repository;
    }

    /**
     * RxJava를 통해 해수욕장 혼잡도를 가져오는 메소드
     * **/
    public void callAllBeachList() {
        if(mCompositeDisposable == null){
            mCompositeDisposable = new CompositeDisposable();
        }
        mCompositeDisposable.add(
                mBeachRepository.getBeachCongestion()
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribeWith(new DisposableSingleObserver&amp;lt;BeachListDTO&amp;gt;(){
                            @Override
                            public void onSuccess(@NonNull BeachListDTO beachListDTO) {
                                mBeachList.setValue(beachListDTO.getAllBeachList());
                            }

                            @Override
                            public void onError(@NonNull Throwable e) {
                                mToastMessage.setValue(&quot;통신에 실패했습니다!&quot;);
                            }
                        })
        );
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        if(mCompositeDisposable != null){
            mCompositeDisposable.clear(); // ViewModel이 clear 될때 CompositeDisposable 삭제
            mCompositeDisposable = null;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변경 후&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1685427593661&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sealed class NetworkResult&amp;lt;T&amp;gt; {
    data class Loading&amp;lt;T&amp;gt;(val isLoading : Boolean) : NetworkResult&amp;lt;T&amp;gt;()
    data class Success&amp;lt;T&amp;gt;(val data : T) : NetworkResult&amp;lt;T&amp;gt;()
    data class Failure&amp;lt;T&amp;gt;(val errorMessage : String) : NetworkResult&amp;lt;T&amp;gt;()
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1685427420211&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MainViewModel @Inject constructor(
    private val beachRepository : BeachRepository
    ) : ViewModel() {
    private val _beachList = MutableStateFlow&amp;lt;List&amp;lt;BeachDTO&amp;gt;&amp;gt;(emptyList())
    val beachList: StateFlow&amp;lt;List&amp;lt;BeachDTO&amp;gt;&amp;gt;
    get() = _beachList

    /**
     * Coroutine과 Flow를 네트워크 통신을 하는 함수
     */
    fun callAllBeachList() {
        viewModelScope.launch {
            beachRepository.getBeachCongestion().collect{ result -&amp;gt;
                when(result){
                    is NetworkResult.Success -&amp;gt; {
                        _beachList.value = result.data.getAllBeachList()
                    }

                    is NetworkResult.Failure -&amp;gt; {
                        Log.d(TAG, &quot;callAllBeachList: ${result.errorMessage}&quot;)
                    }

                    is NetworkResult.Loading -&amp;gt; {
                        Log.d(TAG, &quot;callAllBeachList: Loading...&quot;)
                    }
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Rx를 걷어내고 해당 부분을 Corouine을 통해 값을 수신하도록 수정하였습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아울러, LiveData를 사용하던 부분도 StateFlow를 사용하도록 수정하였습니다 :)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기존&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1685427951584&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
    private static final String TAG = &quot;MainActivity&quot;;

    private ActivityMainBinding mBinding;
    private MainViewModel mViewModel;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this , R.layout.activity_main);
        mViewModel = new ViewModelProvider(this).get(MainViewModel.class);
        mViewModel.callAllBeachList();
        observeData();
    }

    private void observeData() {
        mViewModel.getToastMessage().observe(this, new Observer&amp;lt;String&amp;gt;() { // Toast Message
            @Override
            public void onChanged(String message) {
                Toast.makeText(MainActivity.this, message , Toast.LENGTH_SHORT).show();
            }
        });

        mViewModel.getBeachList().observe(this, new Observer&amp;lt;List&amp;lt;BeachDTO&amp;gt;&amp;gt;() {
            @Override
            public void onChanged(List&amp;lt;BeachDTO&amp;gt; beachDTOs) {
                for(BeachDTO beach : beachDTOs){
                    Log.d(TAG, &quot;onChanged: &quot; + beach.toString());
                }
            }
        });
    }
}&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;b&gt;변경 후&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1685427920370&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private const val TAG = &quot;MainActivity&quot;

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private var binding: ActivityMainBinding? = null
    private val viewModel : MainViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        viewModel.callAllBeachList()
        observeData()
    }

    private fun observeData() {
       with(viewModel){
           lifecycleScope.launch{
               repeatOnLifecycle(Lifecycle.State.STARTED){
                   launch {
                       beachList.collect{ list -&amp;gt;
                           if(list.isNotEmpty()){
                               Log.d(TAG, &quot;observeData: $list&quot;)
                           }
                       }
                   }
               }
           }
       }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 Activity 코드입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;간단한 예제를 적용해 본 것이지만 코드가 매우 간결해지는 것이 느껴졌습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여러분들의 생각은 어떠신가요?  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이상으로 포스팅을 마치며&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Legacy코드를 유지 보수하는 여러분들에게 많은 도움이 되었으면 좋겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘도 즐코하세요 :)&lt;/p&gt;</description>
      <category>Android</category>
      <category>Android</category>
      <category>android coroutine</category>
      <category>android java</category>
      <category>Android Kotlin</category>
      <category>Android rx</category>
      <category>Coroutine</category>
      <category>Legacy</category>
      <category>RXjava</category>
      <category>레거시코드</category>
      <category>안드로이드</category>
      <author>뎁요</author>
      <guid isPermaLink="true">https://devyo-111commit.tistory.com/38</guid>
      <comments>https://devyo-111commit.tistory.com/38#entry38comment</comments>
      <pubDate>Tue, 30 May 2023 15:47:04 +0900</pubDate>
    </item>
    <item>
      <title>[Java][Kotlin][Android] 안드로이드 자바와 코틀린 함께 사용하기 - 안드로이드 Legacy 코드 유지보수 하기 (1)</title>
      <link>https://devyo-111commit.tistory.com/37</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;거의 한 달 만에 포스팅으로 찾아뵙는 것 같습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여러 프로젝트들을 경험하다 보면 자바로 되어있는 코드를 코틀린으로 바꾸거나&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;레거시 코드들을 유지 보수하면서 코틀린을 함께 사용하는 경우가 더러 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 이런 경우에 대한 여러가지 내용들을 포스팅해보려 합니다.&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;018&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/018.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/018.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅은 총 2편으로 기획이 될 것으로 보이며&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;1편(본 포스팅)은 자바와 코틀린을 함께 사용하는 방법&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;2편은 자바 코드를 코틀린으로 변경하는 방법에 대해서 포스팅 해보려 합니다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 바로 시작해보도록 할까요?  &lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 설정&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자바 기반 프로젝트에서 코틀린을 사용하기 위해 위 코드와 같이 build.gradle을 설정해줍니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1684465584347&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Project gradle
plugins {
    ... // 생략
    id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1684465589405&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Module gradle
plugins {
	... // 생략
    id 'org.jetbrains.kotlin.android'
}

android {
	... // 생략
    
    sourceSets{
        main.java.srcDirs+='src/main/kotlin'
    }
}

dependencies {
    ... // 생략
    
    // Kotlin
    implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.10'
}&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;b&gt;2. 디렉토리 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Android Studio에서 프로젝트 보기 설정을 Android -&amp;gt; Project로 변경 후 main에 kotlin이라는 이름으로 디렉토리를 생성해 줍니다.&lt;/b&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-origin-width=&quot;1246&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lC8td/btsgvBKu5uF/K7Tdkq7jeMkfGqceKWBV90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lC8td/btsgvBKu5uF/K7Tdkq7jeMkfGqceKWBV90/img.png&quot; data-alt=&quot;디렉토리 생성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lC8td/btsgvBKu5uF/K7Tdkq7jeMkfGqceKWBV90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlC8td%2FbtsgvBKu5uF%2FK7Tdkq7jeMkfGqceKWBV90%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;559&quot; height=&quot;368&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;디렉토리 생성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;우선 기본적인 설정은 이렇게 마치면 되겠습니다.  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 지금부터는 아직 자바를 쓰고 있는 프로젝트에 여러 가지 라이브러리 및 패턴 등을 적용시키는 방법을 알아보겠습니다.&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;&lt;b&gt;&amp;middot; &lt;/b&gt;&lt;b&gt;MainActivity&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1684466185269&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
    private static final String TAG = &quot;MainActivity&quot;;

    private ActivityMainBinding mBinding;
    private MainViewModel mViewModel;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this , R.layout.activity_main);
        mViewModel = new ViewModelProvider(this).get(MainViewModel.class);
        mViewModel.callAllBeachList();
        observeData();
    }

    private void observeData() {
        mViewModel.getToastMessage().observe(this, new Observer&amp;lt;String&amp;gt;() { // Toast Message
            @Override
            public void onChanged(String message) {
                Toast.makeText(MainActivity.this, message , Toast.LENGTH_SHORT).show();
            }
        });

        mViewModel.getBeachList().observe(this, new Observer&amp;lt;List&amp;lt;BeachDTO&amp;gt;&amp;gt;() {
            @Override
            public void onChanged(List&amp;lt;BeachDTO&amp;gt; beachDTOs) {
                for(BeachDTO beach : beachDTOs){
                    Log.d(TAG, &quot;onChanged: &quot; + beach.toString());
                }
            }
        });
    }
}&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;b&gt;&amp;middot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;MainViewModel&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1684466514017&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@HiltViewModel
public class MainViewModel extends ViewModel {
    private BeachRepository mBeachRepository;
    private CompositeDisposable mCompositeDisposable;

    private MutableLiveData&amp;lt;List&amp;lt;BeachDTO&amp;gt;&amp;gt; mBeachList = new MutableLiveData&amp;lt;&amp;gt;();
    public LiveData&amp;lt;List&amp;lt;BeachDTO&amp;gt;&amp;gt; getBeachList() {
        return mBeachList;
    }

    private MutableLiveData&amp;lt;String&amp;gt; mToastMessage = new MutableLiveData&amp;lt;&amp;gt;();
    public void setToastMessage(String message) {
        mToastMessage.setValue(message);
    }
    public MutableLiveData&amp;lt;String&amp;gt; getToastMessage() {
        return mToastMessage;
    }

    @Inject
    MainViewModel(BeachRepository repository){
        mBeachRepository = repository;
    }

    /**
     * RxJava를 통해 해수욕장 혼잡도를 가져오는 메소드
     * **/
    public void callAllBeachList() {
        if(mCompositeDisposable == null){
            mCompositeDisposable = new CompositeDisposable();
        }
        mCompositeDisposable.add(
                mBeachRepository.getBeachCongestion()
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribeWith(new DisposableSingleObserver&amp;lt;BeachListDTO&amp;gt;(){
                            @Override
                            public void onSuccess(@NonNull BeachListDTO beachListDTO) {
                                mBeachList.setValue(beachListDTO.getAllBeachList());
                            }

                            @Override
                            public void onError(@NonNull Throwable e) {
                                mToastMessage.setValue(&quot;통신에 실패했습니다!&quot;);
                            }
                        })
        );
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        if(mCompositeDisposable != null){
            mCompositeDisposable.clear(); // ViewModel이 clear 될때 CompositeDisposable 삭제
            mCompositeDisposable = null;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위 보시는 바와 같이 MainActivity는 Java기반으로 RxJava와 LiveData를 통해 통신하고 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;해당 Activity와 함께 사용하는 Repository와 Hilt의 module , Model에서 사용할 class 등을 코틀린으로&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;작성해 함께 사용하는 방법에 대해 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;middot; RestApi (Retrofit API 통신을 위한 Interface)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1684466769289&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface RestApi {
    @GET(Utils.GET_BEACH_CONGESTION_URL)
    fun getBeachCongestion() : Single&amp;lt;BeachListDTO&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;size16&quot;&gt;&lt;b&gt;&amp;middot; BeachRepository ( Repository Interface RxJava와 함께 사용하기 위해 Single 객체를 전달받는다. )&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1684466823518&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface BeachRepository {
    /**
     * 해수욕장 혼잡도 가져오기
     * **/
    fun getBeachCongestion() : Single&amp;lt;BeachListDTO&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;size16&quot;&gt;&lt;b&gt;&amp;middot; BeachRepositoryImpl (Repository의 구현체 )&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1684466867883&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class BeachRepositoryImpl @Inject constructor(
    private val restApi: RestApi
) : BeachRepository {
    /**
     * 해수욕장 혼잡도 가져오기
     * **/
    override fun getBeachCongestion(): Single&amp;lt;BeachListDTO&amp;gt; {
        return restApi.getBeachCongestion()
    }
}&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;b&gt;&amp;middot; Model에서 사용할 DTO class들&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1684467007493&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class BeachDTO(
    val etlDt : String = &quot;&quot;,
    val seqId : Int = -1,
    val poiNm : String = &quot;&quot;,
    val uniqPop : Int = -1,
    var congestion : String = &quot;&quot;
)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1684467099869&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class BeachListDTO {
    private lateinit var mBeachList : MutableList&amp;lt;BeachDTO&amp;gt;

    @SerializedName(&quot;Beach0&quot;)
    val beach0 : BeachDTO? = null
    @SerializedName(&quot;Beach1&quot;)
    val beach1 : BeachDTO? = null
    @SerializedName(&quot;Beach2&quot;)
    ... 생략

    fun getAllBeachList() : MutableList&amp;lt;BeachDTO&amp;gt; {
        if(!::mBeachList.isInitialized){
            mBeachList = mutableListOf()
            mBeachList.add(beach0 as BeachDTO)
            mBeachList.add(beach1 as BeachDTO)
            mBeachList.add(beach2 as BeachDTO)
            ... 생략
        }
       return mBeachList
    }
}&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;b&gt;&amp;middot; ProvideModule( Provides를 해줄 요소들을 모아놓은 Module )&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1684467198504&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Module
@InstallIn(SingletonComponent::class)
object ProvideModule {
    @Provides
    @Singleton
    fun provideRestApi(okHttpClient: OkHttpClient) : RestApi {
        return Retrofit.Builder()
            .baseUrl(Utils.BEACH_BASE_URL)
            .client(okHttpClient)
            .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(RestApi::class.java)
    }

    @Provides
    @Singleton
    fun provideOkHttpClient() : OkHttpClient {
        val interceptor = HttpLoggingInterceptor()
        interceptor.level= HttpLoggingInterceptor.Level.BODY

        return OkHttpClient.Builder()
            .addInterceptor(interceptor)
            .connectTimeout(Utils.CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
            .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;&lt;b&gt;&amp;middot; BindModule( Binds를 해줄 요소들을 모아놓은 Module )&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1684467280385&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Module
@InstallIn(SingletonComponent::class)
abstract class BindModule {
    @Binds
    @Singleton
    abstract fun bindBeachRepository(beachRepositoryImpl: BeachRepositoryImpl) : BeachRepository
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위에서 보이듯이 Hilt 및 여러 가지 Repository 등은 코틀린으로 새로 추가하여 사용할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;애초에 코틀린의 장점 중 하나가 자바와 호환이 된다는 것이기 때문에 이렇게 사용하는 것은 당연할 텐데요&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오래된 프로젝트들을 유지 보수 하다 보면 자바로 된 부분은 자바로 유지보수를 진행하고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;신규 기능부터는 코틀린을 적용하여 개발하는 경우가 많은데 어떻게 시작을 해야 할지 감이 안 잡히는 경우가 많습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그런 분들에게 조금이나마 도움이 되었으면 좋겠습니다.&amp;nbsp;  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다음 포스팅으로는 해당 자바 코드를 어떻게 코틀린으로 Migration 하는지에 대해 다뤄보겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그럼&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘도 즐코하세요 :)&lt;/p&gt;</description>
      <category>Android</category>
      <category>Android</category>
      <category>java</category>
      <category>Java Kotlin</category>
      <category>Java Kotlin 함께</category>
      <category>Kotlin</category>
      <category>안드로이드</category>
      <category>안드로이드 Java Kotlin</category>
      <category>자바</category>
      <category>자바 코틀린 함께</category>
      <category>코틀린</category>
      <author>뎁요</author>
      <guid isPermaLink="true">https://devyo-111commit.tistory.com/37</guid>
      <comments>https://devyo-111commit.tistory.com/37#entry37comment</comments>
      <pubDate>Fri, 19 May 2023 12:43:18 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin][Android] 안드로이드 Coroutine Flow 적용하기 (2) ( LiveData를 Flow로 변경하기 : LiveData 와 Flow 비교하기 )</title>
      <link>https://devyo-111commit.tistory.com/36</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 저번 포스팅에 이어 LiveData를 Flow로 이전하는 과정을 포스팅해보려 합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;뿐만 아니라, LiveData와 Flow의 차이점에 대해서도 함께 다뤄볼 예정입니다  &lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;030&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/030.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/030.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이전 포스팅은 아래 링크를 통해 참고 바랍니다.  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1679486791602&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Kotlin][Android] 안드로이드 Coroutine Flow 적용하기 (1) (네트워크 통신 Flow로 이전하기)&quot; data-og-description=&quot;안녕하세요   오늘은 안드로이드 MVVM패턴에서 Flow를 어떻게 적용하는지를 알아보려고 합니다   Flow에 대한 기본 포스팅은 아래 링크를 참고 바랍니다   [Kotlin] 코틀린 Coroutine에 대하여 (3) -&quot; data-og-host=&quot;devyo-111commit.tistory.com&quot; data-og-source-url=&quot;https://devyo-111commit.tistory.com/35&quot; data-og-url=&quot;https://devyo-111commit.tistory.com/35&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cElquj/hyR0ptEsfv/KRYa6rKgsQlPLvlhjwT7sk/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/jGYAg/hyR14nSmqY/tgcXgikg86R1D7Xk11Auwk/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/jSrs8/hyR0ryeY0y/4JoEtoH8fd65FPKtGXWiMk/img.jpg?width=1200&amp;amp;height=1543&amp;amp;face=438_344_729_662&quot;&gt;&lt;a href=&quot;https://devyo-111commit.tistory.com/35&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://devyo-111commit.tistory.com/35&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cElquj/hyR0ptEsfv/KRYa6rKgsQlPLvlhjwT7sk/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/jGYAg/hyR14nSmqY/tgcXgikg86R1D7Xk11Auwk/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/jSrs8/hyR0ryeY0y/4JoEtoH8fd65FPKtGXWiMk/img.jpg?width=1200&amp;amp;height=1543&amp;amp;face=438_344_729_662');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Kotlin][Android] 안드로이드 Coroutine Flow 적용하기 (1) (네트워크 통신 Flow로 이전하기)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요   오늘은 안드로이드 MVVM패턴에서 Flow를 어떻게 적용하는지를 알아보려고 합니다   Flow에 대한 기본 포스팅은 아래 링크를 참고 바랍니다   [Kotlin] 코틀린 Coroutine에 대하여 (3) -&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;devyo-111commit.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;LiveData를 Flow로 변경하기에 앞서 둘의 차이점과 왜 Flow를 사용하는지에 대해서 알아보도록 하겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;LiveData&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LiveData는 관찰 가능한&amp;nbsp;data holder class입니다.&lt;/b&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;text-align: left;&quot;&gt;또한, LiveData는 다른 안드로이드 구성요소에서 관찰할 수 있는 데이터 집합을 보유할 수 있으며 생명주기를 인식하기 때문에&amp;nbsp;&lt;/span&gt;데이터를 관찰하는 구성 요소가 파괴되거나 활성화되지 않으면 해당 observer에게 데이터 게시를 중지합니다.&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;b&gt;LiveData의 장점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메모리 누수가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 생명 주기를 인식하기 때문에 중지된 컴포넌트로 인한 충돌이 없고, 수동으로 생명주기를 관리해 줄 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UI와 Data의 일치성을 보장한다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;그렇다면 왜 Flow를 사용하려 할까요?  &lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;figure id=&quot;og_1679551615358&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;LiveData 개요 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;LiveData를 사용하여 수명 주기를 인식하는 방식으로 데이터를 처리합니다.&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/topic/libraries/architecture/livedata?hl=ko#livedata-in-architecture&quot; data-og-url=&quot;https://developer.android.com/topic/libraries/architecture/livedata?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b9ixGx/hyR1YPbehF/kLgf7YoRfaKQZ9Dn9Vny10/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/livedata?hl=ko#livedata-in-architecture&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/topic/libraries/architecture/livedata?hl=ko#livedata-in-architecture&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b9ixGx/hyR1YPbehF/kLgf7YoRfaKQZ9Dn9Vny10/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;LiveData 개요 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;LiveData를 사용하여 수명 주기를 인식하는 방식으로 데이터를 처리합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Android Developer에 따르면&lt;b&gt; LiveData는 비동기 데이터 스트림을 처리하도록 설계 되어 있지 않다고 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;LiveData는 &lt;b&gt;기본적으로 변경된 값을 Main Thread에서 관찰하기 때문에 다른 레이어에서 데이터 스트림을 관찰하고 싶을 경우에는&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flow를 사용한 다음 asLiveData()를 통해 ViewModel에서 변환하여 사용하는 게 좋다&lt;/b&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;figure id=&quot;og_1679551700117&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Android] 안드로이드 Clean Architecture에 대해서 ( 1 ) - Clean Architecture의 개념&quot; data-og-description=&quot;안녕하세요   오늘은 Clean Architecture에 대해서 공부한 내용을 포스팅해보려 합니다. 프로젝트에 적용하는 방법까지 포스팅하기엔 내용이 길어질 것 같아 오늘은 개념에 대해서만 간략하게 포&quot; data-og-host=&quot;devyo-111commit.tistory.com&quot; data-og-source-url=&quot;https://devyo-111commit.tistory.com/33&quot; data-og-url=&quot;https://devyo-111commit.tistory.com/33&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cn55JJ/hyR12RAOUU/qoSPL21xAKK8fxjsuH42D1/img.jpg?width=721&amp;amp;height=469&amp;amp;face=0_0_721_469,https://scrap.kakaocdn.net/dn/hBRs0/hyR15AMcVH/C5Ea4GueuPRlIz3yC5Y0qK/img.jpg?width=721&amp;amp;height=469&amp;amp;face=0_0_721_469,https://scrap.kakaocdn.net/dn/cEtbnZ/hyR17ZFU27/kZaxQGqy9UCbpd4CqVlkG0/img.jpg?width=1200&amp;amp;height=1543&amp;amp;face=438_344_729_662&quot;&gt;&lt;a href=&quot;https://devyo-111commit.tistory.com/33&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://devyo-111commit.tistory.com/33&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cn55JJ/hyR12RAOUU/qoSPL21xAKK8fxjsuH42D1/img.jpg?width=721&amp;amp;height=469&amp;amp;face=0_0_721_469,https://scrap.kakaocdn.net/dn/hBRs0/hyR15AMcVH/C5Ea4GueuPRlIz3yC5Y0qK/img.jpg?width=721&amp;amp;height=469&amp;amp;face=0_0_721_469,https://scrap.kakaocdn.net/dn/cEtbnZ/hyR17ZFU27/kZaxQGqy9UCbpd4CqVlkG0/img.jpg?width=1200&amp;amp;height=1543&amp;amp;face=438_344_729_662');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Android] 안드로이드 Clean Architecture에 대해서 ( 1 ) - Clean Architecture의 개념&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요   오늘은 Clean Architecture에 대해서 공부한 내용을 포스팅해보려 합니다. 프로젝트에 적용하는 방법까지 포스팅하기엔 내용이 길어질 것 같아 오늘은 개념에 대해서만 간략하게 포&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;devyo-111commit.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;뿐만 아니라, 안드로이드의 Clean Architecture 측면에서 &lt;b&gt;Domain Layer에는&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;안드로이드 의존성이 없는 순수 자바/코틀린 코드만이 존재해야 하는데 해당 Layer에서 LiveData를 사용하기에는&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;부적합하므로 Flow를 통해 해당 부분을 해소할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1679550928867&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Android) LiveData와 Coroutine Flow 비교해보기&quot; data-og-description=&quot;요즘에 코루틴의 flow를 사용해서 안드로이드 개발을 하고 있는데, LiveData와 함께 flow를 사용 시 올바르게 사용하고 있는 건지에 대한 궁금증이 생겨서 적합한 방법에 대해 글을 써보려고 합니다.&quot; data-og-host=&quot;yoon-dailylife.tistory.com&quot; data-og-source-url=&quot;https://yoon-dailylife.tistory.com/71&quot; data-og-url=&quot;https://yoon-dailylife.tistory.com/71&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/8iiKD/hyR14V9o8k/pI6Mv0HmHB8XmK8Ouklsk0/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/bdOkEg/hyR160KVop/iOTw8U8HQIlaSDJ1wkkL5K/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/ZkjTl/hyR19wpdHH/4sAyIKaGf0dL3DiqTXUbQK/img.jpg?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900&quot;&gt;&lt;a href=&quot;https://yoon-dailylife.tistory.com/71&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yoon-dailylife.tistory.com/71&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/8iiKD/hyR14V9o8k/pI6Mv0HmHB8XmK8Ouklsk0/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/bdOkEg/hyR160KVop/iOTw8U8HQIlaSDJ1wkkL5K/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/ZkjTl/hyR19wpdHH/4sAyIKaGf0dL3DiqTXUbQK/img.jpg?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Android) LiveData와 Coroutine Flow 비교해보기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;요즘에 코루틴의 flow를 사용해서 안드로이드 개발을 하고 있는데, LiveData와 함께 flow를 사용 시 올바르게 사용하고 있는 건지에 대한 궁금증이 생겨서 적합한 방법에 대해 글을 써보려고 합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yoon-dailylife.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;하지만, Flow는 LiveData와는 다르게 생명주기를 인식하지 못하며 상태가 존재하지 않아 현재 무슨 값을 가지고 있는지 알기 어렵습니다. 뿐만 아니라, Cold Stream으로 사용자가 collect 하기 전까지는 값을 반환하지 않아 연속해서 들어오는 값에 대한 처리가 어렵습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;LiveData는 구성 변경 시에 안정성을 제공하고 최신 데이터를 뷰로 전달하는 역할을 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Flow는 UseCase, Repository, DataSources Layer와 긴밀하게 작동해 데이터를 수집 및 처리해서&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;서로 다른 코루틴 범위에서 작업을 실행한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그러므로 ViewModel, View 사이의 상호작용은 LiveData, 더 깊은 레이어와 쓰레딩 같은 더 복잡한 처리는 Flow가 처리한다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;즉, LiveData와 Flow는 서로의 단점을 보완해 주며 Repository에서 처리되는 일련의 데이터 스트림은 Flow를 통해 처리하고&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;전달받은 View에서의 처리는 LiveData를 통해 사용하는 구조로 많이 사용한다고 보면 될 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;하지만 ✋&lt;/b&gt;&lt;/h4&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;StateFlow의 등장&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;b&gt;StateFlow란 현재 상태와 새로운 상태 업데이트를 수집기에 내보내는 관찰 가능한 상태 홀더 흐름&lt;/b&gt;으로&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;value 속성을 통해 현재 상태값을 읽을 수 있습니다. &lt;/span&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;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;위에선 언급한 Flow의 단점을 해소하여 LiveData와 같이 만든 것이 바로 이 StateFlow입니다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;그렇다면 사용하는 방법은 어떻게 될까요?  &lt;/b&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;b&gt;StateFlow는 기본적으로 Read-Only입니다. 해당 값을 변경하기 위해서는 MutableStateFlow를 사용해야 합니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;b&gt;StateFlow는 LiveData와는 다르게 기본값을 설정해주어야 합니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;b&gt;StateFlow는 다른 Flow에서 수집하는 경우 &lt;span style=&quot;text-align: left;&quot;&gt;LiveData와는 다르게 &lt;/span&gt;View가 stop 되었을 때 자동으로 관찰을 멈추지 않아 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;repeatOnLifeCycle {} 혹은 flowWithLifecycle.collect{} 을 통해 값을 수집해야 합니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;repeatOnLifeCycle{} 은 여러 개의 Flow를 수집할 때 사용하며 &lt;br /&gt;&lt;span style=&quot;text-align: left;&quot;&gt;flowWithLifecycle.collect {}는 하나의 Flow만 수집할 때 사용합니다.&lt;/span&gt;&lt;/span&gt;&lt;/b&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;지금부터는 이전 포스팅에 이어 LiveData를 Flow로 변경하는 작업에 대해서 말씀드리겠습니다.&lt;/h3&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;[MyViewModel.kt] 네트워크 통신 결과를 관찰하던 LiveData를 stateFlow로 변경해주었습니다.&lt;/b&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: #ffffff;&quot;&gt;기존&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1679553089333&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@HiltViewModel
class MyViewModel @Inject constructor(
    private val getBeachCongestionList: GetBeachCongestionList
) : ViewModel(){
    private val _networkResult = MutableLiveData&amp;lt;NetworkResult&amp;lt;BeachCongestionList&amp;gt;&amp;gt;()
    val networkResult : LiveData&amp;lt;NetworkResult&amp;lt;BeachCongestionList&amp;gt;&amp;gt;
    get() = _networkResult

    private val _beachCongestionList = MutableLiveData&amp;lt;BeachCongestionList&amp;gt;()
    val beachCongestionList : LiveData&amp;lt;BeachCongestionList&amp;gt;
    get() = _beachCongestionList
    fun setBeachCongestionList(beachCongestionList: BeachCongestionList){
        _beachCongestionList.value = beachCongestionList
    }

    private val _toastMessage = MutableLiveData&amp;lt;String&amp;gt;()
    val toastMessage : LiveData&amp;lt;String&amp;gt;
    get() = _toastMessage

    fun getBeachInfo(){
        viewModelScope.launch {
            getBeachCongestionList.invoke().collect{ result -&amp;gt;
                _networkResult.value = result
            }
        }
    }

    fun onError(message : String) {
        _toastMessage.value = message
    }
}&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_1679553182488&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@HiltViewModel
class MyViewModel @Inject constructor(
    private val getBeachCongestionList: GetBeachCongestionList
) : ViewModel(){
    private val _networkResult = MutableStateFlow&amp;lt;NetworkResult&amp;lt;BeachCongestionList&amp;gt;&amp;gt;(NetworkResult.Success(BeachCongestionList(arrayListOf())))
    val networkResult : StateFlow&amp;lt;NetworkResult&amp;lt;BeachCongestionList&amp;gt;&amp;gt;
    get() = _networkResult

    private val _beachCongestionList =  MutableLiveData&amp;lt;BeachCongestionList&amp;gt;()
    val beachCongestionList : LiveData&amp;lt;BeachCongestionList&amp;gt;
    get() = _beachCongestionList
    fun setBeachCongestionList(beachCongestionList: BeachCongestionList){
        _beachCongestionList.value = beachCongestionList
    }

    private val _toastMessage = MutableLiveData&amp;lt;String&amp;gt;()
    val toastMessage : LiveData&amp;lt;String&amp;gt;
    get() = _toastMessage

    fun getBeachInfo(){
        viewModelScope.launch {
            getBeachCongestionList.invoke().collect{ result -&amp;gt;
                _networkResult.value = result
            }
        }
    }

    fun onError(message : String) {
        _toastMessage.value = message
    }
}&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;b&gt;[MainActivity.kt] 전달받은 값을 collect 하여 값을 관찰한다.&lt;/b&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: #ffffff;&quot;&gt;기존&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1679553323543&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private const val TAG = &quot;MainActivity&quot;

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private lateinit var binding : ActivityMainBinding
    private val viewModel : MyViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater).also {
            setContentView(it.root)
        }
        observeData()
    }

    override fun onResume() {
        super.onResume()
        viewModel.getBeachInfo()
    }

    private fun observeData() {
        with(viewModel){
            networkResult.observe(this@MainActivity){ result -&amp;gt;
                when(result){
                    is NetworkResult.Success -&amp;gt; setBeachCongestionList(result.data) // 성공시
                    is NetworkResult.Failure -&amp;gt; onError(result.code.toString()) // 실패시
                    is NetworkResult.Exception -&amp;gt; onError(result.errorMessage) // 예외 발생시
                    is NetworkResult.Loading -&amp;gt; { // 통신중
                        // 현재 프로젝트에서는 할일이 없음
                    }
                }
            }

            beachCongestionList.observe(this@MainActivity){
                Log.d(TAG, &quot;observeData: $it&quot;)
            }
            toastMessage.observe(this@MainActivity){
                Toast.makeText(this@MainActivity , it , Toast.LENGTH_SHORT).show()
            }
        }
    }
}&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;b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;flowWithLifecycle.collect {}를 사용한 방법 &lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1679553383568&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private const val TAG = &quot;MainActivity&quot;

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private lateinit var binding : ActivityMainBinding
    private val viewModel : MyViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater).also {
            setContentView(it.root)
        }
        observeData()
    }

    override fun onResume() {
        super.onResume()
        viewModel.getBeachInfo()
    }

    private fun observeData() {
        with(viewModel){
            lifecycleScope.launch{
                networkResult.flowWithLifecycle(lifecycle , Lifecycle.State.STARTED).collect{ result -&amp;gt; // 하나의 Flow만 collect할때 사용
                    when(result){
                        is NetworkResult.Success -&amp;gt; setBeachCongestionList(result.data)
                        is NetworkResult.Failure -&amp;gt; onError(result.code.toString())
                        is NetworkResult.Exception -&amp;gt; onError(result.errorMessage)
                        is NetworkResult.Loading -&amp;gt; {
                            // Noting to do
                        }
                    }
                }
            }

            beachCongestionList.observe(this@MainActivity){ list -&amp;gt;
                Log.d(TAG, &quot;observeData: $list&quot;)
            }

            toastMessage.observe(this@MainActivity){ message -&amp;gt;
                Log.d(TAG, &quot;observeData: $message&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;text-align: start;&quot;&gt;변경 후 ( &lt;b&gt;repeatOnLifecycle {}을&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;b&gt; 사용한 방법&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1679554532722&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private const val TAG = &quot;MainActivity&quot;

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private lateinit var binding : ActivityMainBinding
    private val viewModel : MyViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater).also {
            setContentView(it.root)
        }
        observeData()
    }

    override fun onResume() {
        super.onResume()
        viewModel.getBeachInfo()
    }

    private fun observeData() {
        with(viewModel){
            lifecycleScope.launch{
                repeatOnLifecycle(Lifecycle.State.STARTED){ // 여러개의 Flow를 collect 할때 사용
                    launch {
                        networkResult.collect{ result -&amp;gt;
                            when(result){
                                is NetworkResult.Success -&amp;gt; setBeachCongestionList(result.data)
                                is NetworkResult.Failure -&amp;gt; onError(result.code.toString())
                                is NetworkResult.Exception -&amp;gt; onError(result.errorMessage)
                                is NetworkResult.Loading -&amp;gt; {
                                    // Noting to do
                                }
                            }
                        }
                    }
                    
                    launch{
                        // 다른 Flow
                    }
                }
            }

            beachCongestionList.observe(this@MainActivity){ list -&amp;gt;
                Log.d(TAG, &quot;observeData: $list&quot;)
            }

            toastMessage.observe(this@MainActivity){ message -&amp;gt;
                Log.d(TAG, &quot;observeData: $message&quot;)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;(위 코드를 보면 모든 LiveData를 StateFlow로 변경시키지는 않았는데 다른 LiveData 또한 StateFlow로 변경해도 무관합니다.)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 LiveData와 Flow의 차이점 및 StateFlow를 통해 LiveData를 Flow로 이전하는 방법까지 모두 살펴보았습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;StateFlow가 등장하면서 현재 많은 API가 개발되고 있는 것 같은데요&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;나중에는 Kotlin기반 앱에서는 LiveData대신 Flow를 주로 사용하게 되지 않을까 생각해 봅니다.  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여러분들도 LiveData를 사용하던 프로젝트를 StateFlow로 이전해 보는 건 어떨까요?&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;긴 글 읽어주셔서 감사드리며&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이상 포스팅을 마치겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘도 즐코하세요 :)&lt;/p&gt;</description>
      <category>Android/Kotlin</category>
      <category>Android</category>
      <category>Android Flow</category>
      <category>Coroutine flow</category>
      <category>flow</category>
      <category>flow와 livedata</category>
      <category>livedata</category>
      <category>ShaterFlow</category>
      <category>StateFlow</category>
      <category>안드로이드</category>
      <category>코루틴 Flow</category>
      <author>뎁요</author>
      <guid isPermaLink="true">https://devyo-111commit.tistory.com/36</guid>
      <comments>https://devyo-111commit.tistory.com/36#entry36comment</comments>
      <pubDate>Thu, 23 Mar 2023 16:04:30 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin][Android] 안드로이드 Coroutine Flow 적용하기 (1) (네트워크 통신 Flow로 이전하기)</title>
      <link>https://devyo-111commit.tistory.com/35</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 안드로이드 MVVM패턴에서 Flow를 어떻게 적용하는지를 알아보려고 합니다  &lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends2&quot; data-emoticon-name=&quot;005&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends2/large/005.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends2/large/005.png&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Flow에 대한 기본 포스팅은 아래 링크를 참고 바랍니다  &lt;/p&gt;
&lt;figure id=&quot;og_1679455990516&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Kotlin] 코틀린 Coroutine에 대하여 (3) - Coroutine Flow(플로우)&quot; data-og-description=&quot;안녕하세요  오늘은 코루틴 관련 마지막 포스팅으로 코루틴 Flow에 대해 공부한 내용을 포스팅해보려 합니다. 1. Coroutine Flow란? Flow는 순차적으로 값을 내보내고 정상적으로 또는 예외로 완료되&quot; data-og-host=&quot;devyo-111commit.tistory.com&quot; data-og-source-url=&quot;https://devyo-111commit.tistory.com/16&quot; data-og-url=&quot;https://devyo-111commit.tistory.com/16&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bYDAbm/hyR18wK7HI/CaHVsO53MhlMLkZJLZmHZk/img.jpg?width=400&amp;amp;height=277&amp;amp;face=0_0_400_277,https://scrap.kakaocdn.net/dn/bmAh23/hyR1YVc26f/7qD4Dg8hQIewJcxrafwIy0/img.jpg?width=400&amp;amp;height=277&amp;amp;face=0_0_400_277,https://scrap.kakaocdn.net/dn/cp8pUo/hyR1WXoIww/0UzPhWf3pxkbcTubaVdKo1/img.png?width=2508&amp;amp;height=1186&amp;amp;face=0_0_2508_1186&quot;&gt;&lt;a href=&quot;https://devyo-111commit.tistory.com/16&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://devyo-111commit.tistory.com/16&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bYDAbm/hyR18wK7HI/CaHVsO53MhlMLkZJLZmHZk/img.jpg?width=400&amp;amp;height=277&amp;amp;face=0_0_400_277,https://scrap.kakaocdn.net/dn/bmAh23/hyR1YVc26f/7qD4Dg8hQIewJcxrafwIy0/img.jpg?width=400&amp;amp;height=277&amp;amp;face=0_0_400_277,https://scrap.kakaocdn.net/dn/cp8pUo/hyR1WXoIww/0UzPhWf3pxkbcTubaVdKo1/img.png?width=2508&amp;amp;height=1186&amp;amp;face=0_0_2508_1186');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Kotlin] 코틀린 Coroutine에 대하여 (3) - Coroutine Flow(플로우)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요  오늘은 코루틴 관련 마지막 포스팅으로 코루틴 Flow에 대해 공부한 내용을 포스팅해보려 합니다. 1. Coroutine Flow란? Flow는 순차적으로 값을 내보내고 정상적으로 또는 예외로 완료되&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;devyo-111commit.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 이전 Clean Architecture에 대한 포스팅을 할 때 사용했던 예제를 Flow로 이전하며 사용할 예정입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;해당 예제의 전체 코드 및 설명은 아래 포스팅을 참고해주세요  &lt;/p&gt;
&lt;figure id=&quot;og_1679456170459&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Android] 안드로이드 Clean Architecture에 대해서 (2) - 안드로이드 프로젝트에 적용하는 방법&quot; data-og-description=&quot;안녕하세요   오늘은 지난 포스팅에 이어 안드로이드 프로젝트에서 어떻게 Clean Architecture를 적용시키는지에 대해서 포스팅해보려 합니다. Clean Architecture의 개념에 대해서는 아래 포스팅을 참&quot; data-og-host=&quot;devyo-111commit.tistory.com&quot; data-og-source-url=&quot;https://devyo-111commit.tistory.com/34&quot; data-og-url=&quot;https://devyo-111commit.tistory.com/34&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/DTwkh/hyR19bmKg2/lOiSxePhcLSVg1oa0L6KzK/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/7J9CA/hyR1Xos8SB/GlnzE92oQ69S21f9BVWWE1/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/oQaLm/hyR1T0FFx1/kCJKELZvDGLPam9wCMF3mK/img.jpg?width=1200&amp;amp;height=1543&amp;amp;face=438_344_729_662&quot;&gt;&lt;a href=&quot;https://devyo-111commit.tistory.com/34&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://devyo-111commit.tistory.com/34&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/DTwkh/hyR19bmKg2/lOiSxePhcLSVg1oa0L6KzK/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/7J9CA/hyR1Xos8SB/GlnzE92oQ69S21f9BVWWE1/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/oQaLm/hyR1T0FFx1/kCJKELZvDGLPam9wCMF3mK/img.jpg?width=1200&amp;amp;height=1543&amp;amp;face=438_344_729_662');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Android] 안드로이드 Clean Architecture에 대해서 (2) - 안드로이드 프로젝트에 적용하는 방법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요   오늘은 지난 포스팅에 이어 안드로이드 프로젝트에서 어떻게 Clean Architecture를 적용시키는지에 대해서 포스팅해보려 합니다. Clean Architecture의 개념에 대해서는 아래 포스팅을 참&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;devyo-111commit.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구현체가 수정된 경우 interface도 함께 수정되었다는점 참고 바랍니다.  &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Data Layer&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[MyApi.kt] Response에 객체를 담도록 수정&lt;/b&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;/p&gt;
&lt;pre id=&quot;code_1679456519776&quot; class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface MyApi {
    @GET(DataConst.GET_CONGESTION_URL)
    suspend fun getBeachCongestion() : BeachCongestionListDTO
}&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;변경 후&lt;/p&gt;
&lt;pre id=&quot;code_1679456516972&quot; class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface MyApi {
    @GET(DataConst.GET_CONGESTION_URL)
    suspend fun getBeachCongestion() : Response&amp;lt;BeachCongestionListDTO&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[BeachDataSourceImpl.kt] 반환값을 seald class로 감싸고 Flow로 반환하도록 수정&lt;/b&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;/p&gt;
&lt;pre id=&quot;code_1679456959998&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class BeachDataSourceImpl @Inject constructor(
    private val myApi: MyApi
) : BeachDataSource{
    override suspend fun getCongestionList(): BeachCongestionList {
        return BeachMapper.mapperToBeachCongestionList(myApi.getBeachCongestion())
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경 후&lt;/p&gt;
&lt;pre id=&quot;code_1679456976662&quot; class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class BeachDataSourceImpl @Inject constructor(
    private val myApi: MyApi
) : BeachDataSource{
    override suspend fun getCongestionList(): Flow&amp;lt;NetworkResult&amp;lt;BeachCongestionList&amp;gt;&amp;gt; {
        return BeachMapper.mapperToBeachCongestionList(myApi.getBeachCongestion())
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[MainRepositoryImpl.kt] 반환값을 seald class로 감싸고 Flow로 반환하도록 수정&lt;/b&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기존&lt;/p&gt;
&lt;pre id=&quot;code_1679457061402&quot; class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MainRepositoryImpl @Inject constructor(
    private val beachDataSource: BeachDataSource
) : MainRepository {
    override suspend fun getBeachCongestion(): BeachCongestionList {
        return beachDataSource.getCongestionList()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;변경 후&lt;/p&gt;
&lt;pre id=&quot;code_1679457127184&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MainRepositoryImpl @Inject constructor(
    private val beachDataSource: BeachDataSource
) : MainRepository {
    override suspend fun getBeachCongestion(): Flow&amp;lt;NetworkResult&amp;lt;BeachCongestionList&amp;gt;&amp;gt; {
        return beachDataSource.getCongestionList()
    }
}&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;b&gt;[BeachMapper.kt] &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전달받은 객체를 domain의 model과 mapping 후에 성공/실패/예외 여부에 따라 결괏값을 seald class에 담아 보내는 Flow를 생성하여 반환하도록 수정&lt;/b&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기존&lt;/p&gt;
&lt;pre id=&quot;code_1679457168667&quot; class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object BeachMapper {
    private fun mapperToBeach(beachDTO: BeachDTO) : Beach {
        val beach = beachDTO.run {
            Beach(poiNm, congestion)
        }
        return beach
    }

    fun mapperToBeachCongestionList(beachCongestionListDTO: BeachCongestionListDTO) : BeachCongestionList {
        val list = arrayListOf&amp;lt;Beach&amp;gt;()
        beachCongestionListDTO.getAllBeachList().forEach {
            val beach = mapperToBeach(it)
            list.add(beach)
        }

        return BeachCongestionList(list)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;변경 후&lt;/p&gt;
&lt;pre id=&quot;code_1679457168668&quot; class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object BeachMapper {
    private fun mapperToBeach(beachDTO: BeachDTO) : Beach {
        val beach = beachDTO.run {
            Beach(poiNm, congestion)
        }
        return beach
    }

     fun mapperToBeachCongestionList(response : Response&amp;lt;BeachCongestionListDTO&amp;gt;) : Flow&amp;lt;NetworkResult&amp;lt;BeachCongestionList&amp;gt;&amp;gt; {
        if(response.isSuccessful){
            val list = arrayListOf&amp;lt;Beach&amp;gt;()
            response.body()?.getAllBeachList()?.forEach {
                val beach = mapperToBeach(it)
                list.add(beach)
            }
            val beachCongestionList = BeachCongestionList(list)
            return flow { // 성공시에 반환할 Flow
                emit(NetworkResult.Success(beachCongestionList))
            }
        } else {
            return flow&amp;lt;NetworkResult&amp;lt;BeachCongestionList&amp;gt;&amp;gt; {  // 실패시에 반환할 Flow
                emit(NetworkResult.Failure(response.code()))
            }.catch { exception -&amp;gt; // 예외처리
                emit(NetworkResult.Exception(exception.message!!))
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; Domain Layer&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[NetworkResult.kt] 네트워크 통신의 결과값을 담아 보내는 sealed class&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1679457604257&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * 네트워크 통신의 결과들을 모아놓은 sealed class
 * **/
sealed class NetworkResult&amp;lt;T&amp;gt; {
    data class Loading&amp;lt;T&amp;gt;(val isLoading: Boolean) : NetworkResult&amp;lt;T&amp;gt;()
    data class Success&amp;lt;T&amp;gt;(val data: T) : NetworkResult&amp;lt;T&amp;gt;()
    data class Exception&amp;lt;T&amp;gt;(val errorMessage: String) : NetworkResult&amp;lt;T&amp;gt;()
    data class Failure&amp;lt;T&amp;gt;(val code: Int) : NetworkResult&amp;lt;T&amp;gt;()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[GetBeachCongstionList.kt] &lt;b&gt;반환값을 seald class로 감싸고 Flow로 반환하도록 수정&lt;/b&gt;&lt;/b&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기존&lt;/p&gt;
&lt;pre id=&quot;code_1679482711171&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class GetBeachCongestionList @Inject constructor(
    private val repository: MainRepository
) {
    suspend fun invoke() : BeachCongestionList {
        return repository.getBeachCongestion()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;변경 후&lt;/p&gt;
&lt;pre id=&quot;code_1679482684770&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class GetBeachCongestionList @Inject constructor(
    private val repository: MainRepository
) {
    suspend fun invoke() : Flow&amp;lt;NetworkResult&amp;lt;BeachCongestionList&amp;gt;&amp;gt; {
        return repository.getBeachCongestion()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; Presantation Layer&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[MyViewModel.kt] 결과값을 받는 LiveData를 추가하여 값을 setting 하도록 수정&lt;/b&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;기존&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1679457916726&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@HiltViewModel
class MyViewModel @Inject constructor(
    private val getBeachCongestionList: GetBeachCongestionList
) : ViewModel(){
    private val _beachCongestionList = MutableLiveData&amp;lt;BeachCongestionList&amp;gt;()
    val beachCongestionList : LiveData&amp;lt;BeachCongestionList&amp;gt;
    get() = _beachCongestionList

    private val _toastMessage = MutableLiveData&amp;lt;String&amp;gt;()
    val toastMessage : LiveData&amp;lt;String&amp;gt;
    get() = _toastMessage

    fun getBeachInfo(){
        val exceptionHandler = CoroutineExceptionHandler{ _ , exception -&amp;gt;
            when(exception){
                is SocketTimeoutException -&amp;gt; {onError(&quot;통신시간 초과!&quot;)}
            }
        }
        viewModelScope.launch(exceptionHandler) {
            _beachCongestionList.value = getBeachCongestionList.invoke()
        }
    }

    private fun onError(message : String) {
        _toastMessage.value = message
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경 후&lt;/p&gt;
&lt;pre id=&quot;code_1679457965059&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@HiltViewModel
class MyViewModel @Inject constructor(
    private val getBeachCongestionList: GetBeachCongestionList
) : ViewModel(){
    private val _networkResult = MutableLiveData&amp;lt;NetworkResult&amp;lt;BeachCongestionList&amp;gt;&amp;gt;()
    val networkResult : LiveData&amp;lt;NetworkResult&amp;lt;BeachCongestionList&amp;gt;&amp;gt;
    get() = _networkResult

    private val _beachCongestionList = MutableLiveData&amp;lt;BeachCongestionList&amp;gt;()
    val beachCongestionList : LiveData&amp;lt;BeachCongestionList&amp;gt;
    get() = _beachCongestionList
    fun setBeachCongestionList(beachCongestionList: BeachCongestionList){
        _beachCongestionList.value = beachCongestionList
    }

    private val _toastMessage = MutableLiveData&amp;lt;String&amp;gt;()
    val toastMessage : LiveData&amp;lt;String&amp;gt;
    get() = _toastMessage

    fun getBeachInfo(){
        viewModelScope.launch {
            getBeachCongestionList.invoke().collect{ result -&amp;gt;
                _networkResult.value = result
            }
        }
    }

    fun onError(message : String) {
        _toastMessage.value = message
    }
}&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;b&gt;[MainActivity.kt] LiveData에 전달된 결과값에 따라 각각의 처리를 하도록 수정&lt;/b&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;기존&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1679458044521&quot; class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private const val TAG = &quot;MainActivity&quot;

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private lateinit var binding : ActivityMainBinding
    private val viewModel : MyViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater).also {
            setContentView(it.root)
        }
        observeData()
    }

    override fun onResume() {
        super.onResume()
        viewModel.getBeachInfo()
    }

    private fun observeData() {
        with(viewModel){
            beachCongestionList.observe(this@MainActivity){
                Log.d(TAG, &quot;observeData: $it&quot;)
            }
            toastMessage.observe(this@MainActivity){
                Toast.makeText(this@MainActivity , it , Toast.LENGTH_SHORT).show()
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;변경 후&lt;/p&gt;
&lt;pre id=&quot;code_1679458131216&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private const val TAG = &quot;MainActivity&quot;

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private lateinit var binding : ActivityMainBinding
    private val viewModel : MyViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater).also {
            setContentView(it.root)
        }
        observeData()
    }

    override fun onResume() {
        super.onResume()
        viewModel.getBeachInfo()
    }

    private fun observeData() {
        with(viewModel){
            networkResult.observe(this@MainActivity){ result -&amp;gt;
                when(result){
                    is NetworkResult.Success -&amp;gt; setBeachCongestionList(result.data) // 성공시
                    is NetworkResult.Failure -&amp;gt; onError(result.code.toString()) // 실패시
                    is NetworkResult.Exception -&amp;gt; onError(result.errorMessage) // 예외 발생시
                    is NetworkResult.Loading -&amp;gt; { // 통신중
                        // 현재 프로젝트에서는 할일이 없음
                    }
                }
            }

            beachCongestionList.observe(this@MainActivity){
                Log.d(TAG, &quot;observeData: $it&quot;)
            }
            toastMessage.observe(this@MainActivity){
                Toast.makeText(this@MainActivity , it , Toast.LENGTH_SHORT).show()
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 네트워크 통신 동작을 Flow로 이전해보는 방법에 대해 다뤄보았습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 LiveData를 Flow로 변경하여 사용하는 방법에 대해 알아보도록 하겠습니다  &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 오늘도 즐코하세요 :)&lt;/p&gt;</description>
      <category>Android/Kotlin</category>
      <category>Android</category>
      <category>Android Flow</category>
      <category>Coroutine flow</category>
      <category>flow</category>
      <category>Kotlin flow</category>
      <category>안드로이드</category>
      <category>안드로이드 flow</category>
      <category>코루틴 Flow</category>
      <category>코틀린</category>
      <category>플로우</category>
      <author>뎁요</author>
      <guid isPermaLink="true">https://devyo-111commit.tistory.com/35</guid>
      <comments>https://devyo-111commit.tistory.com/35#entry35comment</comments>
      <pubDate>Wed, 22 Mar 2023 13:45:49 +0900</pubDate>
    </item>
  </channel>
</rss>