├── .gitignore ├── .idea ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── appmodularizationexample │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── appmodularizationexample │ │ │ ├── App.kt │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.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 │ └── example │ └── appmodularizationexample │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ ├── Dependances.kt │ ├── GradleExtensions.kt │ └── base_plugin │ ├── BaseGradlePlugin.kt │ └── PluginExtensions.kt ├── data ├── local │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── local │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── local │ │ │ ├── AppDatabase.kt │ │ │ ├── dao │ │ │ ├── BlogPostDao.kt │ │ │ ├── CategoryDao.kt │ │ │ ├── ProductDao.kt │ │ │ └── UserDao.kt │ │ │ └── di │ │ │ └── LocalModel.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── local │ │ └── ExampleUnitTest.kt ├── model │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── model │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── model │ │ │ ├── BlogPostApi.kt │ │ │ ├── CategoryApi.kt │ │ │ ├── ProductApi.kt │ │ │ └── User.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── model │ │ └── ExampleUnitTest.kt └── remote │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── remote │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── example │ │ ├── RemoteExtensions.kt │ │ └── remote │ │ ├── NetworkBoundResource.kt │ │ ├── data │ │ └── Resource.kt │ │ └── di │ │ ├── RemoteModule.kt │ │ └── Test.kt │ └── test │ └── java │ └── com │ └── example │ └── remote │ └── ExampleUnitTest.kt ├── features ├── auth │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── auth │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── auth │ │ │ │ ├── data │ │ │ │ ├── AuthServices.kt │ │ │ │ └── datasource │ │ │ │ │ ├── AuthRepository.kt │ │ │ │ │ ├── local │ │ │ │ │ ├── AuthLocal.kt │ │ │ │ │ └── AuthLocalImpl.kt │ │ │ │ │ └── remote │ │ │ │ │ ├── AuthRemote.kt │ │ │ │ │ ├── AuthRepositoryImpl.kt │ │ │ │ │ └── AuthRmoteImpl.kt │ │ │ │ ├── di │ │ │ │ └── AuthModule.kt │ │ │ │ ├── domain │ │ │ │ ├── LoginUseCase.kt │ │ │ │ └── RegisterUseCase.kt │ │ │ │ └── presenter │ │ │ │ ├── AuthActivity.kt │ │ │ │ ├── AuthViewModel.kt │ │ │ │ └── fragments │ │ │ │ ├── LoginFragment.kt │ │ │ │ └── RegisterFragment.kt │ │ └── res │ │ │ ├── layout │ │ │ ├── activity_auth.xml │ │ │ ├── fragment_login.xml │ │ │ └── fragment_resgister.xml │ │ │ ├── navigation │ │ │ └── auth_navigation.xml │ │ │ └── values │ │ │ └── strings.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── auth │ │ └── ExampleUnitTest.kt ├── home │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── home │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── home │ │ │ │ ├── Constants.kt │ │ │ │ ├── data │ │ │ │ ├── HomeRepository.kt │ │ │ │ ├── HomeRepositoryImpl.kt │ │ │ │ ├── HomeResponse.kt │ │ │ │ ├── HomeServices.kt │ │ │ │ └── datasource │ │ │ │ │ ├── local │ │ │ │ │ ├── HomeLocalImpl.kt │ │ │ │ │ └── homeLocal.kt │ │ │ │ │ └── remote │ │ │ │ │ ├── HomeRemote.kt │ │ │ │ │ └── HomeRemoteImpl.kt │ │ │ │ ├── di │ │ │ │ └── di.kt │ │ │ │ ├── domain │ │ │ │ └── GetHomeUserCase.kt │ │ │ │ └── presenter │ │ │ │ ├── HomeActivity.kt │ │ │ │ ├── HomeAdapter.kt │ │ │ │ ├── HomeBinding.kt │ │ │ │ ├── HomeInnerAdapter.kt │ │ │ │ └── HomeViewModel.kt │ │ └── res │ │ │ ├── layout │ │ │ ├── activity_home.xml │ │ │ ├── category_item.xml │ │ │ ├── home_adapter_item.xml │ │ │ ├── home_inner_view_item.xml │ │ │ └── product_item.xml │ │ │ └── values │ │ │ └── strings.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── home │ │ └── ExampleUnitTest.kt └── splash │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── splash │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── splash │ │ │ ├── SplashActivity.kt │ │ │ ├── data │ │ │ ├── SplashRepository.kt │ │ │ ├── SplashServices.kt │ │ │ └── datasource │ │ │ │ ├── local │ │ │ │ ├── BlogPostLocal.kt │ │ │ │ └── BlogPostLocalImpl.kt │ │ │ │ └── remote │ │ │ │ ├── BlogPostRemote.kt │ │ │ │ └── BlogPostRemoteImpl.kt │ │ │ ├── di │ │ │ └── di.kt │ │ │ ├── domain │ │ │ └── GetBlogPostsUseCase.kt │ │ │ └── presenter │ │ │ └── SplashViewModel.kt │ └── res │ │ ├── layout │ │ └── activity_splash.xml │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── example │ ├── CoroutinesMainDispatcherRule.kt │ └── splash │ ├── ExampleUnitTest.kt │ ├── data │ └── SplashRepositoryTest.kt │ └── domain │ └── GetBlogPostsUseCaseTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libraries ├── common │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── common │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── common │ │ │ ├── ApiConfigs.kt │ │ │ ├── BaseActivity.kt │ │ │ ├── BaseFragment.kt │ │ │ ├── ErrorMessageHelper.kt │ │ │ ├── MessageType.kt │ │ │ ├── NetworkCodes.kt │ │ │ ├── UiCommunicator.kt │ │ │ └── extensions.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── common │ │ └── ExampleUnitTest.kt └── uicomponents │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── uicomponents │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── uicomponents │ │ │ ├── category │ │ │ └── CategoryView.kt │ │ │ ├── helpers │ │ │ └── UiExtensions.kt │ │ │ └── product │ │ │ └── ProductView.kt │ └── res │ │ ├── anim │ │ ├── fade_in.xml │ │ ├── fade_out.xml │ │ ├── slide_in_left.xml │ │ ├── slide_in_right.xml │ │ ├── slide_out_left.xml │ │ └── slide_out_right.xml │ │ ├── drawable │ │ └── rounded_overlay.xml │ │ ├── font │ │ ├── raleway_bold.ttf │ │ ├── raleway_italic.ttf │ │ ├── raleway_medium.ttf │ │ ├── raleway_regular.ttf │ │ └── raleway_semibold.ttf │ │ ├── layout │ │ ├── category_view.xml │ │ └── product_view.xml │ │ └── values │ │ └── color.xml │ └── test │ └── java │ └── com │ └── example │ └── uicomponents │ └── ExampleUnitTest.kt └── settings.gradle /.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 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | App Modularization example -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | xmlns:android 17 | 18 | ^$ 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | xmlns:.* 28 | 29 | ^$ 30 | 31 | 32 | BY_NAME 33 | 34 |
35 |
36 | 37 | 38 | 39 | .*:id 40 | 41 | http://schemas.android.com/apk/res/android 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | .*:name 51 | 52 | http://schemas.android.com/apk/res/android 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 | name 62 | 63 | ^$ 64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | style 73 | 74 | ^$ 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 | .* 84 | 85 | ^$ 86 | 87 | 88 | BY_NAME 89 | 90 |
91 |
92 | 93 | 94 | 95 | .* 96 | 97 | http://schemas.android.com/apk/res/android 98 | 99 | 100 | ANDROID_ATTRIBUTE_ORDER 101 | 102 |
103 |
104 | 105 | 106 | 107 | .* 108 | 109 | .* 110 | 111 | 112 | BY_NAME 113 | 114 |
115 |
116 |
117 |
118 | 119 | 121 |
122 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 32 | 33 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 1.8 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id(GradlePluginId.ANDROID_APP) 3 | id(GradlePluginId.BASE_GRADLE_PLUGIN) 4 | } 5 | 6 | dependencies { 7 | commonDevelopmentDependencies() 8 | implementation(project(FeaturesDependency.Splash)) 9 | implementation(project(FeaturesDependency.Home)) 10 | implementation(project(FeaturesDependency.Auth)) 11 | implementation(project(ModulesDependency.REMOTE)) 12 | implementation(project(ModulesDependency.LOCAL)) 13 | implementation(project(ModulesDependency.MODEL)) 14 | 15 | 16 | } -------------------------------------------------------------------------------- /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.kts. 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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/appmodularizationexample/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.appmodularizationexample 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.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.getInstrumentation().targetContext 22 | assertEquals("com.example.appmodularizationexample", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/appmodularizationexample/App.kt: -------------------------------------------------------------------------------- 1 | package com.example.appmodularizationexample 2 | 3 | import android.app.Application 4 | import com.example.auth.di.authModule 5 | import com.example.di.localModule 6 | import com.example.remote.di.getRemoteModule 7 | import com.example.home.di.homeModule 8 | import com.example.splash.di.splashModule 9 | import org.koin.android.ext.koin.androidContext 10 | import org.koin.core.context.startKoin 11 | 12 | class App : Application() { 13 | override fun onCreate() { 14 | super.onCreate() 15 | val list = listOf(splashModule,getRemoteModule("http://192.168.1.3/blog/public/api/"), localModule, 16 | homeModule,authModule 17 | ) 18 | startKoin { 19 | modules(list) 20 | androidContext(this@App) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/appmodularizationexample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.appmodularizationexample 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | 6 | 7 | class MainActivity : AppCompatActivity() { 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | setContentView(R.layout.activity_main) 11 | 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /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/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #673AB7 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | App Modularization example 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/example/appmodularizationexample/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.appmodularizationexample 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.kts: -------------------------------------------------------------------------------- 1 | import org.jlleitschuh.gradle.ktlint.reporter.ReporterType 2 | 3 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 4 | plugins { 5 | id(GradlePluginId.KTLINT_GRADLE) version CoreVersion.KTLINT_GRADLE 6 | id(GradlePluginId.DETEKT) version CoreVersion.DETEKT 7 | id(GradlePluginId.KAPT) 8 | } 9 | 10 | buildscript { 11 | repositories { 12 | // Android plugin & support libraries 13 | google() 14 | 15 | // Main open-source repository 16 | jcenter() 17 | 18 | // Ktlint Gradle 19 | maven(GradlePluginId.KTLINT_MAVEN) 20 | } 21 | dependencies { 22 | classpath(GradleClasspath.ANDROID_GRADLE) 23 | classpath(kotlin(GradleClasspath.KOTLIN_PlUGIN, version = CoreVersion.KOTLIN)) 24 | classpath(GradleClasspath.SAFE_ARGS) 25 | classpath(GradleClasspath.KTLINT_CLASSPATH) 26 | } 27 | } 28 | 29 | allprojects { 30 | repositories { 31 | google() 32 | jcenter() 33 | } 34 | 35 | // We want to apply ktlint at all project level because it also checks build gradle files 36 | plugins.apply(GradlePluginId.KTLINT_GRADLE) 37 | // Ktlint configuration for sub-projects 38 | ktlint { 39 | version.set(CoreVersion.KTLINT) 40 | verbose.set(true) 41 | android.set(true) 42 | reporters { 43 | reporter(ReporterType.CHECKSTYLE) 44 | } 45 | ignoreFailures.set(true) 46 | filter { 47 | exclude("**/generated/**") 48 | } 49 | 50 | } 51 | 52 | plugins.apply(GradlePluginId.DETEKT) 53 | 54 | detekt { 55 | config = files("${project.rootDir}/config/detekt.yml") 56 | parallel = true 57 | } 58 | 59 | } 60 | 61 | tasks.register("clean", Delete::class) { 62 | delete(rootProject.buildDir) 63 | } 64 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | kotlin("kapt") version "1.3.21" 4 | } 5 | 6 | //The kotlin-dsl plugin requires a repository to be declared 7 | repositories { 8 | mavenCentral() 9 | google() 10 | jcenter() 11 | } 12 | 13 | gradlePlugin { 14 | plugins { 15 | register("base-gradle-plugin") { 16 | id = "base-gradle-plugin" 17 | implementationClass = "base_plugin.BaseGradlePlugin" 18 | } 19 | } 20 | } 21 | 22 | 23 | dependencies { 24 | /* Depend on the android gradle plugin, since we want to access it in our plugin */ 25 | implementation("com.android.tools.build:gradle:3.5.3") 26 | /* Depend on the kotlin plugin, since we want to access it in our plugin */ 27 | implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61") 28 | } 29 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Dependances.kt: -------------------------------------------------------------------------------- 1 | object AndroidConfig { 2 | 3 | const val COMPILE_SDK_VERSION = 29 4 | const val MIN_SDK_VERSION = 21 5 | const val TARGET_SDK_VERSION = 29 6 | const val VERSION_CODE = 1 7 | const val VERSION_NAME = "1" 8 | 9 | const val ID = "com.example.appmodularizationexample" 10 | const val TEST_INSTRUMENTATION_RUNNER = "androidx.test.runner.AndroidJUnitRunner" 11 | } 12 | 13 | 14 | interface BuildType { 15 | 16 | companion object { 17 | const val RELEASE = "release" 18 | const val DEBUG = "release" 19 | } 20 | 21 | val isMinifyEnabled: Boolean 22 | } 23 | 24 | object BuildTypeDebug : BuildType { 25 | override val isMinifyEnabled = false 26 | } 27 | 28 | object BuildTypeRelease : BuildType { 29 | override val isMinifyEnabled = true 30 | } 31 | 32 | object CoreVersion { 33 | const val KOTLIN = "1.3.70" 34 | const val NAVIGATION = "2.2.0-rc03" 35 | const val ANDROID_GRADLE = "3.5.3" 36 | const val KTLINT_GRADLE = "9.2.1" 37 | const val KTLINT = "0.34.2" 38 | const val DETEKT = "1.0.0" 39 | const val KAPT = "1.3.71" 40 | } 41 | 42 | object GradlePluginId { 43 | const val ANDROID_APP = "com.android.application" 44 | const val ANDROID_LIB = "com.android.library" 45 | const val ANDROID = "kotlin-android" 46 | const val ANDROID_EXT = "kotlin-android-extensions" 47 | const val SAFE_ARGS = "androidx.navigation.safeargs" 48 | const val BASE_GRADLE_PLUGIN = "base-gradle-plugin" 49 | const val KTLINT_GRADLE = "org.jlleitschuh.gradle.ktlint" 50 | const val KTLINT_MAVEN = "https://plugins.gradle.org/m2/" 51 | const val DETEKT = "io.gitlab.arturbosch.detekt" 52 | const val KAPT = "kotlin-kapt" 53 | } 54 | 55 | object GradleClasspath { 56 | const val KOTLIN_PlUGIN = "gradle-plugin" 57 | const val ANDROID_GRADLE = "com.android.tools.build:gradle:${CoreVersion.ANDROID_GRADLE}" 58 | const val SAFE_ARGS = 59 | "androidx.navigation:navigation-safe-args-gradle-plugin:${CoreVersion.NAVIGATION}" 60 | const val KTLINT_CLASSPATH = "org.jlleitschuh.gradle:ktlint-gradle:${CoreVersion.KTLINT_GRADLE}" 61 | } 62 | 63 | 64 | object LibraryDependency { 65 | object Version { 66 | const val SUPPORT_LIB = "1.1.0" 67 | const val CONSTRAINT = "1.1.3" 68 | const val KOIN = "2.0.1" 69 | val kotlin = "1.3.21" 70 | val gradle = "3.3.2" 71 | val compileSdk = 28 72 | val minSdk = 23 73 | val targetSdk = 28 74 | val appCompat = "1.1.0-alpha04" 75 | const val coreKtx = "1.1.0-alpha04" 76 | val constraintLayout = "1.1.3" 77 | val junit = "4.12" 78 | val androidTestRunner = "1.1.2-alpha02" 79 | val espressoCore = "3.2.0-alpha02" 80 | const val retrofit = "2.6.2" 81 | const val retrofitCoroutines = "0.9.2" 82 | const val retrofitGson = "2.6.2" 83 | const val gson = "2.8.5" 84 | const val okHttp = "3.12.1" 85 | val coroutines = "1.1.1" 86 | val koin = "1.0.2" 87 | val timber = "4.7.1" 88 | const val lifecycle = "2.1.0-alpha04" 89 | const val livedata_version = "2.2.0-rc02" 90 | val nav = "2.0.0" 91 | const val room = "2.1.0-alpha06" 92 | const val recyclerview = "1.0.0" 93 | val safeArgs = "2.1.0-alpha01" 94 | val glide = "4.9.0" 95 | val mockwebserver = "2.7.5" 96 | const val archCoreTest = "2.1.0" 97 | val androidJunit = "1.1.0" 98 | const val mockk = "1.9.2" 99 | val fragmentTest = "1.1.0-alpha06" 100 | val databinding = "3.3.2" 101 | const val coroutines_android_version = "1.3.4" 102 | const val COROUTINESTESTING="1.3.2" 103 | const val PICASSO = "2.71828" 104 | const val ROUNDED_IMAGE = "2.3.0" 105 | 106 | } 107 | 108 | const val KOTLIN_STD = "org.jetbrains.kotlin:kotlin-stdlib:${CoreVersion.KOTLIN}" 109 | const val CORE = "androidx.core:core-ktx:${Version.SUPPORT_LIB}" 110 | const val APPCOMPAT = "androidx.appcompat:appcompat:${Version.SUPPORT_LIB}" 111 | const val MATERIAL = "com.google.android.material:material:${Version.SUPPORT_LIB}" 112 | const val CONSTRAINT = "androidx.constraintlayout:constraintlayout:${Version.CONSTRAINT}" 113 | const val NAVIGATION_FRAGMENT = 114 | "androidx.navigation:navigation-fragment-ktx:${CoreVersion.NAVIGATION}" 115 | 116 | const val ROUNDED_IMAGE_VIEW = "com.makeramen:roundedimageview:${Version.ROUNDED_IMAGE}" 117 | const val NAVIGATION_UI = "androidx.navigation:navigation-ui-ktx:${CoreVersion.NAVIGATION}" 118 | const val KOIN = "org.koin:koin-android:${Version.KOIN}" 119 | const val KOIN_VIEWMODEL = "org.koin:koin-androidx-viewmodel:${Version.KOIN}" 120 | const val KOIN_SCOPE = "org.koin:koin-androidx-scope:${Version.KOIN}" 121 | const val PICASSO = "com.squareup.picasso:picasso:${Version.PICASSO}" 122 | const val RECYCLERVIEW = "androidx.recyclerview:recyclerview:${Version.recyclerview}" 123 | 124 | const val COREKTX = "androidx.core:core-ktx:${Version.coreKtx}" 125 | const val LIFECYCLEViewModel = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Version.lifecycle}" 126 | const val LIFECYCLEEXTENSIONS = "androidx.lifecycle:lifecycle-extensions:${Version.lifecycle}" 127 | const val LIVEDATAKTX = "androidx.lifecycle:lifecycle-livedata-ktx:${Version.livedata_version}" 128 | 129 | 130 | //retrofit 131 | const val RETROFITCOROUTINESADAPTER = 132 | "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:${Version.retrofitCoroutines}" 133 | const val GSON = "com.google.code.gson:gson:${Version.gson}" 134 | const val RETROFIT = "com.squareup.retrofit2:retrofit:${Version.retrofit}" 135 | const val RETROFITGSONADAPTER = "com.squareup.retrofit2:converter-gson:${Version.retrofitGson}" 136 | const val HTTPLOGGER = "com.squareup.okhttp3:logging-interceptor:${Version.okHttp}" 137 | 138 | const val coroutines_core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Version.coroutines_android_version}" 139 | const val coroutines_android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Version.coroutines_android_version}" 140 | 141 | //room 142 | 143 | const val roomCompiler = "androidx.room:room-compiler:${Version.room}" 144 | const val roomRunTime = "androidx.room:room-runtime:${Version.room}" 145 | const val roomKtx = "androidx.room:room-ktx:${Version.room}" 146 | 147 | //testing mockk 148 | 149 | const val MOCKkANDROIDTESTING = "io.mockk:mockk-android:${Version.mockk}" 150 | const val MOCKKTESTING = "io.mockk:mockk:${Version.mockk}" 151 | 152 | const val COROUTINESTESTING = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Version.COROUTINESTESTING}" 153 | const val LIFECYCLETESTING = "androidx.arch.core:core-testing:${Version.archCoreTest}" 154 | 155 | 156 | } 157 | 158 | object ModulesDependency { 159 | const val COMMON = ":libraries:common" 160 | const val REMOTE = ":data:remote" 161 | const val MODEL = ":data:model" 162 | const val LOCAL = ":data:local" 163 | const val UI = ":libraries:uicomponents" 164 | 165 | } 166 | 167 | object FeaturesDependency { 168 | const val prefix = ":features:" 169 | const val Splash = "${prefix}splash" 170 | const val Home = "${prefix}home" 171 | const val Auth = "${prefix}auth" 172 | 173 | } 174 | 175 | object TestLibraryDependency { 176 | object Version { 177 | const val JUNIT = "4.13" 178 | const val JUNIT_ANDROID = "1.1.1" 179 | const val ESPRESSO = "3.2.0" 180 | } 181 | 182 | const val JUNIT = "junit:junit:${Version.JUNIT}" 183 | const val JUNIT_ANDROID = "androidx.test.ext:junit:${Version.JUNIT_ANDROID}" 184 | const val ESPRESSO = "androidx.test.espresso:espresso-core:${Version.ESPRESSO}" 185 | const val KOIN = "org.koin:koin-test:${LibraryDependency.Version.KOIN}" 186 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/GradleExtensions.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.artifacts.Dependency 2 | import org.gradle.api.artifacts.dsl.DependencyHandler 3 | 4 | 5 | /* 6 | Define common dependencies, so they can be easily updated across feature modules 7 | */ 8 | fun DependencyHandler.addTestDependencies() { 9 | testImplementation(TestLibraryDependency.JUNIT) 10 | testImplementation(TestLibraryDependency.KOIN) 11 | androidTestImplementation(TestLibraryDependency.JUNIT_ANDROID) 12 | androidTestImplementation(TestLibraryDependency.ESPRESSO) 13 | testImplementation(LibraryDependency.MOCKKTESTING) 14 | androidTestImplementation(LibraryDependency.MOCKkANDROIDTESTING) 15 | testImplementation(LibraryDependency.COROUTINESTESTING) 16 | testImplementation(LibraryDependency.LIFECYCLETESTING) 17 | 18 | } 19 | 20 | fun DependencyHandler.commonDevelopmentDependencies() { 21 | api(LibraryDependency.APPCOMPAT) 22 | api(LibraryDependency.CONSTRAINT) 23 | api(LibraryDependency.MATERIAL) 24 | api(LibraryDependency.PICASSO) 25 | api(LibraryDependency.ROUNDED_IMAGE_VIEW) 26 | api(LibraryDependency.RECYCLERVIEW) 27 | api(LibraryDependency.KOIN) 28 | api(LibraryDependency.KOIN_SCOPE) 29 | api(LibraryDependency.KOIN_VIEWMODEL) 30 | api(LibraryDependency.LIFECYCLEEXTENSIONS) 31 | api(LibraryDependency.COREKTX) 32 | api(LibraryDependency.LIFECYCLEViewModel) 33 | 34 | } 35 | 36 | fun DependencyHandler.lifeCycleDependencies() { 37 | api(LibraryDependency.LIFECYCLEEXTENSIONS) 38 | api(LibraryDependency.COREKTX) 39 | api(LibraryDependency.LIFECYCLEViewModel) 40 | api(LibraryDependency.LIVEDATAKTX) 41 | 42 | 43 | } 44 | 45 | fun DependencyHandler.diDependencies() { 46 | api(LibraryDependency.KOIN) 47 | api(LibraryDependency.KOIN_SCOPE) 48 | api(LibraryDependency.KOIN_VIEWMODEL) 49 | } 50 | 51 | fun DependencyHandler.networkDependencies() { 52 | api(LibraryDependency.RETROFITCOROUTINESADAPTER) 53 | api(LibraryDependency.GSON) 54 | api(LibraryDependency.RETROFIT) 55 | api(LibraryDependency.RETROFITGSONADAPTER) 56 | api(LibraryDependency.HTTPLOGGER) 57 | api(LibraryDependency.coroutines_core) 58 | api(LibraryDependency.coroutines_android) 59 | 60 | } 61 | 62 | 63 | fun DependencyHandler.localRoomDependencies() { 64 | kapt(LibraryDependency.roomCompiler) 65 | api(LibraryDependency.roomKtx) 66 | api(LibraryDependency.roomRunTime) 67 | } 68 | 69 | fun DependencyHandler.navigationDependencies() { 70 | api(LibraryDependency.NAVIGATION_FRAGMENT) 71 | api(LibraryDependency.NAVIGATION_UI) 72 | } 73 | 74 | 75 | /* 76 | * These extensions mimic the extensions that are generated on the fly by Gradle. 77 | * They are used here to provide above dependency syntax that mimics Gradle Kotlin DSL syntax in module\build.gradle.kts.kts files. 78 | */ 79 | fun DependencyHandler.implementation(dependencyNotation: Any): Dependency? = 80 | add("implementation", dependencyNotation) 81 | 82 | private fun DependencyHandler.api(dependencyNotation: Any): Dependency? = 83 | add("api", dependencyNotation) 84 | 85 | private fun DependencyHandler.kapt(dependencyNotation: Any): Dependency? = 86 | add("kapt", dependencyNotation) 87 | 88 | private fun DependencyHandler.testImplementation(dependencyNotation: Any): Dependency? = 89 | add("testImplementation", dependencyNotation) 90 | 91 | private fun DependencyHandler.androidTestImplementation(dependencyNotation: Any): Dependency? = 92 | add("androidTestImplementation", dependencyNotation) 93 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/base_plugin/BaseGradlePlugin.kt: -------------------------------------------------------------------------------- 1 | package base_plugin 2 | 3 | import org.gradle.api.Plugin 4 | import org.gradle.api.Project 5 | 6 | open class BaseGradlePlugin : Plugin { 7 | override fun apply(project: Project) { 8 | project.configureDefaultPlugins() 9 | project.configureAndroidApp() 10 | project.configureDependencies() 11 | } 12 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/base_plugin/PluginExtensions.kt: -------------------------------------------------------------------------------- 1 | package base_plugin 2 | import com.android.build.gradle.BaseExtension 3 | import org.gradle.api.Project 4 | import org.gradle.api.JavaVersion 5 | import org.gradle.kotlin.dsl.getByType 6 | import org.gradle.kotlin.dsl.dependencies 7 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 8 | 9 | internal fun Project.configureDefaultPlugins() { 10 | plugins.apply(GradlePluginId.ANDROID) 11 | plugins.apply(GradlePluginId.ANDROID_EXT) 12 | } 13 | 14 | private typealias AndroidBaseExtension = BaseExtension 15 | 16 | internal fun Project.configureAndroidApp() = this.extensions.getByType().run { 17 | compileSdkVersion(AndroidConfig.COMPILE_SDK_VERSION) 18 | defaultConfig { 19 | minSdkVersion(AndroidConfig.MIN_SDK_VERSION) 20 | targetSdkVersion(AndroidConfig.TARGET_SDK_VERSION) 21 | versionCode = AndroidConfig.VERSION_CODE 22 | versionName = AndroidConfig.VERSION_NAME 23 | testInstrumentationRunner = AndroidConfig.TEST_INSTRUMENTATION_RUNNER 24 | } 25 | buildTypes { 26 | getByName(BuildType.DEBUG) { 27 | isTestCoverageEnabled = true 28 | } 29 | getByName(BuildType.RELEASE) { 30 | isMinifyEnabled = false 31 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") 32 | } 33 | } 34 | 35 | compileOptions { 36 | sourceCompatibility = JavaVersion.VERSION_1_8 37 | targetCompatibility = JavaVersion.VERSION_1_8 38 | } 39 | 40 | project.tasks.withType(KotlinCompile::class.java).configureEach { 41 | kotlinOptions { 42 | jvmTarget = "1.8" 43 | } 44 | } 45 | 46 | dataBinding.isEnabled = true 47 | 48 | 49 | } 50 | 51 | internal fun Project.configureDependencies() = this.dependencies { 52 | add("implementation", LibraryDependency.KOTLIN_STD) 53 | //koin - di 54 | add("implementation", LibraryDependency.KOIN) 55 | add("implementation", LibraryDependency.KOIN_VIEWMODEL) 56 | add("implementation", LibraryDependency.KOIN_SCOPE) 57 | } 58 | -------------------------------------------------------------------------------- /data/local/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /data/local/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id(GradlePluginId.ANDROID_LIB) 3 | id(GradlePluginId.KAPT) 4 | id(GradlePluginId.BASE_GRADLE_PLUGIN) 5 | 6 | } 7 | 8 | dependencies { 9 | //network libs 10 | localRoomDependencies() 11 | diDependencies() 12 | implementation(project(ModulesDependency.MODEL)) 13 | } 14 | -------------------------------------------------------------------------------- /data/local/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/data/local/consumer-rules.pro -------------------------------------------------------------------------------- /data/local/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.kts. 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 | -------------------------------------------------------------------------------- /data/local/src/androidTest/java/com/example/local/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.local 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.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.getInstrumentation().targetContext 22 | assertEquals("com.example.local.test", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /data/local/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /data/local/src/main/java/com/example/local/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.example.local 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | import com.example.dao.BlogPostDao 8 | import com.example.local.dao.CategoryDao 9 | import com.example.local.dao.ProductDao 10 | import com.example.local.dao.UserDao 11 | import com.example.model.BlogPostApi 12 | import com.example.model.Category 13 | import com.example.model.Product 14 | import com.example.model.User 15 | 16 | @Database(entities = [BlogPostApi::class,Category::class,Product::class,User::class], version = 2, exportSchema = false) 17 | abstract class AppDatabase : RoomDatabase() { 18 | abstract fun blogPostDao(): BlogPostDao 19 | abstract fun categoryDao(): CategoryDao 20 | abstract fun productDao(): ProductDao 21 | abstract fun userDao(): UserDao 22 | 23 | 24 | companion object { 25 | fun buildDatabase(context: Context) = 26 | Room.databaseBuilder( 27 | context.applicationContext, 28 | AppDatabase::class.java, 29 | "appdatabase.db" 30 | ).build() 31 | 32 | } 33 | } -------------------------------------------------------------------------------- /data/local/src/main/java/com/example/local/dao/BlogPostDao.kt: -------------------------------------------------------------------------------- 1 | package com.example.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.example.model.BlogPost 8 | import com.example.model.BlogPostApi 9 | 10 | @Dao 11 | interface BlogPostDao { 12 | 13 | @Insert(onConflict = OnConflictStrategy.REPLACE) 14 | suspend fun insert(blogPost: BlogPostApi):Long 15 | 16 | @Query("SELECT * FROM blogposts") 17 | suspend fun getAllPosts() : List 18 | } -------------------------------------------------------------------------------- /data/local/src/main/java/com/example/local/dao/CategoryDao.kt: -------------------------------------------------------------------------------- 1 | package com.example.local.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.example.model.Category 8 | 9 | @Dao 10 | interface CategoryDao { 11 | 12 | @Insert(onConflict = OnConflictStrategy.REPLACE) 13 | suspend fun insert(category: Category) : Long 14 | 15 | 16 | @Query("SELECT * from category") 17 | suspend fun getAll() : List 18 | } -------------------------------------------------------------------------------- /data/local/src/main/java/com/example/local/dao/ProductDao.kt: -------------------------------------------------------------------------------- 1 | package com.example.local.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.example.model.Category 8 | import com.example.model.Product 9 | 10 | @Dao 11 | interface ProductDao { 12 | 13 | @Insert(onConflict = OnConflictStrategy.REPLACE) 14 | suspend fun insert(product: Product) : Long 15 | 16 | 17 | @Query("SELECT * from product") 18 | suspend fun getAll() : List 19 | } -------------------------------------------------------------------------------- /data/local/src/main/java/com/example/local/dao/UserDao.kt: -------------------------------------------------------------------------------- 1 | package com.example.local.dao 2 | 3 | import androidx.room.* 4 | import com.example.model.User 5 | 6 | @Dao 7 | interface UserDao { 8 | 9 | @Insert(onConflict = OnConflictStrategy.REPLACE) 10 | suspend fun insert(user: User): Long 11 | 12 | @Query("select * from user limit 1") 13 | suspend fun getUser(): User 14 | 15 | @Delete 16 | suspend fun delete(user: User) 17 | } -------------------------------------------------------------------------------- /data/local/src/main/java/com/example/local/di/LocalModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.di 2 | 3 | import com.example.local.AppDatabase 4 | import org.koin.android.ext.koin.androidContext 5 | import org.koin.dsl.module 6 | 7 | val localModule = module { 8 | 9 | single { AppDatabase.buildDatabase(androidContext()) } 10 | factory { get().blogPostDao() } 11 | factory { get().productDao() } 12 | factory { get().categoryDao() } 13 | factory { get().userDao() } 14 | 15 | } -------------------------------------------------------------------------------- /data/local/src/test/java/com/example/local/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.local 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 | -------------------------------------------------------------------------------- /data/model/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /data/model/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id(GradlePluginId.ANDROID_LIB) 3 | id(GradlePluginId.KAPT) 4 | id(GradlePluginId.BASE_GRADLE_PLUGIN) 5 | } 6 | 7 | dependencies { 8 | 9 | localRoomDependencies() 10 | api(LibraryDependency.GSON) 11 | 12 | } 13 | -------------------------------------------------------------------------------- /data/model/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/data/model/consumer-rules.pro -------------------------------------------------------------------------------- /data/model/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.kts. 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 | -------------------------------------------------------------------------------- /data/model/src/androidTest/java/com/example/model/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.model 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.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.getInstrumentation().targetContext 22 | assertEquals("com.example.model.test", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /data/model/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /data/model/src/main/java/com/example/model/BlogPostApi.kt: -------------------------------------------------------------------------------- 1 | package com.example.model 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | 7 | @Entity(tableName = "BlogPosts") 8 | data class BlogPostApi(@PrimaryKey val pk: String, val title: String) 9 | 10 | 11 | data class BlogPost(val pk: String, val title: String) -------------------------------------------------------------------------------- /data/model/src/main/java/com/example/model/CategoryApi.kt: -------------------------------------------------------------------------------- 1 | package com.example.model 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.google.gson.annotations.SerializedName 6 | 7 | @Entity(tableName = "category") 8 | data class Category( 9 | @SerializedName("updated_at") 10 | var updatedAt: String? = null, 11 | @SerializedName("image_url") 12 | var imageUrl: String? = null, 13 | @SerializedName("name") 14 | var name: String = "", 15 | @SerializedName("created_at") 16 | var createdAt: String? = null, 17 | @PrimaryKey 18 | @SerializedName("id") 19 | var id: Int = 0 20 | ) -------------------------------------------------------------------------------- /data/model/src/main/java/com/example/model/ProductApi.kt: -------------------------------------------------------------------------------- 1 | package com.example.model 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.google.gson.annotations.SerializedName 6 | 7 | @Entity(tableName = "product") 8 | data class Product( 9 | @SerializedName("isMastVisited") 10 | var isMastVisited: Boolean? = false, 11 | @SerializedName("reviews") 12 | var reviews: Int? = 0, 13 | @SerializedName("price") 14 | var price: String? = "", 15 | @SerializedName("image_url") 16 | var imageUrl: String? = "", 17 | @SerializedName("isBestSell") 18 | var isBestSell: Boolean? = false, 19 | @SerializedName("isMastRated") 20 | var isMastRated: Boolean? = false, 21 | @SerializedName("cat_id") 22 | var catId: Int? = 0, 23 | @SerializedName("isRecommended") 24 | var isRecommended: Boolean? = false, 25 | @SerializedName("description") 26 | var description: String? = "", 27 | @PrimaryKey 28 | @SerializedName("id") 29 | var id: Int? = 0, 30 | @SerializedName("title") 31 | var title: String? = "", 32 | @SerializedName("isFeatured") 33 | var isFeatured: Boolean? = false 34 | ) -------------------------------------------------------------------------------- /data/model/src/main/java/com/example/model/User.kt: -------------------------------------------------------------------------------- 1 | package com.example.model 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.google.gson.annotations.SerializedName 6 | 7 | @Entity(tableName = "user") 8 | data class User(@SerializedName("updated_at") 9 | val updatedAt: String = "", 10 | @SerializedName("name") 11 | val name: String = "", 12 | @SerializedName("created_at") 13 | val createdAt: String = "", 14 | @PrimaryKey 15 | @SerializedName("id") 16 | val id: Int = 0, 17 | @SerializedName("email") 18 | val email: String = "", 19 | @SerializedName("token") 20 | val token: String = "") -------------------------------------------------------------------------------- /data/model/src/test/java/com/example/model/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.model 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 | -------------------------------------------------------------------------------- /data/remote/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /data/remote/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id(GradlePluginId.ANDROID_LIB) 3 | id(GradlePluginId.KAPT) 4 | id(GradlePluginId.BASE_GRADLE_PLUGIN) 5 | id(GradlePluginId.SAFE_ARGS) 6 | 7 | } 8 | 9 | dependencies { 10 | implementation(project(ModulesDependency.COMMON)) 11 | 12 | //network libs 13 | lifeCycleDependencies() 14 | diDependencies() 15 | networkDependencies() 16 | } 17 | -------------------------------------------------------------------------------- /data/remote/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/data/remote/consumer-rules.pro -------------------------------------------------------------------------------- /data/remote/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.kts. 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 | -------------------------------------------------------------------------------- /data/remote/src/androidTest/java/com/example/remote/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.remote 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.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.getInstrumentation().targetContext 22 | assertEquals("com.example.remote.test", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /data/remote/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /data/remote/src/main/java/com/example/RemoteExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.example 2 | 3 | import com.example.common.MessageType 4 | import com.example.common.MessageType.Dialog 5 | import com.example.common.MessageType.SnackBar 6 | import com.example.common.NetworkCodes 7 | import com.example.common.NetworkCodes.GENERAL_ERROR 8 | import com.google.gson.Gson 9 | import kotlinx.coroutines.CoroutineDispatcher 10 | import kotlinx.coroutines.TimeoutCancellationException 11 | import kotlinx.coroutines.withContext 12 | import org.json.JSONArray 13 | import org.json.JSONObject 14 | import org.json.JSONTokener 15 | import retrofit2.HttpException 16 | import java.io.IOException 17 | 18 | sealed class ResultWrapper { 19 | data class Success(val value: T) : ResultWrapper() 20 | data class GenericError(val messageType: MessageType) : ResultWrapper() 21 | } 22 | 23 | 24 | suspend fun safeApiCall( 25 | dispatcher: CoroutineDispatcher, 26 | apiCall: suspend () -> T? 27 | ): ResultWrapper { 28 | return withContext(dispatcher) { 29 | try { 30 | val call = apiCall.invoke() 31 | ResultWrapper.Success(call) 32 | } catch (throwable: Throwable) { 33 | throwable.printStackTrace() 34 | when (throwable) { 35 | is TimeoutCancellationException -> { 36 | ResultWrapper.GenericError(messageType = SnackBar(NetworkCodes.TIMEOUT_ERROR)) 37 | } 38 | is IOException -> { 39 | ResultWrapper.GenericError( 40 | SnackBar(NetworkCodes.CONNECTION_ERROR) 41 | ) 42 | } 43 | is HttpException -> { 44 | val code = throwable.code() 45 | ResultWrapper.GenericError( 46 | SnackBar(code, message = convertErrorBody(throwable)) 47 | ) 48 | } 49 | else -> { 50 | ResultWrapper.GenericError( 51 | Dialog(GENERAL_ERROR) 52 | ) 53 | } 54 | } 55 | 56 | } 57 | } 58 | } 59 | 60 | 61 | //for custom error body 62 | private fun convertErrorBody(throwable: HttpException): String? { 63 | try { 64 | val json = JSONTokener(throwable.response()?.errorBody()?.string()).nextValue(); 65 | if (json is JSONObject || json is JSONArray) { 66 | val errorResponse = Gson().fromJson(json.toString(), ErrorResponse::class.java) 67 | errorResponse?.let { return it.message } 68 | } 69 | return null 70 | 71 | } catch (exception: Exception) { 72 | return null 73 | } 74 | } 75 | 76 | class ErrorResponse(val message: String? = "") 77 | 78 | -------------------------------------------------------------------------------- /data/remote/src/main/java/com/example/remote/NetworkBoundResource.kt: -------------------------------------------------------------------------------- 1 | package com.example.remote 2 | 3 | import com.example.ResultWrapper 4 | import com.example.remote.data.Resource 5 | import com.example.safeApiCall 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.flow 9 | import kotlinx.coroutines.flow.onStart 10 | 11 | 12 | abstract class NetworkBoundResource { 13 | 14 | fun asFlow(): Flow> = flow { 15 | 16 | if (shouldFetch()) { 17 | if (shouldFetchWithLocalData()) { 18 | emit(Resource.loading(data = localFetch())) 19 | } 20 | 21 | val results = safeApiCall(dispatcher = Dispatchers.IO) { 22 | remoteFetch() 23 | } 24 | 25 | when (results) { 26 | is ResultWrapper.Success -> { 27 | results.value?.let { 28 | saveFetchResult(results.value) 29 | } 30 | emit(Resource.success(data = results.value)) 31 | } 32 | 33 | is ResultWrapper.GenericError -> { 34 | emit(Resource.error(data = null, error = results.messageType)) 35 | } 36 | } 37 | } else { 38 | //get from cash 39 | emit(Resource.success(data = localFetch())) 40 | } 41 | 42 | 43 | }.onStart { 44 | //get From cache 45 | emit(Resource.loading(data = null)) 46 | } 47 | 48 | 49 | abstract suspend fun remoteFetch(): T 50 | abstract suspend fun saveFetchResult(data: T) 51 | abstract suspend fun localFetch(): T 52 | open fun onFetchFailed(throwable: Throwable) = Unit 53 | open fun shouldFetch() = true 54 | open fun shouldFetchWithLocalData() = false 55 | } 56 | 57 | 58 | -------------------------------------------------------------------------------- /data/remote/src/main/java/com/example/remote/data/Resource.kt: -------------------------------------------------------------------------------- 1 | package com.example.remote.data 2 | 3 | import com.example.common.MessageType 4 | 5 | data class Resource(val status: Status, val data: T?, val messageType: MessageType?) { 6 | companion object { 7 | fun success(data: T?): Resource { 8 | return Resource( 9 | Status.SUCCESS, 10 | data, 11 | null 12 | ) 13 | } 14 | 15 | fun error(error: MessageType, data: T?): Resource { 16 | return Resource( 17 | Status.ERROR, 18 | data, 19 | error 20 | ) 21 | } 22 | 23 | fun loading(data: T?): Resource { 24 | return Resource( 25 | Status.LOADING, 26 | data, 27 | null 28 | ) 29 | } 30 | } 31 | 32 | enum class Status { 33 | SUCCESS, 34 | ERROR, 35 | LOADING 36 | } 37 | } -------------------------------------------------------------------------------- /data/remote/src/main/java/com/example/remote/di/RemoteModule.kt: -------------------------------------------------------------------------------- 1 | package com.example.remote.di 2 | 3 | import com.google.gson.GsonBuilder 4 | import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory 5 | import okhttp3.Interceptor 6 | import okhttp3.OkHttpClient 7 | import okhttp3.logging.HttpLoggingInterceptor 8 | import org.koin.dsl.module 9 | import retrofit2.Retrofit 10 | import retrofit2.converter.gson.GsonConverterFactory 11 | 12 | fun getRemoteModule(baseUrl: String) = module { 13 | single { 14 | Retrofit.Builder().client(get()).baseUrl(baseUrl) 15 | .addCallAdapterFactory(CoroutineCallAdapterFactory()) 16 | .addConverterFactory(GsonConverterFactory.create(get())) 17 | .build() 18 | } 19 | 20 | 21 | factory { 22 | GsonBuilder() 23 | .setLenient() 24 | .create(); 25 | } 26 | 27 | 28 | factory { 29 | HttpLoggingInterceptor() 30 | .setLevel(HttpLoggingInterceptor.Level.BODY) 31 | } 32 | 33 | factory { OkHttpClient.Builder().addInterceptor(get()).build() } 34 | 35 | 36 | } -------------------------------------------------------------------------------- /data/remote/src/main/java/com/example/remote/di/Test.kt: -------------------------------------------------------------------------------- 1 | package com.example.remote.di 2 | 3 | class Test { 4 | fun test(){ 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /data/remote/src/test/java/com/example/remote/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.remote 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 | -------------------------------------------------------------------------------- /features/auth/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /features/auth/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id(GradlePluginId.ANDROID_LIB) 3 | id(GradlePluginId.KAPT) 4 | id(GradlePluginId.BASE_GRADLE_PLUGIN) 5 | id(GradlePluginId.SAFE_ARGS) 6 | 7 | } 8 | 9 | dependencies { 10 | 11 | localRoomDependencies() 12 | navigationDependencies() 13 | implementation(project(ModulesDependency.COMMON)) 14 | implementation(project(ModulesDependency.REMOTE)) 15 | implementation(project(ModulesDependency.MODEL)) 16 | implementation(project(ModulesDependency.LOCAL)) 17 | implementation(project(ModulesDependency.UI)) 18 | 19 | addTestDependencies() 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /features/auth/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/features/auth/consumer-rules.pro -------------------------------------------------------------------------------- /features/auth/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.kts. 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 | -------------------------------------------------------------------------------- /features/auth/src/androidTest/java/com/example/auth/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.auth 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.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.getInstrumentation().targetContext 22 | assertEquals("com.example.auth.test", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /features/auth/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /features/auth/src/main/java/com/example/auth/data/AuthServices.kt: -------------------------------------------------------------------------------- 1 | package com.example.auth.data 2 | 3 | import com.example.model.User 4 | import com.google.gson.JsonObject 5 | import retrofit2.http.Body 6 | import retrofit2.http.POST 7 | 8 | interface AuthServices { 9 | 10 | @POST("login") 11 | suspend fun login(@Body jsonObject: JsonObject): User 12 | 13 | @POST("register") 14 | suspend fun register(@Body jsonObject: JsonObject): User 15 | } -------------------------------------------------------------------------------- /features/auth/src/main/java/com/example/auth/data/datasource/AuthRepository.kt: -------------------------------------------------------------------------------- 1 | package com.example.auth.data.datasource 2 | 3 | import com.example.model.User 4 | import com.example.remote.data.Resource 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface AuthRepository { 8 | suspend fun login(email:String,password:String) : Flow> 9 | suspend fun register(username:String,email:String,password:String) : Flow> 10 | } -------------------------------------------------------------------------------- /features/auth/src/main/java/com/example/auth/data/datasource/local/AuthLocal.kt: -------------------------------------------------------------------------------- 1 | package com.example.auth.data.datasource.local 2 | 3 | import com.example.model.User 4 | 5 | interface AuthLocal { 6 | suspend fun insertUser(user: User) : Long 7 | suspend fun deleteUser(user: User) 8 | suspend fun getUserUser() : Any 9 | } -------------------------------------------------------------------------------- /features/auth/src/main/java/com/example/auth/data/datasource/local/AuthLocalImpl.kt: -------------------------------------------------------------------------------- 1 | package com.example.auth.data.datasource.local 2 | 3 | import com.example.local.dao.UserDao 4 | import com.example.model.User 5 | 6 | class AuthLocalImpl(private val userDao: UserDao) : AuthLocal { 7 | override suspend fun insertUser(user: User): Long { 8 | return userDao.insert(user) 9 | } 10 | 11 | override suspend fun deleteUser(user: User) { 12 | userDao.delete(user) 13 | } 14 | 15 | override suspend fun getUserUser(): User { 16 | return userDao.getUser() 17 | } 18 | } -------------------------------------------------------------------------------- /features/auth/src/main/java/com/example/auth/data/datasource/remote/AuthRemote.kt: -------------------------------------------------------------------------------- 1 | package com.example.auth.data.datasource.remote 2 | 3 | import com.example.model.User 4 | import com.google.gson.JsonObject 5 | 6 | interface AuthRemote { 7 | suspend fun login(jsonObject: JsonObject) : User 8 | suspend fun register(jsonObject: JsonObject) :User 9 | } -------------------------------------------------------------------------------- /features/auth/src/main/java/com/example/auth/data/datasource/remote/AuthRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.example.auth.data.datasource.remote 2 | 3 | import com.example.auth.data.datasource.AuthRepository 4 | import com.example.auth.data.datasource.local.AuthLocal 5 | import com.example.model.User 6 | import com.example.remote.NetworkBoundResource 7 | import com.example.remote.data.Resource 8 | import com.google.gson.JsonObject 9 | import kotlinx.coroutines.flow.Flow 10 | 11 | class AuthRepositoryImpl(private val remote: AuthRemote, private val local: AuthLocal) : 12 | AuthRepository { 13 | override suspend fun login(email: String, password: String): Flow> { 14 | return object : NetworkBoundResource() { 15 | override suspend fun remoteFetch(): User { 16 | return remote.login(JsonObject().apply { 17 | addProperty("email", email) 18 | addProperty("password", password) 19 | }) 20 | } 21 | 22 | override suspend fun saveFetchResult(data: User) { 23 | local.insertUser(data) 24 | } 25 | 26 | override suspend fun localFetch(): User { 27 | return User() 28 | } 29 | 30 | override fun shouldFetchWithLocalData(): Boolean { 31 | return false 32 | } 33 | }.asFlow() 34 | } 35 | 36 | override suspend fun register( 37 | username: String, 38 | email: String, 39 | password: String 40 | ): Flow> { 41 | return object : NetworkBoundResource() { 42 | override suspend fun remoteFetch(): User { 43 | return remote.register(JsonObject().apply { 44 | addProperty("email", email) 45 | addProperty("name", username) 46 | addProperty("password", password) 47 | }) 48 | } 49 | 50 | override suspend fun saveFetchResult(data: User) { 51 | } 52 | 53 | override suspend fun localFetch(): User { 54 | return User() 55 | } 56 | 57 | override fun shouldFetchWithLocalData(): Boolean { 58 | return false 59 | } 60 | }.asFlow() 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /features/auth/src/main/java/com/example/auth/data/datasource/remote/AuthRmoteImpl.kt: -------------------------------------------------------------------------------- 1 | package com.example.auth.data.datasource.remote 2 | 3 | import com.example.auth.data.AuthServices 4 | import com.example.model.User 5 | import com.google.gson.JsonObject 6 | 7 | class AuthRemoteImpl(private val authServices: AuthServices): AuthRemote { 8 | override suspend fun login(jsonObject: JsonObject): User { 9 | return authServices.login(jsonObject) 10 | } 11 | 12 | override suspend fun register(jsonObject: JsonObject): User { 13 | return authServices.register(jsonObject) 14 | } 15 | } -------------------------------------------------------------------------------- /features/auth/src/main/java/com/example/auth/di/AuthModule.kt: -------------------------------------------------------------------------------- 1 | package com.example.auth.di 2 | 3 | import com.example.auth.data.AuthServices 4 | import com.example.auth.data.datasource.AuthRepository 5 | import com.example.auth.data.datasource.local.AuthLocal 6 | import com.example.auth.data.datasource.local.AuthLocalImpl 7 | import com.example.auth.data.datasource.remote.AuthRemote 8 | import com.example.auth.data.datasource.remote.AuthRemoteImpl 9 | import com.example.auth.data.datasource.remote.AuthRepositoryImpl 10 | import com.example.auth.domain.LoginUseCase 11 | import com.example.auth.domain.RegisterUseCase 12 | import com.example.auth.presenter.AuthViewModel 13 | import org.koin.androidx.viewmodel.dsl.viewModel 14 | import org.koin.dsl.module 15 | import retrofit2.Retrofit 16 | 17 | 18 | val authModule = module { 19 | viewModel { AuthViewModel(get(), get()) } 20 | 21 | factory { 22 | LoginUseCase(get()) 23 | } 24 | factory { 25 | RegisterUseCase(get()) 26 | } 27 | 28 | factory { 29 | AuthRepositoryImpl(get(), get()) 30 | } 31 | 32 | 33 | factory { 34 | AuthRemoteImpl(get()) 35 | } 36 | 37 | factory { 38 | AuthLocalImpl(get()) 39 | } 40 | 41 | single { 42 | get().create(AuthServices::class.java) 43 | } 44 | } -------------------------------------------------------------------------------- /features/auth/src/main/java/com/example/auth/domain/LoginUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.example.auth.domain 2 | 3 | import com.example.auth.data.datasource.AuthRepository 4 | import com.example.model.User 5 | import com.example.remote.data.Resource 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | class LoginUseCase(private val repository: AuthRepository) { 9 | 10 | suspend operator fun invoke(email:String,password:String): Flow> { 11 | return repository.login(email,password) 12 | } 13 | } -------------------------------------------------------------------------------- /features/auth/src/main/java/com/example/auth/domain/RegisterUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.example.auth.domain 2 | 3 | import com.example.auth.data.datasource.AuthRepository 4 | import com.example.model.User 5 | import com.example.remote.data.Resource 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | class RegisterUseCase(private val repository: AuthRepository) { 9 | suspend operator fun invoke(username:String,email:String,password:String): Flow> { 10 | return repository.register(username,email,password) 11 | } 12 | } -------------------------------------------------------------------------------- /features/auth/src/main/java/com/example/auth/presenter/AuthActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.auth.presenter 2 | 3 | import android.os.Bundle 4 | import androidx.navigation.NavController 5 | import androidx.navigation.findNavController 6 | import androidx.navigation.ui.AppBarConfiguration 7 | import androidx.navigation.ui.navigateUp 8 | import androidx.navigation.ui.setupActionBarWithNavController 9 | import com.example.auth.R 10 | import com.example.auth.databinding.ActivityAuthBinding 11 | import com.example.common.BaseActivity 12 | 13 | class AuthActivity : BaseActivity() { 14 | private lateinit var navController: NavController 15 | private lateinit var appBarConfiguration: AppBarConfiguration 16 | 17 | override fun onCreate(instance: Bundle?, binding: ActivityAuthBinding) { 18 | configureNavController() 19 | 20 | } 21 | 22 | override fun onSupportNavigateUp(): Boolean { 23 | return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() 24 | } 25 | 26 | private fun configureNavController() { 27 | navController = findNavController(R.id.nav_host_fragment) 28 | appBarConfiguration = AppBarConfiguration(navController.graph) 29 | setupActionBarWithNavController(navController, appBarConfiguration) 30 | } 31 | 32 | 33 | override val layoutId = R.layout.activity_auth 34 | } 35 | -------------------------------------------------------------------------------- /features/auth/src/main/java/com/example/auth/presenter/AuthViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.auth.presenter 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import com.example.auth.domain.LoginUseCase 8 | import com.example.auth.domain.RegisterUseCase 9 | import com.example.model.User 10 | import com.example.remote.data.Resource 11 | import kotlinx.coroutines.flow.collect 12 | import kotlinx.coroutines.launch 13 | 14 | class AuthViewModel( 15 | private val loginUseCase: LoginUseCase, 16 | private val registerUseCase: RegisterUseCase 17 | ) : ViewModel() { 18 | 19 | private val _userRegister = MutableLiveData>() 20 | private val _userLogin = MutableLiveData>() 21 | 22 | val userRegister: LiveData> get() = _userRegister 23 | val userLogin: LiveData> get() = _userLogin 24 | 25 | fun register(username: String, email: String, password: String) { 26 | viewModelScope.launch { 27 | registerUseCase.invoke(username, email, password).collect { data -> 28 | _userRegister.value = data 29 | } 30 | } 31 | 32 | } 33 | 34 | fun login(email: String, password: String) { 35 | viewModelScope.launch { 36 | loginUseCase.invoke(email, password).collect { data -> 37 | _userLogin.value = data 38 | } 39 | } 40 | 41 | } 42 | } -------------------------------------------------------------------------------- /features/auth/src/main/java/com/example/auth/presenter/fragments/LoginFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.auth.presenter.fragments 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import android.view.View 6 | import androidx.lifecycle.Observer 7 | import androidx.navigation.fragment.findNavController 8 | 9 | import com.example.auth.R 10 | import com.example.auth.presenter.AuthViewModel 11 | import com.example.common.BaseFragment 12 | import com.example.common.showSnackbar 13 | import com.example.remote.data.Resource 14 | import kotlinx.android.synthetic.main.fragment_login.* 15 | import kotlinx.android.synthetic.main.fragment_login.emailET 16 | import kotlinx.android.synthetic.main.fragment_login.passwordEt 17 | import kotlinx.android.synthetic.main.fragment_resgister.* 18 | import kotlinx.coroutines.CoroutineScope 19 | import kotlinx.coroutines.Dispatchers 20 | import kotlinx.coroutines.delay 21 | import kotlinx.coroutines.launch 22 | import org.koin.androidx.viewmodel.ext.android.viewModel 23 | 24 | /** 25 | * A simple [Fragment] subclass. 26 | */ 27 | class LoginFragment : BaseFragment(R.layout.fragment_login) { 28 | 29 | 30 | private val viewModel: AuthViewModel by viewModel() 31 | 32 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 33 | super.onViewCreated(view, savedInstanceState) 34 | signUpBtn.setOnClickListener { 35 | findNavController().navigate(R.id.action_loginFragment_to_registerFragment2) 36 | } 37 | 38 | 39 | viewModel.userLogin.observe(viewLifecycleOwner, Observer { 40 | when (it.status) { 41 | Resource.Status.LOADING -> { 42 | uiCommunicator?.showLoading() 43 | } 44 | Resource.Status.ERROR -> { 45 | uiCommunicator?.handleMessages(it.messageType!!) 46 | uiCommunicator?.hideLoading() 47 | } 48 | Resource.Status.SUCCESS -> { 49 | uiCommunicator?.hideLoading() 50 | //navigate to dashboard 51 | showSnackbar("your account logged in successfully") 52 | } 53 | } 54 | }) 55 | 56 | submitLoginBtn.setOnClickListener { 57 | viewModel.login( 58 | emailET.text.toString(), 59 | passwordEt.text.toString() 60 | ) 61 | } 62 | 63 | 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /features/auth/src/main/java/com/example/auth/presenter/fragments/RegisterFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.auth.presenter.fragments 2 | 3 | 4 | import android.content.Context 5 | import android.os.Bundle 6 | import android.view.View 7 | import androidx.fragment.app.Fragment 8 | import androidx.lifecycle.Observer 9 | import androidx.navigation.fragment.findNavController 10 | import com.example.auth.R 11 | import com.example.auth.presenter.AuthViewModel 12 | import com.example.common.BaseFragment 13 | import com.example.common.UiCommunicator 14 | import com.example.common.showSnackbar 15 | import com.example.remote.data.Resource 16 | import kotlinx.android.synthetic.main.fragment_resgister.* 17 | import kotlinx.coroutines.CoroutineScope 18 | import kotlinx.coroutines.Dispatchers.Main 19 | import kotlinx.coroutines.delay 20 | import kotlinx.coroutines.launch 21 | import org.koin.androidx.viewmodel.ext.android.viewModel 22 | 23 | /** 24 | * A simple [Fragment] subclass. 25 | */ 26 | class RegisterFragment : BaseFragment(R.layout.fragment_resgister) { 27 | 28 | private val viewModel: AuthViewModel by viewModel() 29 | 30 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 31 | super.onViewCreated(view, savedInstanceState) 32 | 33 | viewModel.userRegister.observe(viewLifecycleOwner, Observer { 34 | when (it.status) { 35 | Resource.Status.LOADING -> { 36 | uiCommunicator?.showLoading() 37 | } 38 | Resource.Status.ERROR -> { 39 | uiCommunicator?.handleMessages(it.messageType!!) 40 | uiCommunicator?.hideLoading() 41 | } 42 | Resource.Status.SUCCESS -> { 43 | uiCommunicator?.hideLoading() 44 | showSnackbar("your account register successfully") 45 | CoroutineScope(Main).launch { 46 | delay(2000) 47 | findNavController().navigateUp() 48 | } 49 | } 50 | } 51 | }) 52 | 53 | submitRegisterBtn.setOnClickListener { 54 | viewModel.register( 55 | nameEt.text.toString(), 56 | emailET.text.toString(), 57 | passwordEt.text.toString() 58 | ) 59 | } 60 | 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /features/auth/src/main/res/layout/activity_auth.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /features/auth/src/main/res/layout/fragment_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 27 | 28 | 29 | 30 | 38 | 39 | 45 | 46 | 47 |