@@ -3,29 +3,208 @@ DependencyProperty
3
3
4
4
` DependencyProperty ` is a dependency resolution library by Delegated Property.
5
5
6
+ ## Overview
7
+
8
+ ` DependencyProperty ` is
9
+
10
+ - simple in defining and resolving dependencies.
11
+ - usable in classes accessible to Application instance like Activity, Fragment, and etc.
12
+ - able to constructor injection to ViewModel without affecting other classes.
13
+ - less code for testing than Dagger Hilt.
14
+ - easy to use in multi-module and Dynamic Feature Module.
15
+ - easy to manage modules lifecycle.
16
+ - faster build time than Dagger.
17
+ - faster execution time than Dagger and Koin.
18
+
6
19
## Usage
7
- ### Application
20
+ ### Configure DependencyProperty in Application
21
+ Application class must implements ` DependencyModulesHolder ` like below.
22
+
23
+ ``` kt
24
+ class App : Application (), DependencyModulesHolder {
25
+ override val dependencyModules: DependencyModules by dependencyModules(AppModule (this ), CoroutinesModule ())
26
+ }
27
+ ```
28
+
29
+ You can pass variable number of arguments(` DependencyModule ` ) to ` dependencyModules() ` .
30
+
31
+ ### Define dependencies in DependencyModule
32
+ ` DependencyModule ` is marker interface.
33
+ You can define dependencies as property and function.
34
+
35
+ ``` kt
36
+ open class CoroutinesModule : DependencyModule {
37
+ open val defaultDispatcher: CoroutineDispatcher = Dispatchers .Default
38
+ open val ioDispatcher: CoroutineDispatcher = Dispatchers .IO
39
+ open val mainDispatcher: CoroutineDispatcher = Dispatchers .Main
40
+ open val mainImmediateDispatcher: CoroutineDispatcher = Dispatchers .Main .immediate
41
+ }
42
+ ```
43
+
44
+ You can resolve other DependencyModule using below extension methods.
45
+
46
+ - ` inline fun <reified T : DependencyModule> Application.dependencyModule(): T `
47
+ - ` inline fun <reified T : DependencyModule> FragmentActivity.dependencyModule(): T `
48
+ - ` inline fun <reified T : DependencyModule> Fragment.dependencyModule(): T `
49
+ - ` inline fun <reified T : DependencyModule> AndroidViewModel.dependencyModule(): T `
50
+ - ` inline fun <reified T : DependencyModule> Service.dependencyModule(): T `
51
+
52
+ ``` kt
53
+ class AppModule (private val application : Application ) : DependencyModule {
54
+ private val coroutinesModule: CoroutinesModule by lazy {
55
+ application.dependencyModule<CoroutinesModule >()
56
+ }
57
+ val loadItemsUseCase: LoadItemsUseCase
58
+ get() = LoadItemsUseCase (coroutineModule.ioDispatcher)
59
+ }
60
+ ```
61
+
62
+ - If You want to define dependency as singleton, you can define property as lazy.
63
+ - If You want to define dependency as not singleton, you can define property as getter.
64
+ - If You want to define dependency using parameters, you can define property as function.
65
+
66
+ ### Resolve dependencies
67
+
68
+ You can resolve dependency by delegated property using below extension methods.
69
+
70
+ - ` fun <T: DependencyModule, R> Application.dependency<T, R>(resolve: (T) -> R): Lazy<R> `
71
+ - ` fun <T: DependencyModule, R> FragmentActivity.dependency<T, R>(resolve: (T) -> R): Lazy<R> `
72
+ - ` fun <T: DependencyModule, R> Fragment.dependency<T, R>(resolve: (T) -> R): Lazy<R> `
73
+ - ` fun <T: DependencyModule, R> AndroidViewModel.dependency<T, R>(resolve: (T) -> R): Lazy<R> `
74
+ - ` fun <T: DependencyModule, R> Service.dependency<T, R>(resolve: (T) -> R): Lazy<R> `
75
+
76
+ Activity's example is below.
77
+
78
+ ``` kt
79
+ class MainActivity : AppCompatActivity () {
80
+ private val loadItemsUseCase by dependency<AppModule , LoadItemsUseCase > { it.loadItemsUseCase }
81
+ }
82
+ ```
83
+
84
+ For testing, ViewModel inherits AndroidViewModel and its constructor is annotated ` @JvmOverloads ` .
85
+
86
+ ``` kt
87
+ class MainViewModel @JvmOverloads constructor(
88
+ application : Application ,
89
+ savedStateHandle : SavedStateHandle
90
+ private val loadItemsUseCase : LoadItemsUseCase = application.dependencyModule<AppModule >().loadItemsUseCase
91
+ ) : AndroidViewModel(application) {
92
+ // You can use loadItemsUseCase
93
+ }
94
+ ```
95
+
96
+ By @JvmOverloads , ViewModel's dependencies is passed as default arguments.
97
+
98
+ ### Unit Test
99
+
100
+ In Unit Test, DependencyProperty is not used.
101
+ You can inject to constructor.
102
+
103
+ ``` kt
104
+ @Test
105
+ fun test () {
106
+ // init loadItemsUseCase
107
+ // ...
108
+ // init ViewModel
109
+ val viewModel = MainViewModel (Application (), SavedStateHandle (), loadItemsUseCase)
110
+ // test ViewModel
111
+ }
112
+ ```
113
+
114
+ ### UI Test
115
+
116
+ You need only to define CustomTestRunner and Application for testing.
117
+
118
+ ``` gradle
119
+ android {
120
+ defaultConfig {
121
+ // Replace com.example with your class path.
122
+ testInstrumentationRunner "com.example.CustomTestRunner"
123
+ }
124
+ }
125
+ ```
126
+
127
+ ``` kt
128
+ class CustomTestRunner : AndroidJUnitRunner () {
129
+ override fun newApplication (cl : ClassLoader ? , name : String? , context : Context ? ): Application {
130
+ return super .newApplication(cl, TestApp ::class .java.name, context)
131
+ }
132
+ }
133
+ ```
134
+
135
+ You can override DependencyModule by passing inherited DependencyModule.
136
+
137
+ ``` kt
138
+ class TestApp : Application (), DependencyModulesHolder {
139
+ override val dependencyModules: DependencyModules by dependencyModules(AppModule (this ), TestCoroutinesModule ())
140
+ }
141
+ ```
142
+
143
+ TestCoroutinesModule inherits CoroutinesModule and overrides its properties.
144
+
145
+ ``` kt
146
+ class TestCoroutinesModule : CoroutinesModule () {
147
+ override val defaultDispatcher: CoroutineDispatcher = AsyncTask .THREAD_POOL_EXECUTOR .asCoroutineDispatcher()
148
+ override val ioDispatcher: CoroutineDispatcher = AsyncTask .THREAD_POOL_EXECUTOR .asCoroutineDispatcher()
149
+ override val mainDispatcher: CoroutineDispatcher = Dispatchers .Main
150
+ override val mainImmediateDispatcher: CoroutineDispatcher = Dispatchers .Main .immediate
151
+ }
152
+ ```
153
+
154
+ ### Multi-module and Dynamic Feature Module
155
+
156
+ In multi-module, no extra settings.
157
+ You can use other module's DependencyModule in app module.
158
+
8
159
``` kt
9
160
class App : Application (), DependencyModulesHolder {
10
- override val dependencyModules: DependencyModules by dependencyModules(AppModule ())
161
+ override val dependencyModules: DependencyModules by dependencyModules(AppModule (this ), CoroutinesModule ( ))
11
162
}
12
163
```
13
164
14
- ### Module
165
+ In Dynamic Feature Module, you can add DependencyModule dynamically using below extension methods.
166
+
167
+ ``` kt
168
+ val Application .dependencyModules: DependencyModules
169
+ get() = (this as DependencyModulesHolder ).dependencyModules
170
+ val FragmentActivity .dependencyModules: DependencyModules
171
+ get() = application.dependencyModules
172
+ val Fragment .dependencyModules: DependencyModules
173
+ get() = requireActivity().application.dependencyModules
174
+ val AndroidViewModel .dependencyModules: DependencyModules
175
+ get() = getApplication<Application >().dependencyModules
176
+ val Service .dependencyModules: DependencyModules
177
+ get() = application.dependencyModules
178
+ ```
179
+
180
+ Activity's example is below.
181
+
15
182
``` kt
16
- class AppModule : DependencyModule {
17
- val singleton: String by lazy { " singleton" }
18
- val factory: String get() = " factory"
19
- fun provide (instance : Int ): Pair <String , Int > = singleton to instance
183
+ class MainActivity : AppCompatActivity () {
184
+ override fun onCreate (savedInstanceState : Bundle ? ) {
185
+ super .onCreate(savedInstanceState)
186
+ dependencyModules.addModule(DynamicModule ()) // add only once
187
+ }
20
188
}
21
189
```
22
190
23
- ### Activity/Fragment/AndroidViewModel
191
+ ### Lifecycle of DependencyModule
192
+
193
+ You can manage lifecycle of DependencyModule below methods.
194
+
195
+ - ` fun <T: DependencyModule> DependencyModules.addModule(module: T) `
196
+ - ` fun <T: DependencyModule> DependencyModules.removeModule<T>() `
197
+ - ` fun <T: DependencyModule> DependencyModules.replaceModule(module: T) `
198
+
199
+ Activity's example is below.
200
+
24
201
``` kt
25
202
class MainActivity : AppCompatActivity () {
26
- private val singleton by dependency<AppModule , String > { it.singleton }
27
- private val factory by dependency<AppModule , String > { it.factory }
28
- private val provide by dependency<AppModule , Pair <String , Int >> { it.provide(42 ) }
203
+ override fun onCreate (savedInstanceState : Bundle ? ) {
204
+ super .onCreate(savedInstanceState)
205
+ // Replace an existing DependencyModule of the same type with a new DependencyModule
206
+ dependencyModules.replaceModule(AppModule (application))
207
+ }
29
208
}
30
209
```
31
210
0 commit comments