├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── dbnavigator.xml ├── dictionaries │ └── peyman.xml ├── encodings.xml ├── gradle.xml ├── kotlinScripting.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── xvermilion │ │ └── modulesample │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── xvermilion │ │ │ └── modulesample │ │ │ ├── LauncherActivity.kt │ │ │ ├── ModuleProvider.kt │ │ │ └── MyApplication.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── xvermilion │ └── modulesample │ └── ExampleUnitTest.kt ├── build.gradle ├── buildSrc ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── java │ └── Dependencies.kt ├── domain ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── xvermilion │ │ └── modulesample │ │ └── domain │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── xvermilion │ │ │ └── modulesample │ │ │ └── domain │ │ │ ├── DomainModules.kt │ │ │ ├── ports │ │ │ ├── network │ │ │ │ ├── AuthorNetworkPort.kt │ │ │ │ ├── BookNetworkPort.kt │ │ │ │ └── UserNetworkPort.kt │ │ │ └── persistence │ │ │ │ ├── BookDaoPort.kt │ │ │ │ ├── PreferenceStorage.kt │ │ │ │ └── UserDaoPort.kt │ │ │ └── repository │ │ │ ├── AuthorRepositoryAdapter.kt │ │ │ ├── BookRepositoryAdapter.kt │ │ │ └── UserRepositoryAdapter.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── xvermilion │ └── modulesample │ └── domain │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── home └── .gitignore ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── xvermilion │ │ └── modulesample │ │ └── library │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── xvermilion │ │ │ └── modulesample │ │ │ └── library │ │ │ ├── NetworkResponse.kt │ │ │ ├── dto │ │ │ ├── Author.kt │ │ │ ├── Book.kt │ │ │ ├── Cover.kt │ │ │ └── User.kt │ │ │ └── repository │ │ │ ├── AuthorRepository.kt │ │ │ ├── BookRepository.kt │ │ │ └── UserRepository.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── xvermilion │ └── modulesample │ └── library │ └── ExampleUnitTest.java ├── navigation ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── xvermilion │ │ └── modulesample │ │ └── navigation │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── xvermilion │ │ │ └── modulesample │ │ │ └── navigation │ │ │ ├── DynamicFeature.kt │ │ │ ├── HomeNavigation.kt │ │ │ ├── NavigationActivity.kt │ │ │ └── loaders │ │ │ ├── FragmentLoader.kt │ │ │ ├── GenericLoader.kt │ │ │ └── IntentLoader.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── xvermilion │ └── modulesample │ └── navigation │ └── ExampleUnitTest.java ├── network ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── xvermilion │ │ └── modulesample │ │ └── network │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── xvermilion │ │ │ └── modulesample │ │ │ └── network │ │ │ ├── CustomException.kt │ │ │ ├── ErrorConverter.kt │ │ │ ├── Extensions.kt │ │ │ ├── NetworkModules.kt │ │ │ ├── adapter │ │ │ ├── AuthorNetworkAdapter.kt │ │ │ ├── BookNetworkAdapter.kt │ │ │ └── UserNetworkAdapter.kt │ │ │ ├── model │ │ │ ├── AuthorResponse.kt │ │ │ ├── BaseRequest.kt │ │ │ ├── BaseResponse.kt │ │ │ ├── BookResponse.kt │ │ │ ├── CoverPhotoResponse.kt │ │ │ └── UserResponse.kt │ │ │ └── webservice │ │ │ ├── AuthorWebService.kt │ │ │ ├── BookWebService.kt │ │ │ └── UserWebService.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── xvermilion │ └── modulesample │ └── network │ └── ExampleUnitTest.java ├── persistence ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── xvermilion │ │ └── modulesample │ │ └── persistence │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── xvermilion │ │ │ └── modulesample │ │ │ └── persistence │ │ │ ├── Extensions.kt │ │ │ ├── MyDatabase.kt │ │ │ ├── PersistenceModules.kt │ │ │ ├── adapters │ │ │ ├── BookDaoAdapter.kt │ │ │ └── UserDaoAdapter.kt │ │ │ ├── converter │ │ │ └── DateConverter.kt │ │ │ ├── dao │ │ │ ├── BaseDao.kt │ │ │ ├── BookDao.kt │ │ │ └── UserDao.kt │ │ │ ├── entites │ │ │ ├── BookEntity.kt │ │ │ └── UserEntity.kt │ │ │ └── preferences │ │ │ ├── SharedPreferenceStorage.kt │ │ │ └── TypePreferences.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── xvermilion │ └── modulesample │ └── persistence │ └── ExampleUnitTest.java ├── settings.gradle ├── template-android-library.gradle ├── template-dynamic-feature.gradle ├── template-kotlin-library.gradle ├── ui_home ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── xvermilion │ │ └── modulesample │ │ └── home │ │ ├── HomeActivity.kt │ │ ├── HomeModules.kt │ │ └── HomeViewModel.kt │ └── res │ └── layout │ └── activity_home.xml └── ui_shared ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── xvermilion │ └── modulesample │ └── shared │ └── ExampleInstrumentedTest.java ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── xvermilion │ │ └── modulesample │ │ └── shared │ │ └── EntryActivity.kt └── res │ └── values │ └── strings.xml └── test └── java └── com └── xvermilion └── modulesample └── shared └── ExampleUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 202 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/dbnavigator.xml: -------------------------------------------------------------------------------- 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 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | -------------------------------------------------------------------------------- /.idea/dictionaries/peyman.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | pinner 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 26 | -------------------------------------------------------------------------------- /.idea/kotlinScripting.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ModularizationSample 2 | Sample project for modularizing an Android application 3 | Link to the article 4 | 5 | The main part of the application is completed. 6 | Some UI features are still under construction but the main structre of application that is covered in the part 1 of the article is finished. 7 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion AppMetaData.compileSdkVersion 7 | defaultConfig { 8 | applicationId AppMetaData.id 9 | minSdkVersion AppMetaData.minSdkVersion 10 | targetSdkVersion AppMetaData.targetSdkVersion 11 | versionCode AppMetaData.versionCode 12 | versionName AppMetaData.versionName 13 | multiDexEnabled true 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | vectorDrawables.useSupportLibrary = true 16 | buildConfigField "boolean", "DEBUG_LOGS", "true" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | dynamicFeatures = [Modules.home] 27 | 28 | 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | implementation project(Modules.domain) 34 | implementation project(Modules.navigation) 35 | implementation project(Modules.network) 36 | implementation project(Modules.persistence) 37 | 38 | implementation Libraries.kotlin 39 | implementation Libraries.appCompat 40 | implementation Libraries.androidxCore 41 | implementation Libraries.koinXcore 42 | implementation Libraries.koinXscope 43 | implementation Libraries.koinXviewModel 44 | 45 | api Libraries.lifecycleExtensions 46 | api Libraries.lifecycleViewModel 47 | api Libraries.lifecycleLiveData 48 | api Libraries.lifecycleRuntime 49 | 50 | implementation Libraries.multidex 51 | 52 | testImplementation Libraries.jUnit 53 | androidTestImplementation Libraries.testRunner 54 | androidTestImplementation Libraries.testEspresso 55 | } 56 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -keep class androidx.lifecycle.ViewModel { *; } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/xvermilion/modulesample/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.xvermilion.modulesample", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/xvermilion/modulesample/LauncherActivity.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import com.xvermilion.modulesample.navigation.HomeNavigation 6 | 7 | class LauncherActivity : AppCompatActivity() { 8 | 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | HomeNavigation.dynamicStart?.let { startActivity(it) } 12 | finish() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/xvermilion/modulesample/ModuleProvider.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample 2 | 3 | import android.content.ContentResolver 4 | import com.xvermilion.modulesample.domain.DomainModules 5 | import com.xvermilion.modulesample.network.NetworkModules 6 | import com.xvermilion.modulesample.persistence.PersistenceModules 7 | import org.koin.android.ext.koin.androidContext 8 | import org.koin.core.module.Module 9 | import org.koin.dsl.module 10 | 11 | object ModuleProvider { 12 | 13 | private val appModules = module { 14 | single { androidContext().contentResolver } 15 | } 16 | 17 | val modules: List 18 | get() { 19 | return ArrayList().apply { 20 | add(appModules) 21 | addAll(DomainModules.modules) 22 | addAll(PersistenceModules.modules) 23 | addAll(NetworkModules.modules) 24 | } 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/xvermilion/modulesample/MyApplication.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.multidex.MultiDex 6 | import org.koin.android.ext.koin.androidContext 7 | import org.koin.core.context.startKoin 8 | 9 | class MyApplication : Application() { 10 | 11 | override fun onCreate() { 12 | super.onCreate() 13 | startKoin { 14 | printLogger() 15 | androidContext(this@MyApplication) 16 | modules(ModuleProvider.modules) 17 | } 18 | } 19 | 20 | override fun attachBaseContext(base: Context?) { 21 | super.attachBaseContext(base) 22 | MultiDex.install(this) 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marchosiax/ModularizationSample/e4e1f0772882abd2183c449d6bd323f7fbf587a8/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marchosiax/ModularizationSample/e4e1f0772882abd2183c449d6bd323f7fbf587a8/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marchosiax/ModularizationSample/e4e1f0772882abd2183c449d6bd323f7fbf587a8/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marchosiax/ModularizationSample/e4e1f0772882abd2183c449d6bd323f7fbf587a8/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marchosiax/ModularizationSample/e4e1f0772882abd2183c449d6bd323f7fbf587a8/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marchosiax/ModularizationSample/e4e1f0772882abd2183c449d6bd323f7fbf587a8/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marchosiax/ModularizationSample/e4e1f0772882abd2183c449d6bd323f7fbf587a8/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marchosiax/ModularizationSample/e4e1f0772882abd2183c449d6bd323f7fbf587a8/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marchosiax/ModularizationSample/e4e1f0772882abd2183c449d6bd323f7fbf587a8/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marchosiax/ModularizationSample/e4e1f0772882abd2183c449d6bd323f7fbf587a8/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Modularization Sample 3 | Home 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/xvermilion/modulesample/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | repositories { 4 | google() 5 | jcenter() 6 | 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.4.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /buildSrc/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.gradle.kotlin.dsl.`kotlin-dsl` 2 | 3 | plugins { 4 | `kotlin-dsl` 5 | } 6 | 7 | repositories { 8 | jcenter() 9 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/Dependencies.kt: -------------------------------------------------------------------------------- 1 | object AppMetaData { 2 | const val id = "com.xvermilion.modulesample" 3 | const val compileSdkVersion = 28 4 | const val targetSdkVersion = 28 5 | const val minSdkVersion = 19 6 | const val versionCode = 1 7 | const val versionName = "1.0.0" 8 | } 9 | 10 | object Versions { 11 | // Application 12 | const val kotlin = "1.3.41" 13 | const val gradle = "3.4.1" 14 | 15 | // Core 16 | const val androidX = "1.0.2" 17 | const val androidXAnnotation = "1.1.0" 18 | const val androidXLegacy = "1.0.0" 19 | const val androidXTest = "1.0.0" 20 | const val lifecycle = "2.2.0-alpha02" 21 | const val multidex = "1.0.3" 22 | const val koin = "2.0.1" 23 | const val coroutines = "1.3.0-M2" 24 | const val okHttp = "3.12.0" 25 | const val retrofit = "2.6.0" 26 | const val room = "2.1.0" 27 | const val gson = "2.8.5" 28 | const val rxjava2 = "2.2.4" 29 | const val rootBeer = "0.0.7" 30 | 31 | // UI 32 | const val constraintLayout = "1.1.3" 33 | const val googleAndroidMaterial = "1.0.0" 34 | 35 | // Test 36 | const val jUnit = "4.12" 37 | const val testRunner = "1.2.0" 38 | const val testEspresso = "3.2.0" 39 | const val roboElectric = "4.3" 40 | const val truth = "1.0" 41 | } 42 | 43 | object Libraries { 44 | 45 | // Core 46 | const val kotlin = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}" 47 | 48 | const val appCompat = "androidx.appcompat:appcompat:${Versions.androidX}" 49 | const val androidxCore = "androidx.core:core-ktx:${Versions.androidX}" 50 | const val androidxLegacy = 51 | "androidx.legacy:legacy-support-core-utils:${Versions.androidXLegacy}" 52 | const val androidxAnnotation = "androidx.annotation:annotation:${Versions.androidXAnnotation}" 53 | const val androidxTest = "androidx.test:core:${Versions.androidXTest}" 54 | const val ktx = "androidx.core:core-ktx:${Versions.androidX}" 55 | 56 | const val multidex = "com.android.support:multidex:${Versions.multidex}" 57 | 58 | const val lifecycleExtensions = "androidx.lifecycle:lifecycle-extensions:${Versions.lifecycle}" 59 | const val lifecycleViewModel = 60 | "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycle}" 61 | const val lifecycleLiveData = "androidx.lifecycle:lifecycle-livedata-ktx:${Versions.lifecycle}" 62 | const val lifecycleRuntime = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle}" 63 | 64 | const val koinXcore = "org.koin:koin-core:${Versions.koin}" 65 | const val koinXscope = "org.koin:koin-androidx-scope:${Versions.koin}" 66 | const val koinXviewModel = "org.koin:koin-androidx-viewmodel:${Versions.koin}" 67 | const val koinTest = "org.koin:koin-test:${Versions.koin}" 68 | 69 | const val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}" 70 | const val coroutinesAndroid = 71 | "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines}" 72 | 73 | const val okhttp3 = "com.squareup.okhttp3:okhttp:${Versions.okHttp}" 74 | const val okhttp3Interceptor = "com.squareup.okhttp3:logging-interceptor:${Versions.okHttp}" 75 | const val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit}" 76 | const val retrofitGsonConverter = "com.squareup.retrofit2:converter-gson:${Versions.retrofit}" 77 | const val gson = "com.google.code.gson:gson:${Versions.gson}" 78 | const val rxjava2 = "io.reactivex.rxjava2:rxjava:${Versions.rxjava2}" 79 | 80 | const val room = "androidx.room:room-runtime:${Versions.room}" 81 | const val roomRX = "androidx.room:room-rxjava2:${Versions.room}" 82 | const val roomCompiler = "androidx.room:room-compiler:${Versions.room}" 83 | const val roomKTX = "androidx.room:room-ktx:${Versions.room}" 84 | 85 | const val rootBeer = "com.scottyab:rootbeer-lib:${Versions.rootBeer}" 86 | 87 | // UI 88 | const val constraintLayout = 89 | "androidx.constraintlayout:constraintlayout:${Versions.constraintLayout}" 90 | const val googleAndroidMaterial = 91 | "com.google.android.material:material:${Versions.googleAndroidMaterial}" 92 | 93 | // Test 94 | const val jUnit = "junit:junit:${Versions.jUnit}" 95 | const val testRunner = "androidx.test:runner:${Versions.testRunner}" 96 | const val testEspresso = "androidx.test.espresso:espresso-core:${Versions.testEspresso}" 97 | const val roboElectric = "org.robolectric:robolectric:${Versions.roboElectric}" 98 | const val truth = "com.google.truth:truth:${Versions.truth}" 99 | 100 | } 101 | 102 | object Modules { 103 | const val app = ":app" 104 | const val library = ":library" // **** Abstraction Module **** 105 | const val domain = ":domain" 106 | const val navigation = ":navigation" 107 | const val network = ":network" 108 | const val persistence = ":persistence" 109 | const val shared = ":ui_shared" 110 | 111 | // Dynamics 112 | const val home = ":ui_home" 113 | } 114 | 115 | object GradleTemplates { 116 | const val dynamicFeature = "template-dynamic-feature.gradle" 117 | const val kotlinLibrary = "template-kotlin-library.gradle" 118 | const val androidLibrary = "template-android-library.gradle" 119 | } -------------------------------------------------------------------------------- /domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /domain/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/${GradleTemplates.androidLibrary}" 2 | 3 | dependencies { 4 | api project(Modules.library) 5 | implementation Libraries.retrofit 6 | implementation Libraries.lifecycleLiveData 7 | } 8 | -------------------------------------------------------------------------------- /domain/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /domain/src/androidTest/java/com/xvermilion/modulesample/domain/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.domain; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.xvermilion.modulesample.domain.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /domain/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /domain/src/main/java/com/xvermilion/modulesample/domain/DomainModules.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.domain 2 | 3 | import com.xvermilion.modulesample.domain.repository.AuthorRepositoryAdapter 4 | import com.xvermilion.modulesample.domain.repository.BookRepositoryAdapter 5 | import com.xvermilion.modulesample.domain.repository.UserRepositoryAdapter 6 | import com.xvermilion.modulesample.library.repository.AuthorRepository 7 | import com.xvermilion.modulesample.library.repository.BookRepository 8 | import com.xvermilion.modulesample.library.repository.UserRepository 9 | import org.koin.core.module.Module 10 | import org.koin.dsl.module 11 | 12 | object DomainModules { 13 | 14 | private val repositoryModule = module { 15 | single { AuthorRepositoryAdapter(get()) } 16 | single { BookRepositoryAdapter(get(), get()) } 17 | single { UserRepositoryAdapter(get(), get()) } 18 | } 19 | 20 | val modules: List = listOf(repositoryModule) 21 | 22 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/xvermilion/modulesample/domain/ports/network/AuthorNetworkPort.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.domain.ports.network 2 | 3 | import com.xvermilion.modulesample.library.NetworkResponse 4 | import com.xvermilion.modulesample.library.dto.Author 5 | 6 | interface AuthorNetworkPort { 7 | 8 | suspend fun getAuthors(): NetworkResponse> 9 | 10 | suspend fun getAuthor(id: Int): NetworkResponse 11 | 12 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/xvermilion/modulesample/domain/ports/network/BookNetworkPort.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.domain.ports.network 2 | 3 | import com.xvermilion.modulesample.library.NetworkResponse 4 | import com.xvermilion.modulesample.library.dto.Book 5 | import com.xvermilion.modulesample.library.dto.Cover 6 | 7 | interface BookNetworkPort { 8 | 9 | suspend fun getBooks(): NetworkResponse> 10 | 11 | suspend fun getBook(id: Int): NetworkResponse 12 | 13 | suspend fun getCoverPhoto(bookId: Int): NetworkResponse 14 | 15 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/xvermilion/modulesample/domain/ports/network/UserNetworkPort.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.domain.ports.network 2 | 3 | import com.xvermilion.modulesample.library.NetworkResponse 4 | import com.xvermilion.modulesample.library.dto.User 5 | 6 | interface UserNetworkPort { 7 | 8 | suspend fun getUserInfo(id: Int): NetworkResponse 9 | 10 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/xvermilion/modulesample/domain/ports/persistence/BookDaoPort.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.domain.ports.persistence 2 | 3 | import androidx.lifecycle.LiveData 4 | import com.xvermilion.modulesample.library.dto.Book 5 | 6 | interface BookDaoPort { 7 | 8 | suspend fun insertOrReplace(book: Book) 9 | 10 | suspend fun getBooks(): List 11 | 12 | fun getBooksLive(): LiveData?> 13 | 14 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/xvermilion/modulesample/domain/ports/persistence/PreferenceStorage.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.domain.ports.persistence 2 | 3 | interface PreferenceStorage { 4 | 5 | var test: Boolean 6 | 7 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/xvermilion/modulesample/domain/ports/persistence/UserDaoPort.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.domain.ports.persistence 2 | 3 | import androidx.lifecycle.LiveData 4 | import com.xvermilion.modulesample.library.dto.User 5 | 6 | interface UserDaoPort { 7 | 8 | suspend fun insertOrUpdate(user: User) 9 | 10 | suspend fun update(user: User) 11 | 12 | suspend fun getUser(): User? 13 | 14 | suspend fun getUserLive(): LiveData 15 | 16 | suspend fun countUsers(): Int 17 | 18 | suspend fun deleteAll() 19 | 20 | suspend fun deleteUser() 21 | 22 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/xvermilion/modulesample/domain/repository/AuthorRepositoryAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.domain.repository 2 | 3 | import com.xvermilion.modulesample.domain.ports.network.AuthorNetworkPort 4 | import com.xvermilion.modulesample.library.repository.AuthorRepository 5 | 6 | internal class AuthorRepositoryAdapter(private val network: AuthorNetworkPort) : AuthorRepository { 7 | 8 | override suspend fun getAuthors() = network.getAuthors() 9 | 10 | override suspend fun getAuthor(id: Int) = network.getAuthor(id) 11 | 12 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/xvermilion/modulesample/domain/repository/BookRepositoryAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.domain.repository 2 | 3 | import com.xvermilion.modulesample.domain.ports.network.BookNetworkPort 4 | import com.xvermilion.modulesample.domain.ports.persistence.BookDaoPort 5 | import com.xvermilion.modulesample.library.NetworkResponse 6 | import com.xvermilion.modulesample.library.dto.Book 7 | import com.xvermilion.modulesample.library.repository.BookRepository 8 | 9 | internal class BookRepositoryAdapter( 10 | private val bookDaoPort: BookDaoPort, 11 | private val network: BookNetworkPort 12 | ) : BookRepository { 13 | 14 | override suspend fun insertFavourite(book: Book) { 15 | bookDaoPort.insertOrReplace(book) 16 | } 17 | 18 | override suspend fun getFavouriteBooks() = bookDaoPort.getBooks() 19 | 20 | override fun getFavouriteBooksLive() = bookDaoPort.getBooksLive() 21 | 22 | override suspend fun getBooks(): NetworkResponse> { 23 | val bookResponse = network.getBooks() 24 | bookResponse.data?.let { books -> 25 | books.forEach { 26 | val coverResponse = network.getCoverPhoto(it.id) 27 | it.cover = coverResponse.data?.url 28 | } 29 | } 30 | return bookResponse 31 | } 32 | 33 | override suspend fun getBook(id: Int): NetworkResponse { 34 | val response = network.getBook(id) 35 | if (response.isSuccessful()) { 36 | response.data?.let { 37 | val coverResponse = network.getCoverPhoto(it.id) 38 | it.cover = coverResponse.data?.url 39 | } 40 | } 41 | return response 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/xvermilion/modulesample/domain/repository/UserRepositoryAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.domain.repository 2 | 3 | import com.xvermilion.modulesample.domain.ports.network.UserNetworkPort 4 | import com.xvermilion.modulesample.domain.ports.persistence.UserDaoPort 5 | import com.xvermilion.modulesample.library.NetworkResponse 6 | import com.xvermilion.modulesample.library.dto.User 7 | import com.xvermilion.modulesample.library.repository.UserRepository 8 | 9 | internal class UserRepositoryAdapter( 10 | private val userDaoPort: UserDaoPort, 11 | private val network: UserNetworkPort 12 | ) : UserRepository { 13 | 14 | override suspend fun insertOrUpdateUserInfo(id: Int): NetworkResponse { 15 | val response = network.getUserInfo(id) 16 | if (response.isSuccessful()) 17 | response.data?.let { userDaoPort.insertOrUpdate(it) } 18 | return response 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /domain/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Domain 3 | 4 | -------------------------------------------------------------------------------- /domain/src/test/java/com/xvermilion/modulesample/domain/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.domain; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marchosiax/ModularizationSample/e4e1f0772882abd2183c449d6bd323f7fbf587a8/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jun 30 12:34:35 IRDT 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /home/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply from:"$rootDir/${GradleTemplates.androidLibrary}" 2 | 3 | dependencies { 4 | implementation Libraries.lifecycleLiveData 5 | } 6 | -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/xvermilion/modulesample/library/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.library; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.InstrumentationRegistry; 6 | import androidx.test.runner.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getTargetContext(); 24 | 25 | assertEquals("com.xvermilion.modulesample.library.test", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /library/src/main/java/com/xvermilion/modulesample/library/NetworkResponse.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.library 2 | 3 | class NetworkResponse private constructor( 4 | var data: D? = null, 5 | var error: Error? = null, 6 | private var state: State = State.WAITING 7 | ) { 8 | 9 | companion object { 10 | fun waiting() = NetworkResponse() 11 | 12 | fun successful(data: D?) = NetworkResponse( 13 | data, 14 | null, 15 | if (data != null) State.SUCCESSFUL else State.SUCCESS_NO_DATA 16 | ) 17 | } 18 | 19 | fun succeeded(data: D? = null) { 20 | this.data = data 21 | state = if (data != null) State.SUCCESSFUL else State.SUCCESS_NO_DATA 22 | } 23 | 24 | fun failed(error: Throwable? = null, message: String? = null) { 25 | this.error = Error( 26 | throwable = error, 27 | message = message 28 | ) 29 | state = State.FAILURE 30 | } 31 | 32 | fun failed(status: Int, error: String?, message: String? = null) { 33 | this.error = Error( 34 | status = status, 35 | error = error, 36 | message = message 37 | ) 38 | state = State.FAILURE 39 | } 40 | 41 | fun onSuccess(action: (D?) -> Unit): NetworkResponse { 42 | if (isSuccessful()) 43 | action(data) 44 | return this 45 | } 46 | 47 | fun onFailure(action: (Error?) -> Unit): NetworkResponse { 48 | if (isFailed()) 49 | action(error) 50 | return this 51 | } 52 | 53 | fun isWaiting() = state == State.WAITING 54 | 55 | fun isSuccessful() = state == State.SUCCESSFUL || state == State.SUCCESS_NO_DATA 56 | 57 | fun isFailed() = state == State.FAILURE 58 | 59 | data class Error( 60 | val status: Int = 0, 61 | val error: String? = null, 62 | val message: String? = null, 63 | val throwable: Throwable? = null 64 | ) 65 | 66 | enum class State { 67 | WAITING, SUCCESSFUL, SUCCESS_NO_DATA, FAILURE 68 | } 69 | } 70 | 71 | suspend fun response(build: suspend NetworkResponse.() -> Unit): NetworkResponse { 72 | val response = NetworkResponse.waiting() 73 | response.build() 74 | return response 75 | } -------------------------------------------------------------------------------- /library/src/main/java/com/xvermilion/modulesample/library/dto/Author.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.library.dto 2 | 3 | interface Author { 4 | val id:Int 5 | val firstName:String? 6 | val lastName:String? 7 | } -------------------------------------------------------------------------------- /library/src/main/java/com/xvermilion/modulesample/library/dto/Book.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.library.dto 2 | 3 | interface Book { 4 | val id: Int 5 | val title: String? 6 | val description: String? 7 | val publishDate: String? 8 | val pages: Int 9 | var cover:String? 10 | } -------------------------------------------------------------------------------- /library/src/main/java/com/xvermilion/modulesample/library/dto/Cover.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.library.dto 2 | 3 | interface Cover { 4 | val id: Int 5 | val bookId: Int 6 | val url: String? 7 | } -------------------------------------------------------------------------------- /library/src/main/java/com/xvermilion/modulesample/library/dto/User.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.library.dto 2 | 3 | interface User { 4 | val id: Int 5 | var username: String? 6 | var password: String? 7 | } -------------------------------------------------------------------------------- /library/src/main/java/com/xvermilion/modulesample/library/repository/AuthorRepository.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.library.repository 2 | 3 | import com.xvermilion.modulesample.library.NetworkResponse 4 | import com.xvermilion.modulesample.library.dto.Author 5 | 6 | interface AuthorRepository { 7 | 8 | suspend fun getAuthors(): NetworkResponse> 9 | 10 | suspend fun getAuthor(id: Int): NetworkResponse 11 | 12 | } -------------------------------------------------------------------------------- /library/src/main/java/com/xvermilion/modulesample/library/repository/BookRepository.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.library.repository 2 | 3 | import androidx.lifecycle.LiveData 4 | import com.xvermilion.modulesample.library.NetworkResponse 5 | import com.xvermilion.modulesample.library.dto.Book 6 | 7 | interface BookRepository { 8 | 9 | suspend fun insertFavourite(book: Book) 10 | 11 | suspend fun getFavouriteBooks(): List 12 | 13 | fun getFavouriteBooksLive(): LiveData?> 14 | 15 | suspend fun getBooks(): NetworkResponse> 16 | 17 | suspend fun getBook(id: Int): NetworkResponse 18 | } -------------------------------------------------------------------------------- /library/src/main/java/com/xvermilion/modulesample/library/repository/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.library.repository 2 | 3 | import com.xvermilion.modulesample.library.NetworkResponse 4 | import com.xvermilion.modulesample.library.dto.User 5 | 6 | interface UserRepository { 7 | 8 | suspend fun insertOrUpdateUserInfo(id: Int): NetworkResponse 9 | 10 | } -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | library 3 | 4 | -------------------------------------------------------------------------------- /library/src/test/java/com/xvermilion/modulesample/library/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.library; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /navigation/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /navigation/build.gradle: -------------------------------------------------------------------------------- 1 | apply from:"$rootDir/${GradleTemplates.androidLibrary}" 2 | 3 | dependencies { 4 | implementation Libraries.appCompat 5 | } 6 | -------------------------------------------------------------------------------- /navigation/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /navigation/src/androidTest/java/com/xvermilion/modulesample/navigation/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.navigation; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.xvermilion.modulesample.navigation.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /navigation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /navigation/src/main/java/com/xvermilion/modulesample/navigation/DynamicFeature.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.navigation 2 | 3 | interface DynamicFeature { 4 | val dynamicStart: T? 5 | } -------------------------------------------------------------------------------- /navigation/src/main/java/com/xvermilion/modulesample/navigation/HomeNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.navigation 2 | 3 | import android.content.Intent 4 | import com.xvermilion.modulesample.navigation.loaders.loadIntentOrNull 5 | 6 | object HomeNavigation : DynamicFeature { 7 | 8 | private const val HOME = "com.xvermilion.modulesample.home.HomeActivity" 9 | 10 | override val dynamicStart: Intent? 11 | get() = HOME.loadIntentOrNull() 12 | 13 | 14 | } -------------------------------------------------------------------------------- /navigation/src/main/java/com/xvermilion/modulesample/navigation/NavigationActivity.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.navigation 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | 5 | class NavigationActivity : AppCompatActivity() { 6 | 7 | 8 | 9 | } -------------------------------------------------------------------------------- /navigation/src/main/java/com/xvermilion/modulesample/navigation/loaders/FragmentLoader.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.navigation.loaders 2 | 3 | import androidx.fragment.app.Fragment 4 | 5 | internal fun String.loadFragmentOrNull(): Fragment? = 6 | try { 7 | this.loadClassOrNull()?.newInstance() 8 | } catch (e: ClassNotFoundException) { 9 | null 10 | } 11 | -------------------------------------------------------------------------------- /navigation/src/main/java/com/xvermilion/modulesample/navigation/loaders/GenericLoader.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.navigation.loaders 2 | 3 | private val classMap = mutableMapOf>() 4 | 5 | private inline fun Any.castOrNull() = this as? T 6 | 7 | internal fun String.loadClassOrNull(): Class? = 8 | classMap.getOrPut(this) { 9 | try { 10 | Class.forName(this) 11 | } catch (e: ClassNotFoundException) { 12 | return null 13 | } 14 | }.castOrNull() 15 | -------------------------------------------------------------------------------- /navigation/src/main/java/com/xvermilion/modulesample/navigation/loaders/IntentLoader.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.navigation.loaders 2 | 3 | import android.content.Intent 4 | 5 | private const val PACKAGE_NAME = "com.xvermilion.modulesample" 6 | 7 | private fun intentTo(className: String): Intent = 8 | Intent(Intent.ACTION_VIEW).setClassName(PACKAGE_NAME, className) 9 | 10 | internal fun String.loadIntentOrNull(): Intent? = 11 | try { 12 | Class.forName(this).run { intentTo(this@loadIntentOrNull) } 13 | } catch (e: ClassNotFoundException) { 14 | null 15 | } 16 | -------------------------------------------------------------------------------- /navigation/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Navigation 3 | 4 | -------------------------------------------------------------------------------- /navigation/src/test/java/com/xvermilion/modulesample/navigation/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.navigation; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /network/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /network/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/${GradleTemplates.androidLibrary}" 2 | 3 | android { 4 | testOptions { 5 | unitTests.includeAndroidResources = true 6 | } 7 | } 8 | 9 | dependencies { 10 | implementation project(Modules.domain) 11 | implementation Libraries.okhttp3 12 | implementation Libraries.okhttp3Interceptor 13 | implementation Libraries.retrofit 14 | implementation Libraries.gson 15 | implementation Libraries.retrofitGsonConverter 16 | 17 | testImplementation Libraries.jUnit 18 | testImplementation Libraries.androidxTest 19 | testImplementation Libraries.truth 20 | testImplementation Libraries.roboElectric 21 | } 22 | -------------------------------------------------------------------------------- /network/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /network/src/androidTest/java/com/xvermilion/modulesample/network/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.xvermilion.modulesample.network.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /network/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /network/src/main/java/com/xvermilion/modulesample/network/CustomException.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network 2 | 3 | import com.xvermilion.modulesample.network.model.BaseResponse 4 | 5 | class CustomException(message: String?, cause: Throwable?) : RuntimeException(message, cause) { 6 | 7 | private val response = BaseResponse() 8 | 9 | val status: Int 10 | get() = response.status 11 | 12 | val error: String? 13 | get() = response.error 14 | 15 | override val message: String? 16 | get() = response.message 17 | 18 | constructor(status: Int, error: String?) : this(null, null) { 19 | response.status = status 20 | response.error = error 21 | } 22 | 23 | constructor( 24 | status: Int, 25 | error: String?, 26 | message: String? 27 | ) : this(message, null) { 28 | response.status = status 29 | response.error = error 30 | response.message = message 31 | } 32 | 33 | constructor( 34 | status: Int, 35 | error: String?, 36 | cause: Throwable? 37 | ) : this(null, cause) { 38 | response.status = status 39 | response.error = error 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /network/src/main/java/com/xvermilion/modulesample/network/ErrorConverter.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network 2 | 3 | import com.xvermilion.modulesample.network.model.BaseResponse 4 | import okhttp3.ResponseBody 5 | import retrofit2.Converter 6 | import java.io.IOException 7 | import java.net.ConnectException 8 | import java.net.UnknownHostException 9 | 10 | class ErrorConverter(private val converter: Converter) { 11 | 12 | fun getErrorBody(body: ResponseBody, ret: T): T { 13 | return try { 14 | val br = converter.convert(body) as BaseResponse 15 | ret.message = br.message 16 | if ("Service Unavailable. Please try after sometime" == br.message) { 17 | ret.status = 503 18 | ret.message = "Service unavailable" 19 | } else { 20 | ret.status = br.status 21 | ret.error = br.error 22 | } 23 | ret 24 | } catch (e: IOException) { 25 | getErrorBody(e, ret) 26 | } 27 | 28 | } 29 | 30 | fun getErrorBody(e: Throwable, ret: T): T { 31 | when (e) { 32 | is ConnectException -> { 33 | ret.status = 500 34 | ret.error = "-2" 35 | ret.message = "Connection Error" 36 | return ret 37 | } 38 | is CustomException -> { 39 | ret.status = e.status 40 | ret.error = e.error 41 | ret.message = e.message 42 | return ret 43 | } 44 | else -> { 45 | ret.status = -1 46 | ret.error = e.message 47 | return ret 48 | } 49 | } 50 | } 51 | 52 | fun getError(body: ResponseBody): Throwable { 53 | try { 54 | val error = converter.convert(body) 55 | if ("Service Unavailable. Please try after sometime" == error?.message) { 56 | error.status = 503 57 | error.message = "Service unavailable" 58 | } 59 | if (error?.status == 0) 60 | error.status = 500 61 | return CustomException(error?.status!!, error.error, error.message) 62 | } catch (e: IOException) { 63 | e.printStackTrace() 64 | } 65 | 66 | return Throwable("Unexpected Error") 67 | } 68 | 69 | fun getCustomError(e: Throwable): CustomException { 70 | return if (e is ConnectException || e is UnknownHostException) { 71 | CustomException(500, "-2", "Connection error") 72 | } else if (e is CustomException) { 73 | e as CustomException 74 | } else { 75 | CustomException(500, "-1", e.message) 76 | } 77 | } 78 | 79 | fun getError(status: Int, body: ResponseBody): Throwable { 80 | try { 81 | val error = converter.convert(body) 82 | if ("Service Unavailable. Please try after sometime" == error?.message) { 83 | error.message = "Service unavailable" 84 | } 85 | error?.status = status 86 | return CustomException(error?.status!!, error.error, error.message) 87 | } catch (e: IOException) { 88 | e.printStackTrace() 89 | } 90 | 91 | return Throwable("Unexpected Error") 92 | } 93 | 94 | } -------------------------------------------------------------------------------- /network/src/main/java/com/xvermilion/modulesample/network/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network 2 | 3 | import com.xvermilion.modulesample.library.NetworkResponse 4 | import com.xvermilion.modulesample.network.model.BaseResponse 5 | import retrofit2.Response 6 | 7 | fun Response.ifSuccessful(action: (T) -> Unit): Response { 8 | if (isSuccessful || (body() is BaseResponse && (body() as BaseResponse).isSuccessful())) 9 | body()?.let { action(it) } 10 | return this 11 | } 12 | 13 | fun Response.handleResponse( 14 | errorConverter: ErrorConverter, 15 | networkResponse: NetworkResponse<*>, 16 | action: (T) -> Unit 17 | ): Response { 18 | if (isSuccessful || (body() is BaseResponse && (body() as BaseResponse).isSuccessful())) 19 | body()?.let { action(it) } 20 | else { 21 | errorBody()?.let { 22 | val e = errorConverter.getErrorBody(it, BaseResponse()) 23 | networkResponse.failed(e.status, e.error, e.message) 24 | } 25 | } 26 | return this 27 | } 28 | 29 | fun Response.ifFailed( 30 | errorConverter: ErrorConverter? = null, 31 | action: (BaseResponse?) -> Unit 32 | ): Response { 33 | if (!isSuccessful || (body() is BaseResponse && !(body() as BaseResponse).isSuccessful())) 34 | errorBody()?.let { action(errorConverter?.getErrorBody(it, BaseResponse())) } 35 | return this 36 | } 37 | 38 | fun Response.ifFailedCaptureError( 39 | errorConverter: ErrorConverter, 40 | networkResponse: NetworkResponse<*> 41 | ): Response { 42 | if (!isSuccessful || (body() is BaseResponse && !(body() as BaseResponse).isSuccessful())) 43 | errorBody()?.let { 44 | val e = errorConverter.getErrorBody(it, BaseResponse()) 45 | networkResponse.failed(e.status, e.error, e.message) 46 | } 47 | return this 48 | } -------------------------------------------------------------------------------- /network/src/main/java/com/xvermilion/modulesample/network/NetworkModules.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network 2 | 3 | import com.xvermilion.modulesample.domain.ports.network.AuthorNetworkPort 4 | import com.xvermilion.modulesample.domain.ports.network.BookNetworkPort 5 | import com.xvermilion.modulesample.domain.ports.network.UserNetworkPort 6 | import com.xvermilion.modulesample.network.adapter.AuthorNetworkAdapter 7 | import com.xvermilion.modulesample.network.adapter.BookNetworkAdapter 8 | import com.xvermilion.modulesample.network.adapter.UserNetworkAdapter 9 | import com.xvermilion.modulesample.network.model.BaseResponse 10 | import com.xvermilion.modulesample.network.webservice.AuthorWebService 11 | import com.xvermilion.modulesample.network.webservice.BookWebService 12 | import com.xvermilion.modulesample.network.webservice.UserWebService 13 | import okhttp3.CertificatePinner 14 | import okhttp3.Interceptor 15 | import okhttp3.OkHttpClient 16 | import okhttp3.logging.HttpLoggingInterceptor 17 | import org.koin.core.module.Module 18 | import org.koin.dsl.module 19 | import retrofit2.Retrofit 20 | import retrofit2.converter.gson.GsonConverterFactory 21 | import java.net.Proxy 22 | import java.util.concurrent.TimeUnit 23 | 24 | object NetworkModules { 25 | 26 | private const val READ_TIMEOUT = 5L 27 | private const val CONNECTION_TIMEOUT = 5L 28 | private val TIME_UNIT = TimeUnit.MINUTES 29 | 30 | private val retrofitModule = module { 31 | single { 32 | buildRetrofitClient( 33 | buildOkHttpClient(), 34 | "https://fakerestapi.azurewebsites.net/api/" 35 | ) 36 | } 37 | } 38 | 39 | private val webServiceModule = module { 40 | single { get().create(AuthorWebService::class.java) } 41 | single { get().create(BookWebService::class.java) } 42 | single { get().create(UserWebService::class.java) } 43 | } 44 | 45 | private val portModule = module { 46 | single { AuthorNetworkAdapter(get(), get()) } 47 | single { BookNetworkAdapter(get(), get()) } 48 | single { UserNetworkAdapter(get(), get()) } 49 | } 50 | 51 | private val errorConverterModule = module { 52 | single { 53 | ErrorConverter( 54 | get().responseBodyConverter( 55 | BaseResponse::class.java, 56 | arrayOfNulls(0) 57 | ) 58 | ) 59 | } 60 | } 61 | 62 | private fun buildOkHttpClient( 63 | proxy: Proxy? = Proxy.NO_PROXY, 64 | pinner: CertificatePinner? = null, 65 | interceptor: Interceptor? = null 66 | ): OkHttpClient { 67 | val client = OkHttpClient.Builder() 68 | .proxy(proxy) 69 | .readTimeout(READ_TIMEOUT, TIME_UNIT) 70 | .connectTimeout(CONNECTION_TIMEOUT, TIME_UNIT) 71 | 72 | pinner?.let { client.certificatePinner(it) } 73 | interceptor?.let { client.addInterceptor(it) } 74 | 75 | if (BuildConfig.DEBUG) 76 | client.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) 77 | .proxy(null) 78 | 79 | return client.build() 80 | } 81 | 82 | private fun buildRetrofitClient(okHttpClient: OkHttpClient, url: String): Retrofit { 83 | return Retrofit.Builder() 84 | .baseUrl(url) 85 | .addConverterFactory(GsonConverterFactory.create()) 86 | .client(okHttpClient) 87 | .build() 88 | } 89 | 90 | val modules: List = listOf( 91 | retrofitModule, webServiceModule, errorConverterModule, portModule 92 | ) 93 | 94 | } -------------------------------------------------------------------------------- /network/src/main/java/com/xvermilion/modulesample/network/adapter/AuthorNetworkAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network.adapter 2 | 3 | import com.xvermilion.modulesample.domain.ports.network.AuthorNetworkPort 4 | import com.xvermilion.modulesample.library.dto.Author 5 | import com.xvermilion.modulesample.library.response 6 | import com.xvermilion.modulesample.network.ErrorConverter 7 | import com.xvermilion.modulesample.network.handleResponse 8 | import com.xvermilion.modulesample.network.webservice.AuthorWebService 9 | 10 | internal class AuthorNetworkAdapter( 11 | private val webService: AuthorWebService, 12 | private val errorConverter: ErrorConverter 13 | ) : AuthorNetworkPort { 14 | 15 | override suspend fun getAuthors() = response> { 16 | webService.getAuthors().handleResponse(errorConverter, this) { succeeded(it) } 17 | } 18 | 19 | override suspend fun getAuthor(id: Int) = response { 20 | webService.getAuthors(id).handleResponse(errorConverter, this) { succeeded(it) } 21 | } 22 | } -------------------------------------------------------------------------------- /network/src/main/java/com/xvermilion/modulesample/network/adapter/BookNetworkAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network.adapter 2 | 3 | import com.xvermilion.modulesample.domain.ports.network.BookNetworkPort 4 | import com.xvermilion.modulesample.library.dto.Book 5 | import com.xvermilion.modulesample.library.dto.Cover 6 | import com.xvermilion.modulesample.library.response 7 | import com.xvermilion.modulesample.network.ErrorConverter 8 | import com.xvermilion.modulesample.network.handleResponse 9 | import com.xvermilion.modulesample.network.webservice.BookWebService 10 | 11 | internal class BookNetworkAdapter( 12 | private val webService: BookWebService, 13 | private val errorConverter: ErrorConverter 14 | ) : BookNetworkPort { 15 | 16 | override suspend fun getBooks() = response> { 17 | webService.getBooks().handleResponse(errorConverter, this) { succeeded(it) } 18 | } 19 | 20 | override suspend fun getBook(id: Int) = response { 21 | webService.getBook(id).handleResponse(errorConverter, this) { succeeded(it) } 22 | } 23 | 24 | override suspend fun getCoverPhoto(bookId: Int) = response { 25 | webService.getCoverPhoto(bookId).handleResponse(errorConverter, this) { succeeded(it) } 26 | } 27 | } -------------------------------------------------------------------------------- /network/src/main/java/com/xvermilion/modulesample/network/adapter/UserNetworkAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network.adapter 2 | 3 | import com.xvermilion.modulesample.domain.ports.network.UserNetworkPort 4 | import com.xvermilion.modulesample.library.dto.User 5 | import com.xvermilion.modulesample.library.response 6 | import com.xvermilion.modulesample.network.ErrorConverter 7 | import com.xvermilion.modulesample.network.handleResponse 8 | import com.xvermilion.modulesample.network.webservice.UserWebService 9 | 10 | internal class UserNetworkAdapter( 11 | private val webservice: UserWebService, 12 | private val errorConverter: ErrorConverter 13 | ) : UserNetworkPort { 14 | 15 | override suspend fun getUserInfo(id: Int) = response { 16 | webservice.getUserInfo(id).handleResponse(errorConverter, this) { succeeded(it) } 17 | } 18 | } -------------------------------------------------------------------------------- /network/src/main/java/com/xvermilion/modulesample/network/model/AuthorResponse.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.xvermilion.modulesample.library.dto.Author 5 | 6 | data class AuthorResponse( 7 | @SerializedName("ID") 8 | override val id: Int, 9 | @SerializedName("FirstName") 10 | override val firstName: String?, 11 | @SerializedName("LastName") 12 | override val lastName: String? 13 | ) : Author -------------------------------------------------------------------------------- /network/src/main/java/com/xvermilion/modulesample/network/model/BaseRequest.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network.model 2 | 3 | open class BaseRequest(protected var cif: String? = null) -------------------------------------------------------------------------------- /network/src/main/java/com/xvermilion/modulesample/network/model/BaseResponse.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network.model 2 | 3 | open class BaseResponse(var status: Int = 200, error: String? = null, var message: String? = null) { 4 | 5 | var error: String? = error 6 | set(value) { 7 | field = value 8 | error?.let { 9 | if (it.isNotEmpty() && status == -1) 10 | status = 500 11 | } 12 | } 13 | 14 | open fun isSuccessful() = status == 200 || status == 0 15 | 16 | override fun toString() = "BaseResponse{status=$status, message=$message, error=$error}" 17 | } -------------------------------------------------------------------------------- /network/src/main/java/com/xvermilion/modulesample/network/model/BookResponse.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.xvermilion.modulesample.library.dto.Book 5 | 6 | data class BookResponse( 7 | @SerializedName("ID") 8 | override val id: Int, 9 | @SerializedName("Title") 10 | override val title: String?, 11 | @SerializedName("Description") 12 | override val description: String?, 13 | @SerializedName("PublishDate") 14 | override val publishDate: String?, 15 | @SerializedName("PageCount") 16 | override val pages: Int, 17 | override var cover: String? = null 18 | ) : Book -------------------------------------------------------------------------------- /network/src/main/java/com/xvermilion/modulesample/network/model/CoverPhotoResponse.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.xvermilion.modulesample.library.dto.Cover 5 | 6 | data class CoverPhotoResponse( 7 | @SerializedName("ID") 8 | override val id: Int, 9 | @SerializedName("IDBook") 10 | override val bookId: Int, 11 | @SerializedName("Url") 12 | override val url: String? 13 | ) : Cover -------------------------------------------------------------------------------- /network/src/main/java/com/xvermilion/modulesample/network/model/UserResponse.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.xvermilion.modulesample.library.dto.User 5 | 6 | data class UserResponse( 7 | @SerializedName("ID") 8 | override val id: Int, 9 | @SerializedName("UserName") 10 | override var username: String?, 11 | @SerializedName("PassWord") 12 | override var password: String? 13 | ) : User -------------------------------------------------------------------------------- /network/src/main/java/com/xvermilion/modulesample/network/webservice/AuthorWebService.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network.webservice 2 | 3 | import com.xvermilion.modulesample.network.model.AuthorResponse 4 | import retrofit2.Response 5 | import retrofit2.http.GET 6 | import retrofit2.http.Path 7 | 8 | interface AuthorWebService { 9 | 10 | @GET("Authors") 11 | suspend fun getAuthors(): Response> 12 | 13 | @GET("Authors/{id}") 14 | suspend fun getAuthors(@Path("id") id: Int): Response 15 | 16 | } -------------------------------------------------------------------------------- /network/src/main/java/com/xvermilion/modulesample/network/webservice/BookWebService.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network.webservice 2 | 3 | import com.xvermilion.modulesample.network.model.BookResponse 4 | import com.xvermilion.modulesample.network.model.CoverPhotoResponse 5 | import retrofit2.Response 6 | import retrofit2.http.GET 7 | import retrofit2.http.Path 8 | 9 | interface BookWebService { 10 | 11 | @GET("Books") 12 | suspend fun getBooks(): Response> 13 | 14 | @GET("Books/{id}") 15 | suspend fun getBook(@Path("id") id: Int): Response 16 | 17 | @GET("books/covers/{bookId}") 18 | suspend fun getCoverPhoto(@Path("bookId") bookId: Int): Response 19 | 20 | } -------------------------------------------------------------------------------- /network/src/main/java/com/xvermilion/modulesample/network/webservice/UserWebService.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network.webservice 2 | 3 | import com.xvermilion.modulesample.network.model.UserResponse 4 | import retrofit2.Response 5 | import retrofit2.http.GET 6 | import retrofit2.http.Path 7 | 8 | interface UserWebService { 9 | 10 | @GET("Users/{id}") 11 | suspend fun getUserInfo(@Path("id") id: Int): Response 12 | 13 | } -------------------------------------------------------------------------------- /network/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Network 3 | 4 | -------------------------------------------------------------------------------- /network/src/test/java/com/xvermilion/modulesample/network/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.network; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /persistence/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /persistence/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/${GradleTemplates.androidLibrary}" 2 | 3 | dependencies { 4 | implementation project(Modules.domain) 5 | implementation Libraries.room 6 | implementation Libraries.roomKTX 7 | implementation Libraries.androidxCore 8 | implementation(Libraries.androidxAnnotation) { force = true } 9 | implementation(Libraries.androidxLegacy) { force = true } 10 | 11 | implementation Libraries.coroutines 12 | implementation Libraries.coroutinesAndroid 13 | 14 | kapt Libraries.roomCompiler 15 | } 16 | -------------------------------------------------------------------------------- /persistence/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /persistence/src/androidTest/java/com/xvermilion/modulesample/persistence/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.persistence; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.xvermilion.modulesample.persistence.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /persistence/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /persistence/src/main/java/com/xvermilion/modulesample/persistence/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.persistence 2 | 3 | import android.content.Context 4 | import androidx.room.RoomDatabase 5 | import androidx.sqlite.db.SupportSQLiteDatabase 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.GlobalScope 8 | import kotlinx.coroutines.launch 9 | import java.io.BufferedReader 10 | import java.io.IOException 11 | import java.io.InputStreamReader 12 | 13 | fun RoomDatabase.runInTransactionAsync(body: suspend () -> Unit) { 14 | runInTransaction { 15 | GlobalScope.launch(Dispatchers.IO) { 16 | body() 17 | } 18 | } 19 | } 20 | 21 | fun SupportSQLiteDatabase.execFromRaw(context: Context, fileId: Int) { 22 | try { 23 | val input = context.resources.openRawResource(fileId) 24 | val reader = BufferedReader(InputStreamReader(input)) 25 | val sqlScript = StringBuilder() 26 | 27 | while (reader.ready()) 28 | sqlScript.append(reader.readLine()) 29 | val s = sqlScript.toString() 30 | execSQL(s) 31 | } catch (e: IOException) { 32 | e.printStackTrace() 33 | } 34 | } -------------------------------------------------------------------------------- /persistence/src/main/java/com/xvermilion/modulesample/persistence/MyDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.persistence 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import androidx.room.TypeConverters 6 | import com.xvermilion.modulesample.persistence.converter.DateConverter 7 | import com.xvermilion.modulesample.persistence.dao.UserDao 8 | import com.xvermilion.modulesample.persistence.entites.BookEntity 9 | import com.xvermilion.modulesample.persistence.entites.UserEntity 10 | 11 | object DatabaseMetaData { 12 | const val NAME = "library.db" 13 | const val VERSION = 1 14 | } 15 | 16 | @TypeConverters(DateConverter::class) 17 | @Database(entities = [UserEntity::class, BookEntity::class], version = DatabaseMetaData.VERSION) 18 | abstract class MyDatabase : RoomDatabase() { 19 | 20 | abstract fun userDao(): UserDao 21 | 22 | } -------------------------------------------------------------------------------- /persistence/src/main/java/com/xvermilion/modulesample/persistence/PersistenceModules.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.persistence 2 | 3 | import android.app.Application 4 | import androidx.room.Room 5 | import com.xvermilion.modulesample.domain.ports.persistence.BookDaoPort 6 | import com.xvermilion.modulesample.domain.ports.persistence.PreferenceStorage 7 | import com.xvermilion.modulesample.domain.ports.persistence.UserDaoPort 8 | import com.xvermilion.modulesample.persistence.DatabaseMetaData.NAME 9 | import com.xvermilion.modulesample.persistence.adapters.BookDaoAdapter 10 | import com.xvermilion.modulesample.persistence.adapters.UserDaoAdapter 11 | import com.xvermilion.modulesample.persistence.preferences.SharedPreferenceStorage 12 | import org.koin.android.ext.koin.androidContext 13 | import org.koin.core.module.Module 14 | import org.koin.dsl.module 15 | 16 | object PersistenceModules { 17 | 18 | private val databaseModule = module { 19 | single { 20 | val app: Application = get() 21 | Room.databaseBuilder(app, MyDatabase::class.java, NAME).build() 22 | } 23 | } 24 | 25 | private val daoModules = module { 26 | single { get().userDao() } 27 | } 28 | 29 | private val portsModule = module { 30 | single { UserDaoAdapter(get()) } 31 | single { BookDaoAdapter(get()) } 32 | single { SharedPreferenceStorage(androidContext()) } 33 | } 34 | 35 | val modules: List = listOf(databaseModule, daoModules, portsModule) 36 | 37 | } -------------------------------------------------------------------------------- /persistence/src/main/java/com/xvermilion/modulesample/persistence/adapters/BookDaoAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.persistence.adapters 2 | 3 | import com.xvermilion.modulesample.domain.ports.persistence.BookDaoPort 4 | import com.xvermilion.modulesample.library.dto.Book 5 | import com.xvermilion.modulesample.persistence.dao.BookDao 6 | import com.xvermilion.modulesample.persistence.entites.BookEntity 7 | 8 | internal class BookDaoAdapter(private val dao: BookDao) : BookDaoPort { 9 | 10 | override suspend fun insertOrReplace(book: Book) { 11 | if (book is BookEntity) 12 | dao.insertOrReplace(book) 13 | else 14 | dao.insertOrReplace( 15 | BookEntity( 16 | book.id, 17 | book.title, 18 | book.description, 19 | book.publishDate, 20 | book.pages 21 | ) 22 | ) 23 | } 24 | 25 | override suspend fun getBooks() = dao.getBooks() 26 | 27 | override fun getBooksLive() = dao.getBooksLive() 28 | } -------------------------------------------------------------------------------- /persistence/src/main/java/com/xvermilion/modulesample/persistence/adapters/UserDaoAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.persistence.adapters 2 | 3 | import com.xvermilion.modulesample.domain.ports.persistence.UserDaoPort 4 | import com.xvermilion.modulesample.library.dto.User 5 | import com.xvermilion.modulesample.persistence.dao.UserDao 6 | import com.xvermilion.modulesample.persistence.entites.UserEntity 7 | 8 | data class UserDaoAdapter( 9 | private val userDao: UserDao 10 | ) : UserDaoPort { 11 | 12 | override suspend fun insertOrUpdate(user: User) { 13 | if (user is UserEntity) 14 | userDao.insertOrReplace(user) 15 | else 16 | userDao.insertOrReplace(UserEntity(user.id, user.username, user.password)) 17 | } 18 | 19 | override suspend fun update(user: User) { 20 | if (user is UserEntity) 21 | userDao.update(user) 22 | else 23 | userDao.update(UserEntity(user.id, user.username, user.password)) 24 | } 25 | 26 | override suspend fun getUser() = userDao.getUser() 27 | 28 | override suspend fun getUserLive() = userDao.getUserLive() 29 | 30 | override suspend fun countUsers() = userDao.countUsers() 31 | 32 | override suspend fun deleteAll() = userDao.deleteAll() 33 | 34 | override suspend fun deleteUser() { 35 | getUser()?.let { userDao.delete(it) } 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /persistence/src/main/java/com/xvermilion/modulesample/persistence/converter/DateConverter.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.persistence.converter 2 | 3 | import androidx.room.TypeConverter 4 | import java.util.* 5 | 6 | object DateConverter { 7 | 8 | @JvmStatic 9 | @TypeConverter 10 | fun timeStampToDate(value: Long?) = value?.let { Date(it) } 11 | 12 | @JvmStatic 13 | @TypeConverter 14 | fun dateToTimeStamp(value: Date?) = value?.time 15 | 16 | } -------------------------------------------------------------------------------- /persistence/src/main/java/com/xvermilion/modulesample/persistence/dao/BaseDao.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.persistence.dao 2 | 3 | import androidx.room.Delete 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Update 7 | 8 | interface BaseDao { 9 | 10 | @Insert 11 | suspend fun insert(item: T): Long 12 | 13 | @Insert(onConflict = OnConflictStrategy.REPLACE) 14 | suspend fun insertOrReplace(item: T): Long 15 | 16 | @Insert(onConflict = OnConflictStrategy.IGNORE) 17 | suspend fun insertOrIgnore(item: T): Long 18 | 19 | @Insert 20 | suspend fun insertAll(items: List): List 21 | 22 | @Insert(onConflict = OnConflictStrategy.REPLACE) 23 | suspend fun insertAllOrReplace(items: List): List 24 | 25 | @Insert(onConflict = OnConflictStrategy.IGNORE) 26 | suspend fun insertAllOrIgnore(items: List): List 27 | 28 | @Update 29 | suspend fun update(item: T): Int 30 | 31 | @Update(onConflict = OnConflictStrategy.REPLACE) 32 | suspend fun updateOrReplace(item: T): Int 33 | 34 | @Update(onConflict = OnConflictStrategy.IGNORE) 35 | suspend fun updateOrIgnore(item: T): Int 36 | 37 | @Update 38 | suspend fun updateAll(items: List): Int 39 | 40 | @Update(onConflict = OnConflictStrategy.REPLACE) 41 | suspend fun updateAllOrReplace(items: List): Int 42 | 43 | @Update(onConflict = OnConflictStrategy.IGNORE) 44 | suspend fun updateAllOrIgnore(items: List): Int 45 | 46 | @Delete 47 | suspend fun delete(item: T): Int 48 | 49 | @Delete 50 | suspend fun deleteAll(items: List): Int 51 | 52 | } -------------------------------------------------------------------------------- /persistence/src/main/java/com/xvermilion/modulesample/persistence/dao/BookDao.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.persistence.dao 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Query 6 | import com.xvermilion.modulesample.library.dto.Book 7 | import com.xvermilion.modulesample.persistence.entites.BookEntity 8 | 9 | @Dao 10 | interface BookDao : BaseDao { 11 | 12 | @Query("SELECT * FROM Book") 13 | suspend fun getBooks(): List 14 | 15 | @Query("SELECT * FROM Book") 16 | fun getBooksLive(): LiveData?> 17 | 18 | } -------------------------------------------------------------------------------- /persistence/src/main/java/com/xvermilion/modulesample/persistence/dao/UserDao.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.persistence.dao 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Query 6 | import com.xvermilion.modulesample.persistence.entites.UserEntity 7 | 8 | @Dao 9 | interface UserDao : BaseDao { 10 | 11 | @Query("SELECT * FROM User LIMIT 1") 12 | suspend fun getUser(): UserEntity? 13 | 14 | @Query("SELECT * FROM User LIMIT 1") 15 | fun getUserLive(): LiveData 16 | 17 | @Query("SELECT COUNT(*) FROM User") 18 | suspend fun countUsers(): Int 19 | 20 | @Query("DELETE FROM User") 21 | suspend fun deleteAll() 22 | 23 | } -------------------------------------------------------------------------------- /persistence/src/main/java/com/xvermilion/modulesample/persistence/entites/BookEntity.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.persistence.entites 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.xvermilion.modulesample.library.dto.Book 6 | 7 | @Entity(tableName = "Book") 8 | data class BookEntity( 9 | @PrimaryKey 10 | override val id: Int, 11 | override val title: String?, 12 | override val description: String?, 13 | override val publishDate: String?, 14 | override val pages: Int, 15 | override var cover: String? = null 16 | ) : Book -------------------------------------------------------------------------------- /persistence/src/main/java/com/xvermilion/modulesample/persistence/entites/UserEntity.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.persistence.entites 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.xvermilion.modulesample.library.dto.User 6 | 7 | @Entity(tableName = "User") 8 | data class UserEntity( 9 | @PrimaryKey 10 | override val id: Int, 11 | override var username: String?, 12 | override var password: String? 13 | ) : User -------------------------------------------------------------------------------- /persistence/src/main/java/com/xvermilion/modulesample/persistence/preferences/SharedPreferenceStorage.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.persistence.preferences 2 | 3 | import android.content.Context 4 | import com.xvermilion.modulesample.domain.ports.persistence.PreferenceStorage 5 | 6 | class SharedPreferenceStorage(context: Context) : PreferenceStorage { 7 | 8 | companion object { 9 | private const val PREFERENCES = "MySharedPreferences" 10 | private const val TEST = "Test" 11 | } 12 | 13 | private val prefs = context.applicationContext 14 | .getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE) 15 | 16 | override var test: Boolean by BooleanPreference(prefs, TEST, false) 17 | 18 | } -------------------------------------------------------------------------------- /persistence/src/main/java/com/xvermilion/modulesample/persistence/preferences/TypePreferences.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.persistence.preferences 2 | 3 | import android.content.SharedPreferences 4 | import androidx.annotation.WorkerThread 5 | import androidx.core.content.edit 6 | import kotlin.properties.ReadWriteProperty 7 | import kotlin.reflect.KProperty 8 | 9 | class BooleanPreference( 10 | private val preferences: SharedPreferences, 11 | private val name: String, 12 | private val defaultValue: Boolean 13 | ) : ReadWriteProperty { 14 | 15 | @WorkerThread 16 | override fun getValue(thisRef: Any, property: KProperty<*>): Boolean = 17 | preferences.getBoolean(name, defaultValue) 18 | 19 | override fun setValue(thisRef: Any, property: KProperty<*>, value: Boolean) = 20 | preferences.edit(true) { putBoolean(name, value) } 21 | 22 | } 23 | 24 | class LongPreference( 25 | private val preferences: SharedPreferences, 26 | private val name: String, 27 | private val defaultValue: Long 28 | ) : ReadWriteProperty { 29 | 30 | @WorkerThread 31 | override fun getValue(thisRef: Any, property: KProperty<*>): Long = 32 | preferences.getLong(name, defaultValue) 33 | 34 | override fun setValue(thisRef: Any, property: KProperty<*>, value: Long) = 35 | preferences.edit(true) { putLong(name, value) } 36 | 37 | } 38 | 39 | class StringPreference( 40 | private val preferences: SharedPreferences, 41 | private val name: String, 42 | private val defaultValue: String? 43 | ) : ReadWriteProperty { 44 | 45 | @WorkerThread 46 | override fun getValue(thisRef: Any, property: KProperty<*>): String? = 47 | preferences.getString(name, defaultValue) 48 | 49 | override fun setValue(thisRef: Any, property: KProperty<*>, value: String?) = 50 | preferences.edit(true) { putString(name, value) } 51 | } 52 | 53 | class StringSetPreference( 54 | private val preferences: SharedPreferences, 55 | private val name: String, 56 | private val defaultValue: MutableSet? 57 | ) : ReadWriteProperty?> { 58 | 59 | @WorkerThread 60 | override fun getValue(thisRef: Any, property: KProperty<*>): MutableSet? = 61 | preferences.getStringSet(name, defaultValue) 62 | 63 | override fun setValue(thisRef: Any, property: KProperty<*>, value: MutableSet?) = 64 | preferences.edit(true) { putStringSet(name, value) } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /persistence/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Persistence 3 | 4 | -------------------------------------------------------------------------------- /persistence/src/test/java/com/xvermilion/modulesample/persistence/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.persistence; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include Modules.app, 2 | Modules.library, 3 | Modules.domain, 4 | Modules.navigation, 5 | Modules.network, 6 | Modules.persistence, 7 | Modules.shared, 8 | Modules.home 9 | -------------------------------------------------------------------------------- /template-android-library.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion AppMetaData.compileSdkVersion 8 | 9 | defaultConfig { 10 | minSdkVersion AppMetaData.minSdkVersion 11 | targetSdkVersion AppMetaData.targetSdkVersion 12 | versionCode AppMetaData.versionCode 13 | versionName AppMetaData.versionName 14 | } 15 | 16 | buildTypes { 17 | release { 18 | minifyEnabled true 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation Libraries.kotlin 25 | implementation Libraries.koinXcore 26 | implementation Libraries.koinXscope 27 | implementation Libraries.koinXviewModel 28 | 29 | implementation Libraries.coroutines 30 | implementation Libraries.coroutinesAndroid 31 | 32 | testImplementation Libraries.jUnit 33 | testImplementation Libraries.koinTest 34 | androidTestImplementation Libraries.testRunner 35 | androidTestImplementation Libraries.testEspresso 36 | } 37 | -------------------------------------------------------------------------------- /template-dynamic-feature.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.dynamic-feature' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion AppMetaData.compileSdkVersion 8 | 9 | defaultConfig { 10 | minSdkVersion AppMetaData.minSdkVersion 11 | targetSdkVersion AppMetaData.targetSdkVersion 12 | } 13 | } 14 | 15 | dependencies { 16 | implementation fileTree(dir: 'libs', include: ['*.jar']) 17 | implementation project(Modules.app) 18 | implementation project(Modules.library) 19 | implementation project(Modules.shared) 20 | implementation Libraries.appCompat 21 | implementation Libraries.androidxCore 22 | implementation Libraries.constraintLayout 23 | 24 | implementation Libraries.kotlin 25 | implementation Libraries.koinXcore 26 | implementation Libraries.koinXscope 27 | implementation Libraries.koinXviewModel 28 | 29 | implementation Libraries.coroutines 30 | implementation Libraries.coroutinesAndroid 31 | 32 | testImplementation Libraries.jUnit 33 | testImplementation Libraries.koinTest 34 | androidTestImplementation Libraries.testRunner 35 | androidTestImplementation Libraries.testEspresso 36 | } 37 | -------------------------------------------------------------------------------- /template-kotlin-library.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'kotlin' 2 | apply plugin: 'kotlin-kapt' 3 | 4 | dependencies { 5 | implementation Libraries.kotlin 6 | implementation Libraries.koinXcore 7 | implementation Libraries.koinXscope 8 | implementation Libraries.koinXviewModel 9 | 10 | implementation Libraries.coroutines 11 | 12 | testImplementation Libraries.jUnit 13 | testImplementation Libraries.koinTest 14 | androidTestImplementation Libraries.testRunner 15 | androidTestImplementation Libraries.testEspresso 16 | } 17 | -------------------------------------------------------------------------------- /ui_home/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ui_home/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/${GradleTemplates.dynamicFeature}" 2 | 3 | dependencies { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /ui_home/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /ui_home/src/main/java/com/xvermilion/modulesample/home/HomeActivity.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.home 2 | 3 | import android.os.Bundle 4 | import com.xvermilion.modulesample.shared.EntryActivity 5 | import kotlinx.android.synthetic.main.activity_home.* 6 | import org.koin.android.ext.android.inject 7 | 8 | class HomeActivity : EntryActivity() { 9 | 10 | private val viewModel: HomeViewModel by inject() 11 | 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | setContentView(R.layout.activity_home) 15 | init() 16 | } 17 | 18 | private fun init() { 19 | 20 | button.setOnClickListener { 21 | viewModel.fetchRates() 22 | } 23 | } 24 | 25 | override fun loadModules() { 26 | HomeModules.load() 27 | } 28 | } -------------------------------------------------------------------------------- /ui_home/src/main/java/com/xvermilion/modulesample/home/HomeModules.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.home 2 | 3 | import org.koin.androidx.viewmodel.dsl.viewModel 4 | import org.koin.core.context.loadKoinModules 5 | import org.koin.dsl.module 6 | 7 | object HomeModules { 8 | 9 | fun load() = load 10 | 11 | private val load by lazy { 12 | loadKoinModules(viewModelModules) 13 | } 14 | 15 | private val viewModelModules = module { 16 | viewModel { HomeViewModel(get()) } 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /ui_home/src/main/java/com/xvermilion/modulesample/home/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.xvermilion.modulesample.home 2 | 3 | import android.content.Context 4 | import androidx.lifecycle.ViewModel 5 | 6 | class HomeViewModel( 7 | private val repository: Context 8 | ) : ViewModel() { 9 | 10 | fun fetchRates() { 11 | 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /ui_home/src/main/res/layout/activity_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 |