수동 종속 항목 삽입 (뷰)

개념 및 Jetpack Compose 구현

Android의 권장 앱 아키텍처는 코드를 클래스로 분할하여 관심사 분리의 이점을 누리길 권장합니다. 관심사 분리는 정의된 단일 책임이 계층 구조의 각 클래스에 있는 원칙입니다. 이로 인해 서로의 종속 항목을 충족하기 위해 연결해야 하는 더 작고 많은 클래스가 생성됩니다.

Android 앱은 일반적으로 여러 클래스로 구성되며 그중 일부는 서로 종속됩니다.
그림 1. Android 앱의 애플리케이션 그래프

클래스 간 종속성은 그래프로 표시할 수 있고 그래프에서 각 클래스는 종속된 클래스에 연결됩니다. 모든 클래스와 서로의 종속성을 표시하면 애플리케이션 그래프 가 구성됩니다. 그림 1은 애플리케이션 그래프의 추상적 개념을 보여줍니다. 클래스 A (ViewModel)가 클래스 B (Repository)에 종속되면 A에서 B로 향하는 선이 종속 항목을 나타냅니다.

종속 항목 삽입을 사용하면 클래스를 쉽게 연결할 수 있고 테스트를 위해 구현을 교체할 수 있습니다. 예를 들어 저장소에 종속된 ViewModel을 테스트할 때 가짜 또는 모의 구현과 함께 Repository의 다른 구현을 전달하여 다른 사례를 테스트할 수 있습니다.

수동 종속 항목 삽입의 기본사항

이 섹션에서는 실제 Android 앱 시나리오에서 수동 종속 항목 삽입을 적용하는 방법을 다룹니다. 앱에서 종속성 삽입을 어떻게 사용할 수 있는지 반복되는 접근 방식을 안내합니다. 이 접근 방식은 Dagger에서 자동으로 생성하는 것과 아주 유사해지는 지점에 도달할 때까지 계속 개선됩니다. Dagger에 관한 자세한 내용은 Dagger 기본사항을 참고하세요.

흐름 은 앱에서 기능에 상응하는 화면 그룹이라고 간주합니다. 로그인, 등록, 결제가 모두 흐름의 예입니다.

일반적인 Android 앱의 로그인 흐름을 다룰 때 LoginActivityLoginViewModel에 종속되고 LoginViewModel은 UserRepository에 종속됩니다. 그런 다음 UserRepository 에 종속되고 이는UserLocalDataSourceUserRemoteDataSource 에 종속되며 이는 Retrofit 서비스에 종속됩니다.

LoginActivity 는 로그인 흐름의 진입점이며 사용자는 이 활동과 상호작용합니다. 따라서 LoginActivity는 모든 종속 항목과 함께 LoginViewModel을 만들어야 합니다.

흐름의 RepositoryDataSource 클래스는 다음과 같습니다.

Kotlin

class UserRepository(
   private val localDataSource: UserLocalDataSource,
   private val remoteDataSource: UserRemoteDataSource
) { ... }

class UserLocalDataSource { ... }
class UserRemoteDataSource(
   private val loginService: LoginRetrofitService
) { ... }

자바

class UserLocalDataSource {
   public UserLocalDataSource() { }
   ...
}

class UserRemoteDataSource {

   private final Retrofit retrofit;

   public UserRemoteDataSource(Retrofit retrofit) {
       this.retrofit = retrofit;
   }

   ...
}

class UserRepository {

   private final UserLocalDataSource userLocalDataSource;
   private final UserRemoteDataSource userRemoteDataSource;

   public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
       this.userLocalDataSource = userLocalDataSource;
       this.userRemoteDataSource = userRemoteDataSource;
   }

   ...
}

LoginActivity는 다음과 같습니다.

Kotlin

class LoginActivity: Activity() {

   private lateinit var loginViewModel: LoginViewModel

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

       // In order to satisfy the dependencies of LoginViewModel, you have to also
       // satisfy the dependencies of all of its dependencies recursively.
       // First, create retrofit which is the dependency of UserRemoteDataSource
       val retrofit = Retrofit.Builder()
           .baseUrl("https://example.com")
           .build()
           .create(LoginService::class.java)

       // Then, satisfy the dependencies of UserRepository
       val remoteDataSource = UserRemoteDataSource(retrofit)
       val localDataSource = UserLocalDataSource()

       // Now you can create an instance of UserRepository that LoginViewModel needs
       val userRepository = UserRepository(localDataSource, remoteDataSource)

       // Lastly, create an instance of LoginViewModel with userRepository
       loginViewModel = LoginViewModel(userRepository)
   }
}

자바

public class MainActivity extends Activity {

   private LoginViewModel loginViewModel;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       // In order to satisfy the dependencies of LoginViewModel, you have to also
       // satisfy the dependencies of all of its dependencies recursively.
       // First, create retrofit which is the dependency of UserRemoteDataSource
       Retrofit retrofit