일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- F
- El
- 팩토리 메소드
- Singleton
- PrototypePattern
- Kotlin
- 추상팩토리패턴
- 옵저버 패턴
- 코틀린
- 프로토타입 패턴
- 디자인패턴 #
- builderPattern
- designPattern
- 싱글톤
- 함수형프로그래밍
- ㅓ
- Abstract Factory
- Functional Programming
- Design Pattern
- a
- 빌터패턴
- 디자인패턴
- r
- Observer Pattern
- ㅋㅁ
- factory method
- 추상 팩토리
- Today
- Total
오늘도 더 나은 코드를 작성하였습니까?
Android Code Lab(Dagger2) 정리6 본문
Dagger에 @Inject로 생성자에 주석을 추가하여 SettingsActivity 종속성(예: SettingsViewModel)의 인스턴스를 생성하는 방법을 알려줍니다.
SettingsViewModel.kt
class SettingsViewModel @Inject constructor(
private val userDataRepository: UserDataRepository,
private val userManager: UserManager
) { ... }
AppComponent 인터페이스에서 SettingsActivity를 매개변수로 사용하는 함수를 추가하여 Dagger에서 SettingsActivity를 주입.
AppComponent.kt
@Singleton
@Component(modules = [StorageModule::class, AppSubcomponents::class])
interface AppComponent {
...
fun inject(activity: SettingsActivity)
}
SettingsActivity.kt
class SettingsActivity : AppCompatActivity() {
@Inject
lateinit var settingsViewModel: SettingsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
(application as MyApplication).appComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
setupViews()
}
}
이대로 App을 실행하면 Settomg에서 Notification refresh 기능이 동작하지 않는 것을 확인할 수 있습니다.
MainActivity와 SettingsActivity에서 동일한 UserDataRepository 인스턴스를 재사용하지 않기 때문이다.
@Singleton으로 주석을 달아 UserDataRepository의 범위를 AppComponent로 지정하면 해결되나?
사용자가 Logout 또는 Registration 을 취소하면 동일한 UserDataRepository 인스턴스를 메모리에 유지하면 않기 때문에 하면 안된다. 해당 데이터는 로그인한 사용자에게만 보여줘야한다.
사용자가 로그인되어 있는 동안 지속되는 Component를 만들 필요가 있다!
사용자가 로그인한 후 액세스할 수 있는 모든 Activity는 이 Component에 의해 주입된다.
UserComponent.kt
// Definition of a Dagger subcomponent
@Subcomponent
interface UserComponent {
// Factory to create instances of UserComponent
@Subcomponent.Factory
interface Factory {
fun create(): UserComponent
}
// Classes that can be injected by this Component
fun inject(activity: MainActivity)
fun inject(activity: SettingsActivity)
}
AppSubcomponents.kt
@Module(subcomponents = [RegistrationComponent::class, LoginComponent::class, UserComponent::class])
class AppSubcomponents
UserComponent의 Lifetime을 담당하는 것은 무엇입니까?
LoginComponent 및 RegistrationComponent는 각각의 Activity의 수명에 연결 되지만, UserComponent는 둘 이상의Activity을 주입할 수 있으며 Activity의 수가 앞으로도 증가할 수 있다.
Component의 Lifetime을 사용자가 login 및 logout할 때를 알고 있는 무언가에 연결해야 합니다.
UserManager!
등록, 로그인, 로그아웃 시도를 처리하므로 UserComponent 인스턴스가 있어야 합니다.
UserManager가 UserComponent의 새 인스턴스를 생성해야 하는 경우 UserComponent 팩토리에 액세스해야 한다.
팩토리를 생성자 매개변수로 추가하면 Dagger는 UserManager의 인스턴스를 생성할 때 이를 제공합니다.
수동 종속성 주입에서는 사용자의 세션 데이터(userDataRepository)가 UserManager에 저장되었습니다.
그것은 사용자가 로그인했는지 여부를 결정했습니다. 대신 UserComponent로 동일한 작업을 수행할 수 있습니다.
@Singleton
class UserManager @Inject constructor(
private val storage: Storage,
private val userComponentFactory: UserComponent.Factory
) {
//Remove line
var userDataRepository: UserDataRepository? = null
// Add or edit the following lines
var userComponent: UserComponent? = null
private set
fun isUserLoggedIn() = userComponent != null
fun logout() {
userComponent = null
}
private fun userJustLoggedIn() {
userComponent = userComponentFactory.create()
}
}
사용자가 UserComponent 팩토리의 create 메소드를 사용하여 로그인할 때 userComponent의 인스턴스를 생성합니다. 그리고 logout()이 호출될 때 인스턴스를 제거합니다
MainActivity와 SettingsActivity가 동일한 인스턴스를 공유할 수 있도록 UserDataRepository의 Scope가 UserComponent로 지정되어야 한다.
Activity에 lifetime을 연결되는 Component 에 주석을 달기 위해 @ActivityScope 범위 주석을 사용했기 때문에 모든 애플리케이션이 아닌 Multi Activity 을 포함할 수 있는 Scope가 필요합니다.
새로운 Scope는 사용자가 로그인했을 때의 수명을 시작하므로 LoggedUserScope라고 부른다.
@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class LoggedUserScope
UserComponent가 항상 동일한 UserDataRepository 인스턴스를 제공할 수 있도록 이 주석으로 UserComponent와 UserDataRepository에 주석을 추가한다.
UserComponent.kt
// Scope annotation that the UserComponent uses
// Classes annotated with @LoggedUserScope will have a unique instance in this Component
@LoggedUserScope
@Subcomponent
interface UserComponent { ... }
UserDataRepository.kt
// This object will have a unique instance in a Component that
// is annotated with @LoggedUserScope (i.e. only UserComponent in this case).
@LoggedUserScope
class UserDataRepository @Inject constructor(private val userManager: UserManager) {
...
}
MyApplication에는 수동 종속성 주입 구현에 필요한 userManager 인스턴스를 저장했습니다.
하지만 이제 앱은 Dagger 사용하도록 완전히 리팩토링되었으므로 더 이상 필요하지 않습니다
MyApplication.kt
open class MyApplication : Application() {
val appComponent: AppComponent by lazy {
DaggerAppComponent.factory().create(applicationContext)
}
}
MainActivity 및 SettingsActivity가 더 이상 AppComponent에 의해 주입되지 않으므로 주입 메서드를 제거하고 UserComponent를 사용합니다. MainActivity 및 SettingsActivity가 UserComponent의 인스턴스에 액세스하는 데 필요하므로 그래프에서 UserManager를 노출합니다
AppComponent.kt
@Singleton
@Component(modules = [StorageModule::class, AppSubComponents::class])
interface AppComponent {
// Factory to create instances of the AppComponent
@Component.Factory
interface Factory {
// @BindsInstance, graph 외부객체인 context 를 AppComponent 에 포함 시켜 그래프에 주입한다.
fun create(@BindsInstance context: Context): AppComponent
}
fun userManager(): UserManager
// Expose RegistrationComponent factory from the graph
fun registrationComponent(): RegistrationComponent.Factory
fun loginComponent(): LoginComponent.Factory
}
SettingsActivity에서 @Inject로 ViewModel에 주석을 달고(Dagger에 의해 주입되기를 원하기 때문에) private modifier를 제거
SettingsActivity.kt
class SettingsActivity : AppCompatActivity() {
@Inject
lateinit var settingsViewModel: SettingsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
val userManager = (application as MyApplication).appComponent.userManager()
userManager.userComponent!!.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
setupViews()
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
@Inject
lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_main)
super.onCreate(savedInstanceState)
// 로그인 되어있으면 -> 메인페이지
// 로그인 안되어있으면 -> 등록한적이 있음 -> 로그인 -> 메인
// 없음 -> 등 록 -> 메인
val userManager = (application as MyApplication).appComponent.userManager()
if (!userManager.isUserLoggedIn()) {
if (!userManager.isUserRegistered()) {
startActivity(Intent(this, RegistrationActivity::class.java))
finish()
} else {
startActivity(Intent(this, LoginActivity::class.java))
finish()
}
} else {
userManager.userComponent!!.inject(this)
setupViews()
}
}
}
조건부 필드 주입(사용자가 로그인한 경우에만 주입할 때 MainActivity.kt에서 하는 것처럼)을 수행하는 것은 매우 위험합니다.
개발자는 조건을 알고 있어야 하며, 주입된 필드와 상호 작용할 때 NullPointerException이 발생할 위험이 있습니다.
이 문제를 피하기 위해 사용자의 State에 따라 Registration, Login 또는 Main으로 라우팅되는 SplashScreen을 생성하여 간접 참조를 추가할 수 있습니다
'DI > Dagger2' 카테고리의 다른 글
Android Code Lab(Dagger2) 정리7 (0) | 2021.12.22 |
---|---|
Android Code Lab(Dagger2) 정리5 (0) | 2021.12.22 |
Android Code Lab(Dagger2) 정리4 (0) | 2021.12.21 |
Android Code Lab(Dagger2) 정리3 (0) | 2021.12.21 |
Android Code Lab(Dagger2) 정리2 (0) | 2021.12.21 |