<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Gun.log</title>
        <link>https://velog.io/</link>
        <description>Android Developer</description>
        <lastBuildDate>Fri, 03 Mar 2023 12:00:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. Gun.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/my_gun" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[DI] Hilt를 사용한 의존성 주입]]></title>
            <link>https://velog.io/@my_gun/DI-Hilt%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85</link>
            <guid>https://velog.io/@my_gun/DI-Hilt%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85</guid>
            <pubDate>Fri, 03 Mar 2023 12:00:22 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@my_gun/DI-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85">이전 글</a>에서 의존성 주입의 전반적인 개념에 대해 알아보았다.
구글에서 권장하는 의존성 주입 라이브러리 <strong>Hilt</strong>에 대해 알아보도록 하자.</p>
</br>

<h1 id="의존성-주입-되짚어-보기">[의존성 주입 되짚어 보기]</h1>
<ul>
<li><p><code>의존성 주입이란?</code>
의존성 주입이란 외부에서 인스턴스를 생성하여 주입하는 것이다.</p>
</li>
<li><p><code>의존성 주입이 없을 경우 문제점</code>
의존성 주입 없이 자체적으로 인스턴스를 생성하고 관리하게 되면 본연의 역할 이상을 수행
하므로 코드가 점점 거대해지며 변경에 민감하여 기존 작성된 코드에 영향을 미칠 수 있고 테스트를 어렵게 만든다.</p>
</li>
<li><p><code>수동 의존성 주입 시 문제점</code>
의존성 주입을 수동으로 작업하게 되면 보일러플레이트 코드가 많아지고, 객체 생성에 대한 순서를 고려해야 하며, 컨테이너를 사용하여 재사용하고 관리해야 하고 객체의 수명주기 또한 고려해야 하는 번거로움이 동반된다.</p>
</li>
<li><p><code>라이브러리를 이용한 자동 의존성 주입의 이점</code>
수동 의존성 주입과 서비스 로케이터를 통한 방법보다 셋업시간이 꽤 걸려 초기 비용이 많이 들지만, 앱이 커질수록 비용 곡선이 어느 정도의 선까지만 상승한다.
앱이 중간 정도의 크기로 갈수록 유지관리 비용이 다른 방법들과 큰 차이가 나지 않으며 일정 수준을 넘어설 경우 오히려 비용이 적게 든다.</p>
</li>
</ul>
</br>
</br>

<h1 id="hilt의-등장">[Hilt의 등장]</h1>
</br>
Hilt가 등장하기 전 구글에서는 Dagger 사용을 권장하였다.
하지만 Dagger는 러닝 커브가 높아 개발자들이 선뜻 적용하기엔 부담이 큰 문제가 있었다.

<p>구글은 지속적으로 안드로이드 커뮤니티를 통해 Dagger에 대한 개선사항을 수용하였고, </br> Dagger 라이브러리의 장점을 살리고 추가적인 기능과 추상화를 통해 Dagger의 프로세스를 단순화하여 사용법을 간편화시킨 Hilt라는 라이브러리를 발표하였으며 이에 대한 사용을 권장하였다. </br>
Hilt는 Android 앱을 위한 Jetpack의 권장 DI 솔루션으로, 좋은 아키텍처 설계에 도움이 되는 일부 Jetpack 라이브러리와 함께 사용할 수 있도록 지원한다.
Hilt는 Dagger 라이브러리 기반으로 빌드되었으며, 이는 Hilt 코드를 컴파일할 때 Dagger 코드로 변환됨을 의미한다.</p>
<blockquote>
<p>Dagger와 Hilt를 번역하면 단검과 손잡이다.
Hilt(손잡이)는 Dagger(단검)를 쉽게 사용하기 위해 만들어진 것이다.</p>
</blockquote>
</br>
</br>

<h1 id="hilt의-이점">[Hilt의 이점]</h1>
<p><strong><code>단순화된 사용</code></strong>
Dagger의 어려운 사용법을 개선한 것이 Hilt이다.
개발자는 서술형으로 의존관계를 설정하지 않고 코드를 단순화하는 Annotation을 통해 작성해야 하는 코드의 양을 줄여줌으로써 의존성 주입 과정을 간소화해준다.
Annotation을 명시하면 Annotation Processor에 의해 자동으로 컴파일 타임에 Dagger 라이브러리 코드를 생성하며 보일러플레이트 코드를 감소시킨다.</p>
<p><strong><code>낮은 러닝 커브</code></strong>
Dagger에 비해 상대적으로 러닝 커브가 낮아 쉽고 편리한 환경을 제공한다.</p>
<p><strong><code>테스트 지원</code></strong>
Hilt는 코드를 더 쉽게 테스트할 수 있도록 테스트 코드에서 유용한 어노테이션을 제공한다.
이를 통해 안드로이드 앱에 대한 테스트를 더 쉽게 작성할 수 있다.
이는 버그를 더 일찍 발견하고 앱이 더 안정적이고 신뢰할 수 있도록 보장할 수 있다는 것을 의미한다.</p>
<p><strong><code>Jetpack 라이브러리 호환</code></strong>
Hilt는 Jetpack 라이브러리 중 ViewModel, Navigation, Compose, WorkManager를 지원하며 잘 호환되어 의존성을 쉽게 주입하고 관리할 수 있다.</p>
<p><strong><code>컴파일 타임에 생성되는 코드</code></strong>
Hilt는 컴파일 시 의존성 관련 코드를 생성하기 때문에, 런타임에 리플렉션을 사용하는 일부 라이브러리보다 런타임 포퍼먼스가 향상된다.</p>
</br>
</br>

<h1 id="예제를-통한-hilt-사용-방법">[예제를 통한 Hilt 사용 방법]</h1>
<p>사용자 정보를 화면에 표시하는 간단한 예제를 통해 Hilt를 어떻게 사용하는지 알아보자.</p>
</br>

<h3 id="1-gradle-환경-설정">1. Gradle 환경 설정</h3>
<hr>
<p><strong>build.gradle (Project Level)</strong></p>
<pre><code class="language-java">plugins {
  ...
  id(&quot;com.google.dagger.hilt.android&quot;) version &quot;2.45&quot; apply false
}</code></pre>
</br>

<p><strong>build.gradle (App Level)</strong></p>
<pre><code class="language-java">
plugins {
  // // Hilt는 KAPT(Kotlin Annotation Processing Tool)의 도움을 받아 컴파일 시 코드를 생성한다.
  kotlin(&quot;kapt&quot;) 
  id(&quot;com.google.dagger.hilt.android&quot;)
}

android {
  ...
}

compileOptions {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
  }


dependencies {
  ...
  implementation(&quot;com.google.dagger:hilt-android:2.45&quot;)
  kapt(&quot;com.google.dagger:hilt-android-compiler:2.45&quot;)
}

kapt {
  // correctErrorTypes을 true로 설정할 경우 런타임에서 발생할 수 있는 에러에 대한 검사를 컴파일 시점에 체크한다.
  correctErrorTypes = true

}</code></pre>
<hr>
<blockquote>
<p>아래 내용에서 이해를 돕고자 사용한 코루틴이나 Room DB 같은 라이브러리 의존성은 추가하지 않았다.</p>
</blockquote>
</br>

<h3 id="2-application-클래스-설정">2. Application 클래스 설정</h3>
<p><a href="#hiltandroidapp">@HiltAndroidApp</a> 어노테이션을 통해 Hilt를 활성화 시켜보자.
Application을 상속받는 클래스에 해당 어노테이션을 명시하면 Hilt를 활성화 시키며 의존관계를 설정하기 위한 Hilt 코드가 생성된다.</p>
<hr>
<pre><code class="language-java">@HiltAndroidApp
class MyApplication: Application() {
    override fun onCreate() {
        super.onCreate()
        appContext = applicationContext    
    }

    // 이후 내용에서 Hilt에서 제공하는 Context 어노테이션으로 변경 예정
    companion object {
        lateinit  var appContext: Context
    }
}</code></pre>
<hr>
</br>

<h3 id="3-activity에-의존성-주입">3. Activity에 의존성 주입</h3>
<p>화면에 사용자 정보 리스트를 화면에 표시해보자.
먼저 MainActivity 클래스 상단에 <a href="#androidentrypoint">@AndroidEntryPoint</a> 어노테이션을 선언하여 해당 클래스에 의존성 주입이 필요하다는 것을 알려주도록 하자.
MainActivity 클래스의 UserRecyclerAdapter 변수 선언부에 <a href="#inject">@Inject</a> 어노테이션을 선언하여 필드주입이 필요함을 알리고, Hilt가 UserRecyclerAdapter를 인스턴스화 하는 방법을 알 수 있도록 UserRecyclerAdapter 클래스의 생성자 앞에 @Inject 어노테이션을 선언하여 생성자 주입을 설정하자.</p>
<hr>
<pre><code class="language-java">@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject lateinit var adapter: UserRecyclerAdapter // 필드주입

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

        val recyclerView = findViewById&lt;RecyclerView&gt;(R.id.recycler_view_user)
        recyclerView.adapter = adapter

        adapter.submitList(arrayListOf(User(&quot;홍길동&quot;)))
    }
}

class UserRecyclerAdapter @Inject constructor() : // 생성자 주입
    ListAdapter&lt;User, UserRecyclerAdapter.UserViewHolder&gt;(diffUtil) {  
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder { ... }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) { ... }

    override fun getItemCount() { ... }
}</code></pre>
<hr>
</br>

<h3 id="4-module을-통한-room-의존성-주입-with-provides">4. Module을 통한 Room 의존성 주입 (with @Provides)</h3>
<p>위에서 Hilt에게 UserRecyclerAdapter 를 알려주기 위해 생성자 주입을 하였다.
Room Database를 통해 데이터를 읽어 화면에 표시해보자.</p>
<p>하지만 Room DB는 라이브러리이므로 직접 코드 수정을 통한 생성자 주입을 할 수 없다.
<a href="#module">@Module</a>을 통해 Hilt에게 Room DB를 인스턴스화 하는 방법을 알려줄 수 있다.
Hilt는 모듈을 통해 의존성을 주입할 때 기본적으로 모듈 내 반환 타입과 일치하는 메서드를 사용하며, 해당 메서드에는 <a href="#provides">@Provides</a> 어노테이션을 명시해주어야 한다.</p>
<p>먼저 최상단에 @Module을 선언하고, 이와 함께 <a href="#installin">@InstallIn</a> 어노테이션을 필수적으로 선언해주어야 한다.
Database는 앱 전반적으로 사용되므로 싱글톤으로 사용하기 위해 SingletonComponent를 지정해 주었다.
마찬가지로 DB를 반환하는 메서드에도 @Singleton <a href="#scope">Scope</a>를 명시하였는데 이는 @InstallIn에 명시한 컴포넌트의 범위와 일치해야 한다.</p>
<hr>
<pre><code class="language-java">
@Entity
data class User @Inject constructor(
    @PrimaryKey(autoGenerate = true)
    val userId: Int = 0,
    @ColumnInfo(name = &quot;name&quot;) val name: String?
)

@Dao
interface UserDao {
    @Query(&quot;SELECT * FROM user&quot;)
    suspend fun getUserList(): MutableList&lt;User&gt;

    ...
}

@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    companion object {
        const val TABLE_NAME = &quot;user&quot;
        const val COL_USER_ID = &quot;userId&quot;
        const val COL_USER_NAME= &quot;name&quot;
    }

    abstract fun userDao(): UserDao
}

@Module
@InstallIn(SingletonComponent::class)
object DataModule {
    @Provides
    @Singleton
    fun provideDB(): AppDatabase {
        return Room.databaseBuilder(
            MyApplication.appContext,
            AppDatabase::class.java, &quot;User&quot;
        ).build()
    }
}


@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var adapter: UserRecyclerAdapter

    @Inject
    lateinit var appDatabase: AppDatabase

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
           ...
        queryUser()
    }

    private fun queryUser() {
        lifecycleScope.launch {
            val userList = appDatabase.userDao().getUserList()

            launch(Dispatchers.Main) {
                adapter.submitList(userList.toMutableList())
            }
        }
    }
}</code></pre>
<hr>
</br>

<h3 id="5-module을-통한-room-의존성-주입-with-binds">5. Module을 통한 Room 의존성 주입 (with @Binds)</h3>
<p>현재 Local DB를 통해 데이터를 조회하고 있지만, 추후 다른 Repository를 통해 데이터를 조회할 수 있도록 확장될 수 있기 때문에 Local DB를 Repository에서 관리되도록 수정해보자.</p>
<hr>
<pre><code class="language-java">class UserRepository @Inject constructor(private val dataSource: UserDataSource) {
    suspend fun getUserList(): MutableList&lt;User&gt; {
        return dataSource.getUserList()
    }
}

interface UserDataSource {
    suspend fun getUserList(): MutableList&lt;User&gt;
}

class UserLocalDataSource @Inject constructor(private val appDatabase: AppDatabase) : UserDataSource {
    override suspend fun getUserList(): MutableList&lt;User&gt; {
        return appDatabase.userDao().getUserList()
    }
}
</code></pre>
<hr>
<p>UserRepository는 생성자 주입을 통해 MainActivity에서 사용할 예정이다.
생성자 파라미터에 UserDataSource는 인터페이스로 정의되어 있어 생성자 주입이 불가능하다.
이에 따라 <a href="#module">Module</a>에서 Hilt가 UserDataSource의 구현 클래스를 인스턴스화 하기위한 절차를 명시해주어야 한다.
코드를 간결하게 하기 위해 <a href="#provides">@Provides</a>가 아닌 <a href="#binds">@Binds</a> 어노테이션을 사용해보자.</p>
<hr>
<pre><code class="language-java">@Module
@InstallIn(SingletonComponent::class)
abstract class DataModule {
    @Binds
    abstract fun bindUserLocalDataSource(userLocalDataSource: UserLocalDataSource): UserDataSource

    companion object {
        @Provides
        @Singleton
        fun provideDB(): AppDatabase {
            return Room.databaseBuilder(
                MyApplication.appContext,
                AppDatabase::class.java, &quot;User&quot;
            ).build()
        }
    }

}

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    ...

    @Inject
    lateinit var userRepository: UserRepository

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
           ...
        queryUser()
    }

    private fun queryUser() {
        lifecycleScope.launch {
            val userList = userRepository.getUserList()
            adapter.submitList(userList.toMutableList())
        }
    }
}
</code></pre>
<p>@Binds를 사용하기 위해선 Module의 선언을 interface나 abstract class로 선언해야 한다.
Module의 abstract 메서드 반환타입 또한 abstract class 혹은 interface로 명시되어야 한다.
또한 메서드는 필수적으로 한 개의 파라미터가 요구되는데, Hilt에 의해 파라미터의 클래스가 반환타입의 구현 클래스로 매핑되며 반환된다.
abstract class로 선언할 경우 @Provides 어노테이션을 사용할 수 없으므로, 필요시 companion object를 통해 사용하도록 하자.</p>
<hr>
<h3 id="6-hilt에서-제공하는-context-어노테이션-사용">6. Hilt에서 제공하는 Context 어노테이션 사용</h3>
<p>위에서 DataModule 클래스에서 Room 데이터베이스 생성을 위해 Application Context가 필요해 MyApplication 클래스에서 Context 세팅하여 제공하는 작업을 했었다.
Hilt에서는 Application, Activity 각각 상황에 맞는 <a href="#context-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98">Context 어노테이션</a>을 제공하며 이를 통해 간편하게 Context를 주입할 수 있으므로 이를 활용해보자.</p>
<hr>
<pre><code class="language-java">
@HiltAndroidApp
class MyApplication: Application() {
    override fun onCreate() {
        super.onCreate()
        // appContext = applicationContext    
    }

    /* companion object {
        lateinit  var appContext: Context
    } */
}


@Module
@InstallIn(SingletonComponent::class)
abstract class DataModule {
    ...
    companion object {
        @Provides
        @Singleton
        fun provideDB(@ApplicationContext context: Context): AppDatabase {
            return Room.databaseBuilder(
                context,
                AppDatabase::class.java, &quot;User&quot;
            ).build()
        }
    }
}</code></pre>
<hr>
</br>

<h3 id="7-동일한-반환-타입을-가지는-클래스에-대한-여러-유형의-인스턴스-제공">7. 동일한 반환 타입을 가지는 클래스에 대한 여러 유형의 인스턴스 제공</h3>
<p>위에서 Room 데이터베이스를 통해 데이터를 표시하였지만, DB를 조작하지 않고 Mock 객체를 생성하여 테스트용 데이터를 반환하는 코드를 작성한다고 가정해 보자.</p>
<p>먼저 UserDataSource 인터페이스를 확장하는 UserMockDataSource를 작성해보자.</p>
<hr>
<pre><code class="language-java">class UserMockDataSource @Inject constructor(): UserDataSource {
    override suspend fun getUserList(): MutableList&lt;User&gt; {
        return arrayListOf(User(1,&quot;홍길동(Mock)&quot;), User(2, &quot;김길동(Mock)&quot;))
    }
}</code></pre>
<hr>
<p>이에 따라 DataModule 클래스에도 위에서 작성한 UserLocalDataSource와 같이 UserMockDataSource 또한 인스턴스 생성 방법을 명시해야 한다.
하지만 <a href="#module">Module</a>은 기본적으로 동일한 반환타입을 가지는 메서드를 작성할 수 없기 때문에 <a href="#qualifier">Qualifier</a> 어노테이션을 통해 Hilt가 어떤 메서드를 사용해야 하는지 식별할 수 있게 만들어주어야 한다.</p>
<hr>
<pre><code class="language-java">@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
    @Qualifier
    @Retention(AnnotationRetention.BINARY)
    annotation class UserLocalDataSourceQualifier

    @Qualifier
    @Retention(AnnotationRetention.BINARY)
    annotation class UserMockDataSourceQualifier

    @Binds
    @UserLocalDataSourceQualifier
    abstract fun bindUserLocalDataSource(userLocalDataSource: UserLocalDataSource): UserDataSource

    @Binds
    @UserMockDataSourceQualifier
    abstract fun bindUserMockDataSource(userMockDataSource: UserMockDataSource): UserDataSource

    companion object {
        @Provides
        @Singleton
        fun provideDB(@ActivityContext context: Context): AppDatabase {
            return Room.databaseBuilder(
                context,
                AppDatabase::class.java, &quot;User&quot;
            ).build()
        }
    }

}
</code></pre>
<hr>
<br>

<p>이제 아래와 같이 코드의 큰 수정 없이 UserRepository에서 주입 받을 UserDataSource에 대한 Qualifier 어노테이션 변경만으로 필요에 따른 각 유형에 해당하는 UserDataSource를 사용할 수 있다.</p>
<hr>
<pre><code class="language-java">class UserRepository @Inject constructor(
    // @DataModule.UserLocalDataSourceQualifier
    @DataModule.UserMockDataSourceQualifier
    private val userDataSource: UserDataSource
) {
    suspend fun getUserList(): MutableList&lt;User&gt; {
        return userDataSource.getUserList()
    }
}
</code></pre>
<hr>
<br>

<h3 id="8-hilt가-지원하지-않는-클래스contentprovider에-의존성-주입">8. Hilt가 지원하지 않는 클래스(ContentProvider)에 의존성 주입</h3>
<p>Hilt 의존성 주입은 <a href="#hiltandroidapp">@HiltAndroidApp</a> 어노테이션을 Application 클래스에 명시를 하면, Application onCreate() 가 호출되어 앱이 활성화되는 시점에 Hilt의 Component 클래스들이 자동으로 생성되어 의존성 주입을 관리하는 Hilt 코드에 의해 이루어진다.</p>
<p>하지만 ContentProvider는 Application보다 먼저 실행될 수 있으며 위에서 다루었던 생성자, 필드를 통한 주입이 불가능하다.
이에 따라 Hilt는 ContentProvider를 직접적으로 지원하지 않는다.</p>
<p>Hilt는 이러한 상황에서도 의존성 주입을 사용할 수 있도록 <a href="#entrypoint">@EntryPoint</a>를 제공한다.</p>
<hr>
<p><code>[ContentProvider를 통한 데이터를 제공하는 앱]</code></p>
<pre><code class="language-java">class ExampleContentProvider : ContentProvider() {

    @EntryPoint
    @InstallIn(SingletonComponent::class)
    interface ExampleContentProviderEntryPoint {
        fun getAppDatabase(): AppDatabase
    }

    override fun query(uri: Uri,
                       projection: Array&lt;out String&gt;?,
                       selection: String?,
                       selectionArgs: Array&lt;out String&gt;?,
                       sortOrder: String?): Cursor? {
        ...

        return getAppDatabase().userDao().getUserList()
    }

    fun getAppDatabase(): AppDatabase {
        val appContext = context?.applicationContext ?: throw IllegalStateException()
        val hiltEntryPoint = EntryPointAccessors.fromApplication(
                appContext,
                ExampleContentProviderEntryPoint::class.java
            )

        return hiltEntryPoint.getAppDatabase()
    }
}</code></pre>
<p>외부 앱에서 query 요청이 왔을 시 DB를 통해 Cursor를 반환하는 코드에서, AppDatabase를 주입 받기 위해 EntryPoint를 작성해야 한다.
ExampleContentProviderEntryPoint와 같이 인터페이스로 선언 되어야 하며, @EntryPoint 어노테이션을 명시해야 한다.
작성한 EntryPoint는 EntryPointAccessors를 통해 접근이 가능하다.</p>
<p>query를 요청하는 앱으로 돌아가, 위 앱에서 제공하는 데이터를 읽어오는 코드를 작성해보자.</p>
<hr>
<p><code>[ContentProvider를 통한 데이터를 제공받는 앱]</code></p>
<pre><code class="language-java">class UserProviderDataSource @Inject constructor(
    private val contentResolver: ContentResolver
) : UserDataSource {

    override suspend fun getUserList(): MutableList&lt;User&gt; {
        val cursor = contentResolver.query(
            Constants.URI_EXAMPLE,
            arrayOf(AppDatabase.COL_USER_ID, AppDatabase.COL_USER_NAME),
            null,
            null,
            AppDatabase.COL_USER_ID)

        val userList = arrayListOf&lt;User&gt;()

        cursor?.let {
            while (cursor.moveToNext()) {
                val id = cursor.getInt(cursor.getColumnIndex(AppDatabase.COL_USER_ID))
                val name = cursor.getString(cursor.getColumnIndex(AppDatabase.COL_USER_NAME))
                userList.add(User(id, name))
            }

            cursor.close()
        }

        return userList
    }
}


@Module
@InstallIn(SingletonComponent::class)
abstract class DataModule {

    ...

    @Qualifier
    @Retention(AnnotationRetention.BINARY)
    annotation class UserProviderDataSourceQualifier

    ...

    @Binds
    @UserProviderDataSourceQualifier
    abstract fun bindUserProviderDataSource(userProviderDataSource: UserProviderDataSource): UserDataSource

    companion object {

        ...

        @Provides
        fun provideContentResolver(@ApplicationContext context: Context): ContentResolver {
            return context.contentResolver
        }
    }

}

class UserRepository @Inject constructor(
//    @DataModule.UserLocalDataSourceQualifier
//    @DataModule.UserMockDataSourceQualifier
    @DataModule.UserProviderDataSourceQualifier
    private val userDataSource: UserDataSource
) {
    suspend fun getUserList(): MutableList&lt;User&gt; {
        return userDataSource.getUserList()
    }
}
</code></pre>
<hr>
<p>이제 위와 같이 UserRepository에서 주입 받을 UserDataSource <a href="#qualifier">Qualifier</a> 어노테이션을 @UserProviderDataSourceQualifier로 변경하면 ContentProvider를 통한 데이터 조회가 이뤄진다.</p>
</br>

<h3 id="9viewmodel-의존성-주입">9.ViewModel 의존성 주입</h3>
<p>Hilt는 ViewModel을 포함한 Jetpack 라이브러리를 지원한다.
MainActivity에서 Repository를 통해 데이터를 요청하는 비즈니스 로직을 ViewModel로 옮겨보자.</p>
<hr>
<pre><code class="language-java">@HiltViewModel
class UserViewModel @Inject constructor(private val userRepository: UserRepository) : ViewModel() {
    val userLiveData = MutableLiveData&lt;List&lt;User&gt;&gt;()
    val insertResultLiveData = MutableLiveData&lt;Long&gt;()

    fun getUserList() = viewModelScope.launch {
        userLiveData.value = userRepository.getUserList()
    }
}</code></pre>
<hr>
<p>ViewModel 클래스 상단에 <a href="#hiltviewmodel">@HiltViewModel</a> 어노테이션을 명시하여 Hilt에게 ViewModel임을 알려주면, Hilt는 ViewModel임을 인식하고 ViewModel Factory를 자동으로 생성해 준다.
이와 함께 <a href="#inject">@Inject</a> 어노테이션을 통해 생성자 주입이 가능하도록 설정해 주어야 한다.
이렇게 작성된 ViewModel은 <a href="#androidentrypoint">@AndroidEntryPoint</a> 어노테이션이 명시된 클래스에서 간단하게 사용할 수 있다.</p>
<hr>
<pre><code class="language-java">
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    ...

    private val viewModel: UserViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
           ...

        viewModel.userLiveData.observe(this) {
            adapter.submitList(it.toMutableList())
        }

        viewModel.getUserList()
    }
}
</code></pre>
<hr>
<p>Activity 또는 Fragment에서 ViewModelProvider를 통하지 않고 간단하게 by viewModels() 와 같은 형태로 간단하게 위임 지정이 가능하다. </p>
<br>

<h1 id="annotation">[Annotation]</h1>
<p>Hilt는 Annotation Processor를 통해 코드를 컴파일할 때 자동으로 생성하여 개발자가 수작업해야 하는 보일러 플레이트 코드를 줄여준다.</p>
<p>개발자는 어노테이션을 이용해 Hilt에게 아래와 같은 정보를 알려줄 수 있다.</p>
<blockquote>
<ul>
<li>의존성 주입을 사용할 안드로이드 클래스</li>
</ul>
</blockquote>
<ul>
<li>의존성 주입이 필요한 변수</li>
<li>의존성 주입이 이뤄질 클래스를 인스턴스화 하는 방법</li>
<li>라이브러리에서 제공되는 클래스를 인스턴스화 하는 방법</li>
<li>추상 클래스나 인터페이스의 구현클래스를 인스턴스화 하는 방법</li>
<li>동일한 반환타입을 가지는 메서드에 대한 식별 방법</li>
<li>의존성 주입 인스턴스의 생성/소멸에 대한 생명주기 </li>
</ul>
<br>

<h3 id="hiltandroidapp">@HiltAndroidApp</h3>
<p><code>Hilt를 활성화 시키는 기본 설정 어노테이션</code>
최상위 계층인 Application을 상속받는 클래스에 <strong>@HiltAndroidApp</strong> 어노테이션을 선언 함으로써,
Hilt가 의존성을 관리하는 데 사용되는 Hilt 컴포넌트들을 생성한다.</p>
<br>

<h3 id="androidentrypoint">@AndroidEntryPoint</h3>
<p><code>의존성 주입이 필요한 안드로이드 클래스에 선언하는 어노테이션</code>
<span style="color: green;">Hilt가 지원하는 Android 클래스</span>를 상속받는 클래스에 <strong>@AndroidEntryPoint</strong> 어노테이션을 명시하면 Hilt의 관리 대상이 되며 명시한 클래스 내에서 의존성 주입을 사용할 수 있다.
@AndroidEntryPoint어노테이션을 명시하면 해당 클래스가 의존하는 Android 클래스, 예로 Fragment에 해당 어노테이션을 명시할 경우 Fragment를 사용하는 Activity에도 어노테이션을 명시해야 한다.</p>
<blockquote>
<ul>
<li><span style="color: green;">Hilt가 지원하는 Android 클래스</span><ul>
<li>Activity</li>
<li>Fragment</li>
<li>View</li>
<li>Service</li>
<li>BroadcastReceiver</li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>💡 Android 클래스에 대한 Hilt 지원 예외 사항</li>
</ul>
</blockquote>
<details>
<summary>
  Hilt는 AppCompatActivity와 같은 ComponentActivity를 확장하는 액티비티만 지원한다.</summary>
  <p style="background-color:#EAEAEA;">Hilt는 androidX 라이브러리의 일부인 안드로이드의 Jetpack 컴포넌트와 함께 작동한다.
ComponentActivity는 Hilt가 올바르게 작동하는 데 필요한 라이프사이클 및 상태 관리 기능을 제공하는 Jetpack 액티비티의 기본 클래스이며, Activity와 같이 ComponentActivity를 확장하지 않는 다른 활동에는 필요한 기능이 없으므로 지원되지 않는다.</p></br>
</details>
<details>
<summary>
  Hilt는 androidx.Fragment를 확장하는 프래그먼트만 지원한다.</summary>
  <p style="background-color:#EAEAEA;">android.app.Fragment는 Deprecated된 클래스이며 androidx.fragment.app과는 수명 주기와 동작이 달라 지원되지 않는다.</p></br>
</details>
</details>
<details>
<summary>
  Hilt는 retained 프래그먼트를 지원하지 않는다.</summary>
  <p style="background-color:#EAEAEA;">액티비티가 화면 회전과 같은 configurationChange로 인해 다시 생성될 때, retained 프래그먼트는 다시 생성되지 않으므로 해당 프래그먼트는 재생성되기 전 액티비티를 참조하고 있게 되며 이로인해 메모리 누수가 발생할 수 있기 때문에 지원되지 않는다.</p>
</details>

<br>

<h3 id="inject">@Inject</h3>
<p><code>필드 주입이나 생성자 주입 시 사용되는 어노테이션</code> </p>
<ul>
<li>필드 주입<ul>
<li>안드로이드 클래스를 상속받는 클래스 중 <a href="#androidentrypoint">@AndroidEntryPoint</a>가 명시된 클래스 내의 필드에 의존성 주입 필요시 사용한다.</li>
<li><strong>@Inject</strong> 어노테이션을 통해 지연 초기화를 선언하여 해당 필드에 의존성을 주입 받을 수 있다.<ul>
<li>Hilt가 필드 주입을 해주기 위해선 주입 받으려는 클래스에 생성자 주입을 명시하거나, <a href="#module">@Module</a>을 정의하여  인스턴스화 방법을 알려주어야 한다.</li>
</ul>
</li>
<li>안드로이드 클래스가 생성되는 시기, Activity를 예를 들면 super.onCreate() 시점에 주입이 이뤄지며 해당 시점 이후부터 사용할 수 있다..</br></li>
</ul>
</li>
<li>생성자 주입<ul>
<li>의존 객체를 인스턴스화 하는 방법을 Hilt에게 알려주는 역할을 한다.</li>
<li><strong>@Inject</strong> 어노테이션과 함께 생성자를 명시함으로써 Hilt가 해당 클래스를 인스턴스화 하는 방법을 알게 된다.</li>
<li>생성자 주입 시 생성자에 파라미터가 추가될 경우, 해당 파라미터에 해당하는 클래스 또한 생성자 주입을 명시하거나 Module에서 인스턴스화 하는 방법을 명시해야 한다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>💡 @Inject는 Dagger/Hilt가 아닌 자바에서 제공되는 어노테이션이다.</p>
</blockquote>
<br>

<h3 id="module">@Module</h3>
<p><code>생성자를 통한 주입이 불가능한 경우 @Module 어노테이션을 통해 Hilt에게 인스턴스를 생성하는 방법을 직접적으로 알려주는 역할을 한다.</code>
인터페이스는 생성자를 가질 수 없으므로 생성자를 통한 주입이 불가능하다.
또한 외부 라이브러리의 클래스와 같이 직접 코드를 소유하지 않은 경우에도 생성자를 통한 주입을 할 수 없다.
이 경우 <strong>Module</strong>을 통해 <a href="#binds">@Binds</a> 또는 <a href="#provides">@Provides</a> 어노테이션을 사용하여 Hilt가 주입해야 할 의존 클래스의 인스턴스를 만드는 방법을 지정할 수 있다.</p>
<blockquote>
<ul>
<li>Module 내에 @Provides와 @Binds로 인스턴스화 하는 방법을 알려줄 때
Hilt는 메서드의 이름이 아닌 주입하려는 인스턴스의 타입과 동일한 메서드의 반환 타입을 가지는 메서드 찾아 인스턴스를 제공한다.</li>
</ul>
</blockquote>
<ul>
<li>반환 타입이 같은 두 메서드가 존재할 시 에러가 발생하며, 이 경우 아래에서 설명할 <a href="#qualifier">Qualifier</a> 어노테이션을 추가로 명시하여 구분할 수 있도록 지정해야 한다.</li>
</ul>
<br>

<h3 id="provides">@Provides</h3>
<p><code>외부 라이브러리 또는 빌더 패턴으로 인스턴스를 생성해야 하는 경우 @Provides 어노테이션을 통해 Hilt에게 해당 클래스를 인스턴스화 하는 방법을 알려줄 수 있다.</code>
메서드 반환 타입은 의존성이 주입될 타입을 의미하고, 메서드 파라미터를 명시하여 의존객체 생성자에 전달하는 것과 같이 추가적인 작업이 가능하다.
메서드 본문에는 Hilt가 인스턴스를 생성하기 위한 방법을 작성한다.</p>
<blockquote>
<p><strong>@Provides</strong> 어노테이션으로 provideA 메서드를 작성하였을 때 provideA 메서드에 파라미터가 존재할 경우 해당 파라미터에 대한 생성 방법 또한 알려주어야 한다.
라이브러리나 안드로이드 내부 클래스가 아닌, 해당 파라미터 클래스의 소유권이 있으면 해당 클래스에서 <a href="#inject">@Inject</a>를 통한 생성자 주입을 이용하여 Hilt에게 알려주는 방법이 있고, provideA 메서드와 같이 <a href="#provides">@Provides</a>를 통해 별도의 메서드를 작성하여 해당 파라미터의 인스턴스 방법을 Hilt에게 알려주는 방법이 있다.</p>
</blockquote>
<br>

<h3 id="binds">@Binds</h3>
<p><code>interface 혹은 abstract class에 대한 주입이 필요한 경우 구현 클래스를 파라미터로 전달하여 반환 타입에 해당 구현 클래스로 매핑시켜주는 역할을 한다.</code>
<strong>@Binds</strong>를 사용하기 위해선 <a href="#module">Module</a>의 선언을 interface나 abstract class로 선언해야 한다. 
<a href="#provides">@Provides</a>를 통해 인스턴스화 할 수 있지만 @Binds를 사용할 경우 구현 부를 작성하지 않아도 Hilt에서 자체적인 Module에 대한 구현 클래스를 생성하여 코드가 간결해지는 효과를 얻을 수 있다.</p>
<blockquote>
<p><a href="#binds">@Binds</a> 어노테이션이 명시된 메서드의 경우 파라미터가 하나여야 하며, 해당 파라미터가 곧 반환되는 구현 타입이 된다. 또한 해당 구현 타입 클래스에는 <a href="#inject">@Inject</a>를 통한 생성자 주입이 요구된다.</p>
</blockquote>
<blockquote>
<p>@Provides에서 다룬 내용과 같이, @Binds 어노테이션이 명시된 메서드내 파라미터가 되는 클래스의 소유권이 있으면 해당 파라미터가 되는 클래스에 @Inject 어노테이션을 명시하여 생성 방법을 알려줄 수 있다.
하지만 소유권이 없는 클래스가 포함되면 해당 파라미터 클래스를 인스턴스화 하는 방법을 알려주기 위해 @Provides를 사용해야 한다.
하지만 같은 Module 영역 내에 @Provides와 @Binds를 모두 사용할 수 없다.
이때 다른 모듈에서 @Provides로 해당 파라미터를 정의하거나, companion object를 통해 해당 모듈 내에 @Provides를 명시하여 인스턴스 생성 방법을 알려 줄 수 있다.</p>
</blockquote>
<br>

<h3 id="installin">@InstallIn</h3>
<p><code>@EntryPoint 혹은 @Module에서 함께 쓰이는 어노테이션으로, 의존성을 제공하는 모듈이 어떤 컴포넌트에 설치될지 지정한다.</code></p>
<p>Hilt는 생성된 각각의 컴포넌트 클래스를 Android 클래스의 수명 주기에 따라 자동으로 생성하며 제거한다.</p>
<table>
<thead>
<tr>
<th>Hilt 컴포넌트</th>
<th>인젝터 대상</th>
<th>생성 시기</th>
<th>소멸 시기</th>
</tr>
</thead>
<tbody><tr>
<td>SingletonComponent</td>
<td>Application</td>
<td>Application#onCreate()</td>
<td>Application 소멸 시점</td>
</tr>
<tr>
<td>ActivityRetainedComponent</td>
<td>N/A</td>
<td>Activity#onCreate()</td>
<td>Activity#onDestroy()</td>
</tr>
<tr>
<td>ActivityComponent</td>
<td>Activity</td>
<td>Activity#onCreate()</td>
<td>Activity#onDestroy()</td>
</tr>
<tr>
<td>ViewModelComponent</td>
<td>ViewModel</td>
<td>ViewModel 생성 시점</td>
<td>ViewModel 소멸 시점</td>
</tr>
<tr>
<td>FragmentComponent</td>
<td>Fragment</td>
<td>Fragment#onAttach()</td>
<td>Fragment#onDestroy()</td>
</tr>
<tr>
<td>ViewComponent</td>
<td>View</td>
<td>View#super()</td>
<td>View 소멸 시점</td>
</tr>
<tr>
<td>ViewWithFragmentComponent</td>
<td>@WithFragmentBindings<br>어노테이션이 지정된 View</td>
<td>View#super()</td>
<td>View 소멸 시점</td>
</tr>
<tr>
<td>ServiceComponent</td>
<td>Service</td>
<td>Service#onCreate()</td>
<td>Service#onDestroy()</td>
</tr>
</tbody></table>
<blockquote>
<p>Hilt는 SingletonComponent에서 직접 broadcast receiver를 주입하므로 broadcast receiver의 컴포넌트를 생성하지 않는다.</p>
</blockquote>
<br>

<p>각 컴포넌트는 계층 구조의 형태를 가지며 이를 통해 구조적인 아키텍처를 표현하는 방식으로 의존성을 관리할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/my_gun/post/3a00f18f-1297-48b0-940b-edbdb6b28cce/image.png" alt=""></p>
<p>예를 들어 모듈에 @InstallIn(ActivityComponent::class)로 지정하였다면, 해당 모듈은 ActivityComponent에 설치되게 되며, 액티비티에서 해당 모듈을 사용할 수 있음을 의미한다.
또한 ActivityComponent는 각 액티비티별로 생명주기에 의해 생성되고 소멸된다.
해당 모듈에서 제공되는 주입은 컴포넌트 계층 구조에 의해 하위 컴포넌트들은 상위 컴포넌트 레벨의 모듈에서 제공하는 주입에 직계관계에 한하여 상위 호환되어 접근할 수 있다.</p>
<p>직계관계 상위호환은 컴포넌트 계층구조에서 ViewComponent는 ActivityComponent, ActivityRetainedComponent, SingletonComponent에 접근이 가능하다는 의미이다. 하지만 ViewComponent는 ServiceComponent에 접근이 불가능하다.</p>
<p>다르게 표현하면 View를 상속받은 클래스나 @InstallIn을 ViewComponent로 지정한 모듈에서는 ActivityComponent, ActivityRetainedComponent, SingletonComponent로 설정한 모듈에서 제공하는 의존성 주입은 가능하다.
반대로 Activity 혹은 Application을 상속받은 클래스나 @InstallIn을 ActivityComponent, ActivityRetainedComponent, SingletonComponent로 지정한 모듈에서는 하위 계층에서 제공하는 의존성 주입을 사용할 수 없다.</p>
<p><code>적용하다 보면 상위에서 하위로도 호환되는 것으로 보일 때가 있는데, @Injection 어노테이션을 통한 생성자 주입인 경우일 것이다.
이는 모듈에서 @Provides 혹은 @Binds를 통해 인스턴스화 된 것이 아니라, 생성자 주입에 의해 인스턴스화 된 것이다.</code></p>
<br>

<h3 id="qualifier">Qualifier</h3>
<p><code>하나의 클래스에 대해 여러 인스턴스로 구분하여 의존성을 제공해야 하는 경우 사용된다.</code>
<a href="#module">Module</a>에서의 <a href="#provides">@Provides</a>와 <a href="#binds">@Binds</a>를 통해 인스턴스 생성 방법을 알려주어도 반환 타입이 동일한 메서드가 존재할 경우 에러가 발생한다. 
Hilt는 Module 내  주입되어야 하는 인스턴스의 타입과 동일한 반환 타입을 가지는 메서드를 찾아 인스턴스를 주입한다.
이 경우 Qualifier를 이용해서 구분하여 사용하도록 할 수 있다.</p>
<br>

<h3 id="context-어노테이션">Context 어노테이션</h3>
<p><code>Hilt는 Context에 대한 어노테이션을 제공한다.</code>
Context 유형에 따라 @ApplicationContext, @ActivityContext 둘 중 하나를 사용하면 된다.
아래 소스를 예시로 특정 Activity에서 AnalyticsAdapter를 의존성 주입 받게 되면 해당 Activity의 Context가 생성자에 주입된다.</p>
<pre><code class="language-java">class AnalyticsAdapter @Inject constructor(
    @ActivityContext private val context: Context,
) {
        // Use ActivityContext
  }

class MainActivity : AppCompatActivity() {
    @Inject lateinit var adapter: AnalyticsAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Use AnalyticsAdapter
    }
}
</code></pre>
<br>

<h3 id="scope">Scope</h3>
<p><code>의존성 주입 시 범위를 지정하여 공유되어야 하는 인스턴스를 설정할 때 쓰이는 어노테이션이다.</code>
Hilt는 기본적으로 Scope가 지정되어있지 않으며, 주입 요청 시 새로운 인스턴스를 생성하여
제공한다.
Hilt Component에 해당하는 Scope을 통해 Component의 수명주기에 맞춰진 인스턴스를 공유할 수 있도록 설정할 수 있으며, Scope는 Component 레벨과 동일해야 한다.</p>
<hr>
<pre><code class="language-java">
@Module
@InstallIn(ViewComponent::class)
class Module {
    @Provides
    @ViewScoped
    fun privdeA(): A {
        return A()
    }
}

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        createCustomView1()    // 커스텀 뷰 생성
        createCustomView2()    // 커스텀 뷰 생성
    }
}

@AndroidEntryPoint
class View1: View {
    @Inject lateinit var a1: A
    @Inject lateinit var a2: A
}

@AndroidEntryPoint
class View2: View {
    @Inject lateinit var a1: A
    @Inject lateinit var a2: A
}
</code></pre>
<hr>
<p>위 예시에서 모듈을 ViewComponent에 설치하였기 때문에, View1, View2 각각의 클래스에 해당 컴포넌트가 생성되며 모듈에서 제공하는 A 인스턴스에 대한 의존성 주입을 받을 수 있다.
Component 및 Scope에 따른 A 인스턴스들의 주소값은 아래와 같다.</p>
<table>
<thead>
<tr>
<th>@InstallIn</th>
<th>@Scope</th>
<th>View1</th>
<th>View2</th>
</tr>
</thead>
<tbody><tr>
<td>ViewComponent</td>
<td>@ViewScoped</td>
<td>a1 : @4f9d10</td>
<td>a1 : @775f42f</td>
</tr>
<tr>
<td></td>
<td></td>
<td>a2 : @4f9d10</td>
<td>a2 : @775f42f</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>ActivityComponent</td>
<td>@ActivityScoped</td>
<td>a1 : @4f9d10</td>
<td>a1 : @4f9d10</td>
</tr>
<tr>
<td></td>
<td></td>
<td>a1 : @4f9d10</td>
<td>a1 : @4f9d10</td>
</tr>
</tbody></table>
<br>

<h3 id="entrypoint">@EntryPoint</h3>
<p><code>Hilt가 지원하지 않는 클래스에 필드 삽입 시 사용되는 어노테이션이다.</code>
위에서 설명한 바와 같이 Hilt는 <a href="#androidentrypoint">@AndroidEntryPoint</a> 어노테이션을 통해 Application, Activity, Fragment, Service, View 등 자주 사용되는 일반적인 안드로이드 클래스를 지원한다.
하지만 지원하지 않는 안드로이드 클래스도 존재하며 그러한 예로 ContentProvider를 볼 수 있다.
ContentProvider에 <a href="#androidentrypoint">@AndroidEntryPoint</a> 및 필드 주입, 생성자 주입 시 정상적으로 의존성 주입이 되지 않는다.
Hilt에는 ContentProviderComponent가 없기 때문이다.
ContentProvider에서 Hilt를 통해 의존성 주입을 받기 위해선 주입 받고자 하는 타입별로 인터페이스를 정의하고 @EntryPoint 어노테이션을 명시해야 한다.
EntryPoint에 접근하기 위해선 EntryPointAccessors를 통해 가능하다. 파라미터로 전달되는 Context는 InstallIn에 설정한 Component 레벨을 따라야 한다.</p>
<blockquote>
<p>ContentProvider는 Application보다 먼저 실행될 수 있어 Hilt Component에서 지원되지 않는다.</p>
</blockquote>
<br>

<h3 id="hiltviewmodel">@HiltViewModel</h3>
<p><code>ViewModel 주입 시 사용하는 어노테이션이다.</code>
안드로이드 AAC Viewmodel을 상속받는 클래스에 @HiltViewModel을 명시하여 사용한다.
<a href="#androidentrypoint">@AndroidEntryPoint</a>를 명시한 Activity나 Fragment에서 ViewModelProvider 또는 by viewModels()을 통해 ViewModel을 사용할 수 있다.
@HiltViewModel을 명시하게 되면 Hilt는 컴파일 시 ViewModelComponent 가 생성된다.</p>
<blockquote>
<p>액티비티, 프래그먼트에서 각각 by viewModels() 키워드를 통해 위임을 지정하기 위해선  gradle에 아래와 같은 의존성을 추가해야 한다.</p>
</blockquote>
<ul>
<li>implementation &#39;androidx.fragment:fragment-ktx:x.x.x&#39;</li>
<li>implementation &#39;androidx.activity:activity-ktx:x.x.x&#39;</li>
</ul>
<br>

<hr>
<br>

<p>의존성 주입 라이브러리를 통해 더욱 쉽게 적은 코드로 의존성을 관리할 수 있지만, 복잡한 동작이 래핑 되었다는 것을 잊지 말자.
상황에 적절하지 않은 명령을 내리면 그에 따른 비효율적인 코드가 생성되어 의도하지 않은 결과나 문제가 발생할 수 있다.
특히 컴포넌트와 범위 지정을 잘 이해하여 주입되는 인스턴스가 올바른 생명주기에 맞춰 동작하도록 주의를 기울여 메모리 누수를 방지하도록 하자.</p>
<p>단순한 명령으로 복잡한 코드가 작성되는 만큼 라이브러리의 기본 동작을 잘 이해하고 효과적이고 적절하게 사용하는 것이 중요하다.</p>
<br>
<br>

<blockquote>
<p><strong>참고자료</strong></p>
</blockquote>
<ul>
<li><a href="https://developer.android.com/training/dependency-injection/hilt-android">https://developer.android.com/training/dependency-injection/hilt-android</a></li>
<li><a href="https://dagger.dev/hilt/">https://dagger.dev/hilt/</a></li>
<li><a href="https://www.youtube.com/watch?v=1Zt6aIqZnqU">https://www.youtube.com/watch?v=1Zt6aIqZnqU</a></li>
<li><a href="https://www.youtube.com/watch?v=B56oV3IHMxg">https://www.youtube.com/watch?v=B56oV3IHMxg</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DI] 의존성 주입의 이해와 필요성]]></title>
            <link>https://velog.io/@my_gun/DI-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85</link>
            <guid>https://velog.io/@my_gun/DI-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85</guid>
            <pubDate>Sun, 05 Feb 2023 09:46:10 GMT</pubDate>
            <description><![CDATA[<p><strong>의존성 주입(DI) = Dependency Injection</strong></p>
</br>

<p>라이브러리를 사용하지 않고 의존성을 수동으로 주입하는 것을 &quot;<strong>수동 의존성 주입</strong>&quot;으로 명시하였으며,
의존성 주입 라이브러리를 통해 의존성을 자동으로 주입하는 것을 &quot;<strong>자동 의존성 주입</strong>&quot;으로 명시하였다.</p>
<h1 id="의존관계">[의존관계]</h1>
<p><img src="https://velog.velcdn.com/images/my_gun/post/4165b705-96d6-4d77-9352-e215bf3ea83c/image.png" alt=""></p>
<hr>
<pre><code class="language-java">class Car {
    private val engine = Engine()

    fun wearSeatBelt() {
        println(&quot;안전벨트 착용&quot;)
    }

    fun start() {
        println(&quot;시동걸기&quot;)
        engine.start()
    }
}

fun main() {
    val car = Car()
    car.wearSeatBelt()
    car.start()
}</code></pre>
<hr>
<p>위 코드는 의존성 주입 없이 Car 내부에서 직접 Engine을 생성한다.<br>Car는 Egine을 사용하며, Car는 Egine에 <strong>의존한다</strong>. 라고 표현한다.</p>
</br>

<h1 id="의존성-주입이란">[의존성 주입이란?]</h1>
<p>프로그램은 객체 간의 의존관계에 의해 동작하게 된다.
개발하면서 의존성은 필수적으로 작용하며 의존성 그 자체가 나쁜 것이 아니다.
다만 의존성이 높아지면 가독성, 재사용성, 확장성 등 유지보수함에 있어 어려움을 겪게 된다.
우리의 목표는 코드를 관리하기 쉽도록 객체 간의 의존성을 낮추고 최소화하는 것이다.
이를 돕기 위해 의존성 주입이라는 개념이 등장한다.</p>
<p>Car 클래스가 실행되기 위해서는 Engine 클래스의 인스턴스가 있어야 한다.
Car 내부에서 직접 Engine을 생성하지 않고 외부에서 Engine을 생성하여 Car로 주입하는 것.</p>
<p>의존성 주입이란 이와같이 외부에서 인스턴스를 생성하여 주입하는 것이다.
</br></p>
<h1 id="의존성-주입의-필요성">[의존성 주입의 필요성]</h1>
<p>프로젝트 규모가 커지게 되면, 위의 코드에서 세 가지 문제가 발생한다.</p>
<p><strong>첫 번째. 본연의 역할 이상을 수행한다.</strong>
사실 Car는 부품을 사용하여 동작하는 역할만 하면 된다.
하지만 위 코드에서는 불필요하게 부품을 생성하고 관리하는 역할까지 맡고 있다.
자동차에는 엔진뿐만 아니라 여러 부품이 존재하며 부품들이 추가되면서 Car 클래스 내의 코드는 점점 거대해진다.
SRP(Single Responsibility Principle)를 위반했기 때문이다.</p>
<p><strong>두 번째. 변경에 민감하다</strong>
휘발유를 연료로 하는 가솔린 엔진 기준으로 엔진을 사용하였지만, 가스와 전기를 연료로 하는 엔진이 추가되었다고 하자.
GasEngine, ElectricEngin만 추가하면 될 거로 생각하였지만, Car는 Engine에 대한 종속성이 높아 Car를 재사용할 수 없고 각 유형에 맞게 추가되고 변경되어야 한다.(GasCar, ElectricCar)
OCP(Open Closed Principle)를 위반한 대가이다.</p>
<p><strong>세 번째. 테스트가 어려워진다.</strong>
안전벨트 착용에 대한 테스트 코드를 작성해야 하는 상황에서 안전벨트는 엔진과 무관하다.
하지만 위 코드 구조에 의해 안전벨트 테스트를 위해 엔진을 신경 써야 하는 투머치한 상황이 발생한다. (실제 상황에서는 엔진을 생성하기 위해선 많은 과정이 필요할 것이다)
<span style="color: green;">테스트 더블</span>을 사용하여 Dummy 테스트가 불가한 것이다. </p>
<blockquote>
<p><span style="color: green;"><strong>테스트 더블이란?</strong></span>
  영화나 드라마에서 실제 배우가 연출하기 힘든 위험한 역할을 하는 &#39;Stunt double&#39;에서 유래된 말로, 실제 객체의 의도와 비슷해 보이게 동작하지만, 복잡성을 줄여 특정 상황에 대해 필요로 하는 부분에만 초점을 맞춘 단순화된 버전이며 다섯 가지 유형으로 나뉜다.</p>
</blockquote>
<ul>
<li>Dummy object</li>
<li>Test stub</li>
<li>Test spy </li>
<li>Mock object </li>
<li>Fake object </li>
</ul>
</br>

<h1 id="수동-의존성-주입-방법">[수동 의존성 주입 방법]</h1>
<p>아래 주요 두 가지 방법을 통해 간단하게 의존성 주입이 가능하다.</p>
<p><img src="https://velog.velcdn.com/images/my_gun/post/d3cfc189-8b31-4267-8ab5-a826107c8867/image.png" alt=""></p>
<hr>
<blockquote>
<p><strong>1. 생성자를 통한 의존성 주입</strong>
Car 객체 생성 시 생성자를 통해 Engine 객체를 전달받는다.</p>
</blockquote>
<pre><code class="language-java">class Car(private val engine: Engine) {    
    fun wearSeatBelt() {
        println(&quot;안전벨트 착용&quot;)
    }

    fun start() {
        println(&quot;시동걸기&quot;)
        engine.start()
    }
}

fun main(args: Array) {
    val engine = Engine()
    val car = Car(engine)
    car.wearSeatBelt()
    car.start()
}</code></pre>
<blockquote>
<p><strong>2. 필드 혹은 setter를 통한 의존성 주입</strong>
Car 객체 생성 후 필드나 Setter로 바로 접근하여 Engine을 주입한다.</p>
</blockquote>
<pre><code class="language-java">class Car {
    lateinit var engine: Engine

    fun wearSeatBelt() {
        println(&quot;안전벨트 착용&quot;)
    }

    fun start() {
        println(&quot;시동걸기&quot;)
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.engine = Engine()
    car.wearSeatBelt()
    car.start()
}</code></pre>
<hr>
<p>이제 가스와 전기를 연료로 하는 엔진이 추가되어도 GasEngine, ElectricEngin 인스턴스를 생성하여 Car에 전달하여 Car를 재사용 할 수 있게 되었다.
또한 DummyEngine을 생성하여 안전벨트 테스트의 사전작업 환경을 손쉽게 만들 수 있다.</p>
<p>하지만 여전히 문제가 남아있다.
</br></p>
<h1 id="수동-의존성-주입의-문제점">[수동 의존성 주입의 문제점]</h1>
<p>무기를 사용하는 군인을 예시로 살펴보자.</p>
<hr>
<pre><code class="language-java">fun main() {
    val bullet = Bullet()
    val gun = Gun(bullet)
    val knife = Knife()

    val weapon = Weapon(gun, knife)

    val soldier = Soldier(weapon)
}</code></pre>
<hr>
<p><strong>첫 번째. 보일러플레이트 코드가 많아진다.</strong></p>
<ul>
<li>Soldier 객체를 생성하기 위해 무기 세팅하여 Soldier 객체를 생성하고 있다.
다른 곳에서 Soldier 객체를 생성하기 위해서 위와 같은 코드들을 중복으로 생성해야 한다.</li>
</ul>
<p><strong>두 번째. 객체 생성 순서에 영향을 받는다.</strong></p>
<ul>
<li>Weapon은 Soldier 객체를 생성하기 전에 생성해야 한다.</li>
<li>Gun, Knife는 Weapon 객체를 생성하기 전에 생성해야 한다.</li>
<li>Bullet은 Gun 객체를 생성하기 전에 생성해야 한다.</li>
</ul>
<p><strong>세 번째. 객체를 재사용하기 어렵다.</strong></p>
<ul>
<li>여러 곳에서 Soldier 객체를 재사용하려면 싱글톤 패턴을 따르게 해야 한다. 
그렇게 되면 모든 테스트가 동일한 싱글톤 인스턴스를 공유하므로 테스트가 더 어려워진다.</li>
</ul>
</br>
수동 의존성 주입의 대안으로 <span style="color: green;">서비스 로케이터패턴</span>이 존재하지만, 테스트를 더 어렵게 만들고, 사용하는 서비스의 모든 사용자가 서비스 로케이터에 종속되어 의존관계를 파악하기 힘들게 된다.

<blockquote>
<p><span style="color: green;"><strong>서비스 로케이터란?</strong></span>
<a href="https://www.martinfowler.com/articles/injection.html">서비스 로케이터란 마틴 파울러가 제시한 디자인 패턴</a>으로, 복잡성 및 객체 생성을 추상화하고 클라이언트에게 간단한 인터페이스를 제공하여 인스턴스를 반환한다. 
이에 따라 클라이언트의 복잡성이 줄어들고 재사용할 수 있다는 장점이 있다.
<a href="https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/">Mark Seemann 블로그에서는 Anti-Pattern이라는 비판</a>을 받는 패턴이기도 하다.</p>
</blockquote>
</br>

<h1 id="실제-앱-시나리오에-수동-의존성-주입">[실제 앱 시나리오에 수동 의존성 주입]</h1>
</br>

<p><img src="https://velog.velcdn.com/images/my_gun/post/a35ffdc8-ae68-40f3-84ad-c2e2d0e06faf/image.png" alt=""></p>
</br>

<p>실제 안드로이드 프로젝트는 일반적으로 구글에서 권장하는 위 아키텍처 그래프 모델과 비슷한 형태로 설계하게 된다. 
실제와 비슷한 환경을 가정하여 수동 의존성 주입을 적용해보자.</p>
</br>

<p><img src="https://velog.velcdn.com/images/my_gun/post/4fa30f55-f7df-47d0-9dd5-76536695bcd0/image.png" alt=""></p>
</br>

<p>위와 같은 로그인 비즈니스 플로우를 예시로 알아보자.</p>
<hr>
<blockquote>
<p><strong>1. Repository 및 DataSource</strong>
UserLocalDataSource와 UserRemoteDataSource에 의존하는 UserRepository 구현</p>
</blockquote>
<pre><code class="language-java">
class UserRepository(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) {
    ...
  }

class UserLocalDataSource { 
    ...
}

class UserRemoteDataSource(
    private val loginService: LoginRetrofitService
) {
    ...
  }
</code></pre>
</br>

<blockquote>
<p><strong>2. LoginViewModel 및 LoginData</strong>
로그인 시 사용되는 로그인 관련 Data와 로그인에 대한 비즈니스 로직을 담당하는 ViewModel 구현</p>
</blockquote>
<pre><code class="language-java">
class LoginData(private val userId: String,) {
    ...
}

class LoginViewModel(private val loginRepository: LoginRepository) {
    ...
}
</code></pre>
</br>

<blockquote>
<p><strong>3. Factory</strong>
LoginViewModel이 많은 위치에 필요할 수 있으며, 다른 유형의 ViewModel이 생성될 것을 고려하여 추상 팩토리 디자인 패턴을 적용하여 ViewModel을 생성하는 Factory 구현</p>
</blockquote>
<pre><code class="language-java">
interface Factory&lt;T&gt; {
    fun create(): T
}

class LoginViewModelFactory(private val userRepository: UserRepository) : Factory {
    override fun create(): LoginViewModel {
        return LoginViewModel(userRepository)
    }
}
</code></pre>
</br>

<blockquote>
<p><strong>4. LoginContainer</strong></p>
</blockquote>
<ul>
<li>여러 곳에 파편화된 로그인 관련된 인스턴스 생성을 이 Container에 담아두고 관리</li>
<li>작업 유형별 Container의 수명관리를 위해 로그인 관련된 Container 구현</li>
</ul>
<pre><code class="language-java">
class LoginContainer(val userRepository: UserRepository) {

    val loginData = LoginUserData()

    val loginViewModelFactory = LoginViewModelFactory(userRepository)
}
</code></pre>
</br>

<blockquote>
<p><strong>5. AppContainer</strong>
여러 곳에 파편화된 앱 전반적인 인스턴스 생성을 이 Container에 담아두고 관리</p>
</blockquote>
<pre><code class="language-java">
class AppContainer {
    private val retrofit = Retrofit.Builder()
                            .baseUrl(&quot;https://example.com&quot;)
                            .build()
                            .create(LoginService::class.java)

    private val remoteDataSource = UserRemoteDataSource(retrofit)
    private val localDataSource = UserLocalDataSource()

    val userRepository = UserRepository(localDataSource, remoteDataSource)

    val loginViewModelFactory = LoginViewModelFactory(userRepository)

    var loginContainer: LoginContainer? = null

}
</code></pre>
</br>


<blockquote>
<p><strong>6. Application</strong>
AppContainer가 모든 Activity, Fragment 등 앱 전반에 걸쳐 사용할 수 있도록 Application 클래스에서 AppContainer 인스턴스를 생성한다.</p>
</blockquote>
<pre><code class="language-java">
class MyApplication : Application() {
    val appContainer = AppContainer()
}</code></pre>
</br>

<blockquote>
<p><strong>7. LoginActivity</strong></p>
</blockquote>
<ul>
<li>Application 인스턴스를 통해 AppContainer를 가져와 LoginViewModel 인스턴스 생성한다.</li>
<li>로그인 흐름이 시작될 때 LoginContainer를 생성하고, 흐름이 종료될 때 소멸시킨다.</li>
</ul>
<pre><code class="language-java">
class LoginActivity: Activity() {

    private lateinit var loginViewModel: LoginViewModel
    private lateinit var loginData: LoginUserData
    private lateinit var appContainer: AppContainer

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        appContainer = (application as MyApplication).appContainer

        appContainer.loginContainer = LoginContainer(appContainer.userRepository)

        loginViewModel = appContainer.loginContainer.loginViewModelFactory.create()
        loginData = appContainer.loginContainer.loginData
    }

    override fun onDestroy() {
        appContainer.loginContainer = null
        super.onDestroy()
    }
}
</code></pre>
</br>

<hr>
<p>수동 의존성 주입에 대해 알아보았다.
위에서 언급한 문제점들과 인스턴스에 대한 수명주기를 수동으로 관리해야 하는 점으로 인해 다소 복잡해
지는 경향이 있다.
의존성 주입 라이브러리를 사용함으로써 문제점을 해결할 수 있다.</p>
</br>
</br>

<h1 id="자동-의존성-주입">[자동 의존성 주입]</h1>
<p>라이브러리를 통한 자동 의존성 주입은 적은 양의 코드로 클래스 간의 관계를 선언하고 의존관계와 수명주기를 설정하는 방법을 제공하여 의존성을 쉽게 관리할 수 있도록 도와준다.</p>
<p>아래 앱 크기에 따라 증가하는 비용 그래프를 통해 의존성 주입의 유형별 특성을 알아보자.</p>
<h2 id="의존성-주입-유형-별-비교">의존성 주입 유형 별 비교</h2>
</br>

<p><img src="https://velog.velcdn.com/images/my_gun/post/1c713baf-bcca-41da-a3d5-86f07fef7709/image.png" alt=""></p>
<p><strong>수동 의존성 주입</strong></p>
<ul>
<li>수동 의존성 주입은 러닝 커브가 낮은 장점이 있다.</li>
<li>초기 비용이 들어가지 않지만, 앱 크기가 커질수록 수많은 보일러 플레이트 코드와 직접 관리 해야 할 작업이 늘어나 비용이 기하급수적으로 증가한다.</li>
<li>앱 규모가 작다면 서비스 로케이터나 자동 의존성 주입에 들이는 비용이 적기 때문에 오히려 적합한 방법이 될 수 있다.</li>
</ul>
<p><strong>서비스 로케이터</strong></p>
<ul>
<li>서비스 로케이터는 자동 의존성 주입에 비해 초기 비용이 적지만 앱이 커질수록 비용 곡선이 가파르게 상승하고 결국 수동 의존성 주입과 같은 문제에 직면하게 된다.</li>
</ul>
<p><strong>자동 의존성 주입 (Library)</strong></p>
<ul>
<li>자동 의존성 주입은 셋업시간이 꽤 걸려 초기 비용이 많이 든다.</li>
<li>하지만 앱이 커질수록 비용 곡선이 어느 정도의 선까지만 상승한다.</li>
<li>앱이 중간 정도의 크기로 갈수록 유지관리 비용이 다른 방법들과 큰 차이가 나지 않으며 일정 수준을 넘어설 경우 오히려 비용이 적게 드는 모습을 볼 수 있다.</li>
</ul>
</br>
</br>

<h2 id="의존성-주입-라이브러리-비교">의존성 주입 라이브러리 비교</h2>
<p>안드로이드에서 의존성 주입의 인기가 증가함에 따라 의존성 주입 프로세스를 단순화하는 것을 목표로 하는 여러 라이브러리가 등장했다.</br></p>
<p>의존성 주입 라이브러리들 중 Koin, Hilt, Dagger에 대해 알아보자.</p>
<p><strong>Dagger (2012)</strong></p>
<ul>
<li>컴파일 타임에 의존성을 관리해줄 코드를 검사하고 생성하여 런타임 예외가 발생하지 않아 정확성을 제공한다.</li>
<li>컴파일 타임에 오버헤드가 발생하나 런타임 시 빠르고 안정적으로 작동한다.</li>
<li>러닝 커브가 높다.</li>
</ul>
<blockquote>
<p><strong>Dagger2의 등장 (2016)</strong>
Dagger1은 부분적으로 리플렉션을 사용해 런타임 시 오버헤드가 발생하고 디버깅이 어려운 단점이 있었으며, 이러한 문제점을 보완하기 위해 4년 뒤 Dagger2가 공개되었다.
Dagger2는 코드에 대한 추적을 쉽게 만들고 리플렉션을 사용하지 않고 어노테이션 프로세스를 적용하여 오버헤드를 런타임이 아닌 컴파일 타임으로 옮겨 런타임의 포퍼먼스를 향상 시켰다.</p>
</blockquote>
<p><strong>Koin(2017)</strong></p>
<ul>
<li>Kotlin DSL(Domain Specific Language)을 지원하여 직관적인 사용이 가능하다.</li>
<li>서비스 로케이터 패턴 기반으로 만들어져 요청이 들어왔을 때 인스턴스를 동적으로 반환하여 런타임에 의존성을 해결하며 컴파일 타임에 코드가 생성되지 않는다.</li>
<li>런타임에서 의존성을 해결하기 때문에 런타임 시 에러가 발생할 수 있으며 런타임 성능이 떨어진다.</li>
<li>다른 의존성 주입 라이브러리들에 비해 가볍고 가장 러닝 커브가 낮다.</li>
</ul>
<p><strong>Hilt(2020)</strong></p>
<ul>
<li>2019년 Google I/O에서 2020년부터 Android용 Dagger를 더 잘 만들 계획이라는 입장을 내놓았고, 2020년 Hilt를 공식적으로 발표하였다.</li>
<li>Dagger2를 기반으로 만들어졌기 때문에 Dagger의 장점을 모두 가지고 있다.</li>
<li>Dagger에서의 중복코드 발생과 복잡성을 개선하였으며, 최대 단점인 초기 셋업 비용을 감소시켰다.</li>
<li>Koin에 비해 러닝 커브가 높으나 Dagger에 비해 러닝 커브가 낮다. <blockquote>
<p>Dagger와 Hilt를 번역하면 단검과 손잡이다.
Hilt(손잡이)는 Dagger(단검)를 쉽게 사용하기 위해 만들어진 것이다.</p>
</blockquote>
</li>
</ul>
</br>

<hr>
</br>

<p>의존성 주입을 잘 활용하면 느슨한 결합을 촉진하여 객체간 결합도를 줄이는데 도움이 되며 모듈성, 확장성, 가독성, 유지보수성을 향상시키는데 도움을 주는 등 여러 이점을 얻을 수 있다.</p>
<p>하지만 무조건적인 사용은 오버엔지니어링이 될 수 있으므로, 프로젝트 규모와 상황의 적합성을 잘 따져 배보다 배꼽이 더 큰 상황을 피하도록 하자.</p>
</br>
</br>

<blockquote>
<p><strong>참고자료</strong></p>
</blockquote>
<ul>
<li><a href="https://developer.android.com/training/dependency-injection">https://developer.android.com/training/dependency-injection</a></li>
<li><a href="https://developer.android.com/training/dependency-injection/manual">https://developer.android.com/training/dependency-injection/manual</a></li>
<li><a href="https://www.youtube.com/watch?v=o-ins1nvbDg">https://www.youtube.com/watch?v=o-ins1nvbDg</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Build - Error] Installed Build Tools revision 31.0.0 is corrupted. Remove and install again using the SDK Manager ]]></title>
            <link>https://velog.io/@my_gun/Build-Error-Installed-Build-Tools-revision-31.0.0-is-corrupted.-Remove-and-install-again-using-the-SDK-Manager</link>
            <guid>https://velog.io/@my_gun/Build-Error-Installed-Build-Tools-revision-31.0.0-is-corrupted.-Remove-and-install-again-using-the-SDK-Manager</guid>
            <pubDate>Mon, 16 Jan 2023 10:44:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Installed Build Tools revision 31.0.0 is corrupted. Remove and install again using the SDK Manager.</br><img src="https://velog.velcdn.com/images/my_gun/post/c7a65508-9c7c-4bc0-9dfe-e025eb32e526/image.png"></p>
</blockquote>
<p>안드로이드스튜디오 1.4.3 버전 사용 중이었고 새로운 프로젝트를 생성할 때 해당 툴 버전에 의해 자동으로 TargetSDK가 31로 설정되었으며 <code>빌드 시 위와 같은 에러가 발생하였다.</code></p>
<p>간단한 소스를 테스트하는 목적으로 사용하던 PC라 평소에는 TargetSDK를 다운그레이드시켜 작업하였으나, 원천적인 문제를 해결하고자 다른 방식으로 접근하였다.</p>
</br>

<hr>
</br>

<h2 id="build-tool-3100-재설치">Build Tool (31.0.0) 재설치</h2>
<p>에러 메시지에서 가이드한 바와 같이 API Level 31에 해당하는 <code>Build Tool Version을 제거 후 다시 설치해 보았으나 현상은 마찬가지였다.</code>
</br></p>
<hr>
</br>

<h2 id="androidstudio-업데이트">AndroidStudio 업데이트</h2>
</br>
공식 사이트에 Android 12 사용 시 스튜디오 이전 버전과 호환되지 않는 사항이 존재한다고 명시되어있다.
<img src="https://velog.velcdn.com/images/my_gun/post/b6db4abe-88bc-41ac-ab0a-f85f99b614fa/image.png"/>

<p>이에 따라 <code>안드로이드 스튜디오 버전을 &#39;4.1.3&#39; 버전에서 최신 버전인 &#39;Dolphin&#39;으로 업데이트해 보았지만 동일한 문제가 발생하였다.</code></p>
<blockquote>
<p>안드로이드 스튜디오 버전명이  <a href="https://velog.io/">번호 지정 체계 업데이트</a> 로 인해 영문으로 바뀌었다.</p>
</blockquote>
</br>

<hr>
</br>

<h2 id="dx-파일-생성">dx 파일 생성</h2>
</br>
에러 관련하여 검색해 볼 시 아래와 같은 절차로 수행하라고 나온다.

<p>1) &quot;.../Android/sdk/build-tools/&quot; 진입 (Android SDK 폴더)
2) 31.0.0 폴더 진입 (에러가 발생하는 버전의 폴더)
3) d8.bat 파일을 복사하여 dx.bat 파일명으로 수정
4) lib 폴더에도 마찬가지로 d8.jar 파일을 복사하여 dx.jar 파일명으로 수정</p>
<p><code>위 절차대로 수행 한 이후 빌드 시 정상적으로 동작하는 것을 확인할 수 있었다.</code></p>
<pre><code>Build-tool 31.0.0 is missing DX at /Users/gun/Developement/Android/build-tools/31.0.0/dx

실제 빌드 과정에서 위와 같은 메시지가 출력된다.</code></pre><blockquote>
<p>dx와 d8은 &quot;.class&quot; 바이트코드를 안드로이드 가상머신(ART or DVM) 용 &quot;.dex&quot; 바이트코드로 변환하는 프로세스 도구이며 dex 컴파일러라고 부른다.
d8은 dx보다 더 빠르고 더 작은 크기로 dex 파일을 생성하는 더 우수한 성능을 보인다.
</br>Android 측에서 이전부터 dx를 지원 중단할 예정이라 공식적으로 발표하였고,
 Android 스튜디오 3.1 버전부터 기본 dex 컴파일러로 D8이 적용되었다.
또한 Android 스튜디오 3.4 버전부터 기본 dex 컴파일러로 R8이 적용되었지만, 
그리고 Android 12 (API Level 31) 버전부터 Android SDK 빌드 툴에 dx를 포함 시키지 않게 되었다.
</br>  [D8 dexer로 전환하는 Android Studio] (<a href="https://android-developers.googleblog.com/2018/04/android-studio-switching-to-d8-dexer.html">https://android-developers.googleblog.com/2018/04/android-studio-switching-to-d8-dexer.html</a>)</p>
</blockquote>
</br>

<hr>
<p>.<br>
.<br>
.<br>
<span style="background-color:#FFD8D8; font-weight:bold; font-size:1.3em">그런데 다른 PC 환경에서는 이러한 별도의 세팅 없이 왜 동작하는 것일까?</span>
</br>
.<br>
.<br>
.<br></p>
<hr>
</br>

<h2 id="gradle-버전-업데이트">Gradle 버전 업데이트</h2>
</br>

<p><a href="https://issuetracker.google.com/issues/190734097#comment2">Google Issue Tracker</a>에 동일한 이슈가 올라와 있다.
Android 12 이상을 Target 할 경우 AGP 4.x 이하와 호환되지 않으니 AGP를 7.0 이상으로 업그레이드하라는 내용이다.</p>
<p>현재 Gradle 버전은 6.5, AGP 버전은 4.1.3으로 설정되어있음을 확인 후 <code>Gradle 버전을 7.x 버전으로 업그레이드하였고 이후 빌드 시 정상적으로 동작하는 것을 확인할 수 있었다.</code></p>
<p>Android 스튜디오 Fox 버전으로 업데이트 되면 Gradle을 7.0 이상으로 설정하도록 가이드 해준다.</p>
<blockquote>
<p>Gradle과 AGP(Android Gradle Plugin)</p>
</blockquote>
<ul>
<li>Gradle은 범용 빌드 자동화 도구이며 Android를 포함한 다양한 유형의 소프트웨어를 빌드하는데 사용되는 범용 빌드 자동화 도구이다.</li>
<li>AGP(Android Gradle Plugin)는 Android용으로 지원되는 빌드 시스템이다.
</br><a href="https://medium.com/androiddevelopers/gradle-and-agp-build-apis-configure-your-build-9a10db5b2262">자세히 보기</a></li>
</ul>
</br>

<hr>
<p><strong>[안드로이드 스튜디오 / Gradle / AGP 상호 호환 버전]</strong></p>
<table style="border: 1px solid transparent;">
  <tr style="background-color: #f4f4f4; vertical-align:top">
    <td style="border: 1px solid transparent; padding-right:12px;">
    <img src="https://velog.velcdn.com/images/my_gun/post/5cc3f840-8e79-4207-abeb-25fa86563531/image.png"/>
    </td>
    <td style="padding-left:10px">
      <img src="https://velog.velcdn.com/images/my_gun/post/8b8f4eee-45b7-497a-924b-7858f0162ecf/image.png"/>
    </td>
  </tr>
</table>

<hr>
]]></description>
        </item>
    </channel>
</rss>