├── .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 |
5 |
6 |
7 |
8 |
9 |
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 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
32 |
33 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | 1.8
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
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 |
58 |
59 |
69 |
70 |
--------------------------------------------------------------------------------
/features/auth/src/main/res/layout/fragment_resgister.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
19 |
20 |
26 |
27 |
28 |
36 |
37 |
43 |
44 |
45 |
46 |
54 |
55 |
61 |
62 |
63 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/features/auth/src/main/res/navigation/auth_navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
21 |
22 |
26 |
34 |
35 |
--------------------------------------------------------------------------------
/features/auth/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Hello blank fragment
5 |
6 |
--------------------------------------------------------------------------------
/features/auth/src/test/java/com/example/auth/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.auth
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/home/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/features/home/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 | localRoomDependencies()
11 | implementation(project(ModulesDependency.COMMON))
12 | implementation(project(ModulesDependency.REMOTE))
13 | implementation(project(ModulesDependency.MODEL))
14 | implementation(project(ModulesDependency.LOCAL))
15 | implementation(project(ModulesDependency.UI))
16 |
17 | addTestDependencies()
18 |
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/features/home/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/features/home/consumer-rules.pro
--------------------------------------------------------------------------------
/features/home/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/home/src/androidTest/java/com/example/home/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.home
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.home.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/features/home/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/features/home/src/main/java/com/example/home/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.example.home
2 |
3 | object Constants {
4 |
5 |
6 | var PRODUCT_VIEW_TYPE = "product"
7 | val CATEGORY_VIEW_TYPE = "category"
8 |
9 |
10 | }
--------------------------------------------------------------------------------
/features/home/src/main/java/com/example/home/data/HomeRepository.kt:
--------------------------------------------------------------------------------
1 | package com.example.home.data
2 |
3 | import com.example.model.BlogPostApi
4 | import com.example.remote.data.Resource
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface HomeRepository {
8 | suspend fun getHome() : Flow>>
9 | }
--------------------------------------------------------------------------------
/features/home/src/main/java/com/example/home/data/HomeRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.example.home.data
2 |
3 | import com.example.home.data.datasource.local.HomeLocal
4 | import com.example.home.data.datasource.remote.HomeRemote
5 | import com.example.remote.NetworkBoundResource
6 | import com.example.remote.data.Resource
7 | import kotlinx.coroutines.flow.Flow
8 |
9 | class HomeRepositoryImpl(val homeRemote: HomeRemote, val local: HomeLocal):HomeRepository {
10 | override suspend fun getHome(): Flow>> {
11 | return object: NetworkBoundResource>(){
12 | override suspend fun remoteFetch(): List {
13 | return homeRemote.getHome()
14 | }
15 | override suspend fun saveFetchResult(data: List) {
16 | local.saveHomeData(data)
17 | }
18 | override suspend fun localFetch(): List {
19 | return local.getHome()
20 | }
21 |
22 | override fun shouldFetchWithLocalData(): Boolean {
23 | return true
24 | }
25 | }.asFlow()
26 | }
27 | }
--------------------------------------------------------------------------------
/features/home/src/main/java/com/example/home/data/HomeResponse.kt:
--------------------------------------------------------------------------------
1 | package com.example.home.data
2 |
3 | import com.example.model.Category
4 | import com.example.model.Product
5 | import com.google.gson.annotations.SerializedName
6 |
7 | data class HomeSection(
8 | val title: String,
9 | @SerializedName("view_type") var viewType: String,
10 | var categories: MutableList? = null,
11 | var products: MutableList? = null
12 | )
--------------------------------------------------------------------------------
/features/home/src/main/java/com/example/home/data/HomeServices.kt:
--------------------------------------------------------------------------------
1 | package com.example.home.data
2 |
3 | import retrofit2.http.GET
4 |
5 | interface HomeServices {
6 | @GET("home")
7 | suspend fun getHome():List
8 | }
--------------------------------------------------------------------------------
/features/home/src/main/java/com/example/home/data/datasource/local/HomeLocalImpl.kt:
--------------------------------------------------------------------------------
1 | package com.example.home.data.datasource.local
2 |
3 | import com.example.home.data.HomeSection
4 | import com.example.local.dao.CategoryDao
5 | import com.example.local.dao.ProductDao
6 |
7 | class HomeLocalImpl(val productDao: ProductDao, val categoryDao: CategoryDao) : HomeLocal {
8 | override suspend fun getHome(): List {
9 | val list: MutableList = mutableListOf()
10 | val cats = categoryDao.getAll()
11 | if (cats.isNotEmpty()) {
12 | val catsSection =
13 | HomeSection(title = "Categories", categories = cats.toMutableList(), viewType = "category")
14 | list.add(catsSection)
15 | }
16 | val allProducts = productDao.getAll()
17 | allProducts.filter { item-> item.isFeatured!! }.let {
18 | val section =
19 | HomeSection(title = "Featured", products = it.toMutableList(), viewType = "product")
20 | list.add(section)
21 | }
22 |
23 | allProducts.filter { item-> item.isBestSell!! }.let {
24 | val section =
25 | HomeSection(title = "Best Sell", products = it.toMutableList(), viewType = "product")
26 | list.add(section)
27 | }
28 |
29 | allProducts.filter { item-> item.isMastRated!! }.let {
30 | val section =
31 | HomeSection(title = "Mast Rated", products = it.toMutableList(), viewType = "product")
32 | list.add(section)
33 | }
34 |
35 | allProducts.filter { item-> item.isMastVisited!! }.let {
36 | val section =
37 | HomeSection(title = "Mast Visited", products = it.toMutableList(), viewType = "product")
38 | list.add(section)
39 | }
40 |
41 | allProducts.filter { item-> item.isRecommended!! }.let {
42 | val section =
43 | HomeSection(title = "Recommend for you", products = it.toMutableList(), viewType = "product")
44 | list.add(section)
45 | }
46 | return list
47 | }
48 |
49 | override suspend fun saveHomeData(data: List) {
50 | for (section in data) {
51 | section.categories?.let {
52 | it.forEach {
53 | categoryDao.insert(it)
54 | }
55 | }
56 |
57 | section.products?.let {
58 | it.forEach {
59 | productDao.insert(it)
60 | }
61 | }
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/features/home/src/main/java/com/example/home/data/datasource/local/homeLocal.kt:
--------------------------------------------------------------------------------
1 | package com.example.home.data.datasource.local
2 |
3 | import com.example.home.data.HomeSection
4 |
5 | interface HomeLocal {
6 | suspend fun getHome(): List
7 | suspend fun saveHomeData(data: List)
8 |
9 | }
--------------------------------------------------------------------------------
/features/home/src/main/java/com/example/home/data/datasource/remote/HomeRemote.kt:
--------------------------------------------------------------------------------
1 | package com.example.home.data.datasource.remote
2 |
3 | import com.example.home.data.HomeSection
4 | import com.example.model.BlogPostApi
5 |
6 | interface HomeRemote {
7 | suspend fun getHome(): List
8 | }
--------------------------------------------------------------------------------
/features/home/src/main/java/com/example/home/data/datasource/remote/HomeRemoteImpl.kt:
--------------------------------------------------------------------------------
1 | package com.example.home.data.datasource.remote
2 |
3 | import com.example.home.data.HomeSection
4 | import com.example.home.data.HomeServices
5 |
6 | class HomeRemoteImpl(private val remote: HomeServices) : HomeRemote {
7 | override suspend fun getHome(): List {
8 | return remote.getHome()
9 | }
10 | }
--------------------------------------------------------------------------------
/features/home/src/main/java/com/example/home/di/di.kt:
--------------------------------------------------------------------------------
1 | package com.example.home.di
2 |
3 | import com.example.home.data.HomeRepository
4 | import com.example.home.data.HomeRepositoryImpl
5 | import com.example.home.data.HomeServices
6 | import com.example.home.data.datasource.local.HomeLocal
7 | import com.example.home.data.datasource.local.HomeLocalImpl
8 | import com.example.home.data.datasource.remote.HomeRemote
9 | import com.example.home.data.datasource.remote.HomeRemoteImpl
10 | import com.example.home.domain.GetHomeUserCase
11 | import com.example.home.presenter.HomeViewModel
12 | import com.example.local.dao.ProductDao
13 | import org.koin.androidx.viewmodel.dsl.viewModel
14 | import org.koin.dsl.module
15 | import retrofit2.Retrofit
16 |
17 | val homeModule = module {
18 | viewModel { HomeViewModel(get()) }
19 |
20 | factory {
21 | GetHomeUserCase(get())
22 | }
23 |
24 |
25 | factory {
26 | HomeRemoteImpl(get())
27 | }
28 |
29 | factory {
30 | HomeLocalImpl(get(),get())
31 | }
32 |
33 |
34 | factory {
35 | HomeRepositoryImpl(get(),get())
36 | }
37 |
38 | single {
39 | get().create(HomeServices::class.java)
40 | }
41 | }
--------------------------------------------------------------------------------
/features/home/src/main/java/com/example/home/domain/GetHomeUserCase.kt:
--------------------------------------------------------------------------------
1 | package com.example.home.domain
2 |
3 | import com.example.home.data.HomeRepository
4 | import com.example.home.data.HomeSection
5 | import com.example.remote.data.Resource
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | class GetHomeUserCase(private val repository: HomeRepository) {
9 | suspend operator fun invoke(): Flow>> {
10 | return repository.getHome()
11 | }
12 | }
--------------------------------------------------------------------------------
/features/home/src/main/java/com/example/home/presenter/HomeActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.home.presenter
2 |
3 | import android.os.Bundle
4 | import androidx.lifecycle.Observer
5 | import com.example.common.BaseActivity
6 | import com.example.home.R
7 | import com.example.home.databinding.ActivityHomeBinding
8 | import com.example.remote.data.Resource
9 | import org.koin.androidx.viewmodel.ext.android.viewModel
10 |
11 |
12 | class HomeActivity : BaseActivity() {
13 | val viewModel: HomeViewModel by viewModel()
14 |
15 | override fun onCreate(instance: Bundle?, binding: ActivityHomeBinding) {
16 | binding.lifecycleOwner = this
17 | binding.viewmodel = viewModel
18 | initObservables()
19 | initRecyclerView()
20 | }
21 |
22 | private fun initObservables() {
23 | viewModel.homeSectionsLD.observe(this, Observer {
24 | if(it.status == Resource.Status.ERROR){
25 | it.messageType?.let { it1 -> handleMessages(it1) }
26 | }
27 | })
28 |
29 | viewModel.gethome()
30 | }
31 |
32 | private fun initRecyclerView() {
33 | getCurrentViewBinding().sectionsRv.adapter = HomeAdapter(viewModel = viewModel)
34 |
35 | }
36 |
37 | override val layoutId = R.layout.activity_home
38 | }
39 |
--------------------------------------------------------------------------------
/features/home/src/main/java/com/example/home/presenter/HomeAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.example.home.presenter
2 |
3 | import android.view.View
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.LinearLayoutManager
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.example.home.R
8 | import com.example.home.data.HomeSection
9 | import com.example.home.databinding.HomeAdapterItemBinding
10 | import com.example.uicomponents.helpers.inflate
11 | import kotlinx.android.synthetic.main.home_adapter_item.view.*
12 |
13 | class HomeAdapter( viewModel: HomeViewModel) :
14 | RecyclerView.Adapter() {
15 |
16 | var list: MutableList = mutableListOf()
17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
18 | HomeViewHolder(parent.inflate(R.layout.home_adapter_item))
19 |
20 | override fun getItemCount() = list.size
21 | override fun onBindViewHolder(holder: HomeViewHolder, position: Int) {
22 | val section = list[position]
23 | holder.bind(section)
24 | }
25 |
26 | class HomeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
27 | lateinit var homeInnerAdapter: HomeInnerAdapter
28 | private val binding = HomeAdapterItemBinding.bind(itemView)
29 | fun bind(section: HomeSection) {
30 | binding.section = section
31 | homeInnerAdapter = HomeInnerAdapter(section)
32 | itemView.sectionsRv.apply {
33 | layoutManager = LinearLayoutManager(context,LinearLayoutManager.HORIZONTAL,false)
34 | adapter = homeInnerAdapter
35 | }
36 | }
37 | }
38 |
39 | fun updateList(listUpdate: MutableList) {
40 | list = listUpdate
41 | notifyDataSetChanged()
42 |
43 | }
44 | }
45 |
46 |
47 |
--------------------------------------------------------------------------------
/features/home/src/main/java/com/example/home/presenter/HomeBinding.kt:
--------------------------------------------------------------------------------
1 | package com.example.home.presenter
2 |
3 | import android.view.View
4 | import androidx.databinding.BindingAdapter
5 | import androidx.recyclerview.widget.RecyclerView
6 | import com.example.home.data.HomeSection
7 | import com.example.remote.data.Resource
8 | import com.example.uicomponents.helpers.gone
9 | import com.example.uicomponents.helpers.visible
10 |
11 | object HomeBinding {
12 | @BindingAdapter("app:items")
13 | @JvmStatic
14 | fun setItems(recyclerView: RecyclerView, resource: Resource>?) {
15 | with(recyclerView.adapter as HomeAdapter) {
16 | resource?.data?.let { updateList(it.toMutableList()) }
17 | }
18 | }
19 |
20 |
21 |
22 |
23 | @BindingAdapter("app:showWhenLoading")
24 | @JvmStatic
25 | fun showWhenLoading(view: View, resource: Resource?) {
26 | resource?.let {
27 | if (resource.status == Resource.Status.LOADING){
28 | view.visible()
29 | }else{
30 | view.gone()
31 | }
32 | }
33 |
34 | }
35 |
36 |
37 | }
--------------------------------------------------------------------------------
/features/home/src/main/java/com/example/home/presenter/HomeInnerAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.example.home.presenter
2 |
3 | import android.util.Log
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.example.common.ApiConfigs.IMAGE_BASE_URL
8 | import com.example.home.Constants
9 | import com.example.home.R
10 | import com.example.home.data.HomeSection
11 | import com.example.uicomponents.helpers.inflate
12 | import kotlinx.android.synthetic.main.category_item.view.*
13 | import kotlinx.android.synthetic.main.product_item.view.*
14 |
15 | class HomeInnerAdapter(val section: HomeSection) : RecyclerView.Adapter() {
16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
17 | when (section.viewType) {
18 | Constants.PRODUCT_VIEW_TYPE ->{
19 | return ProductViewHolder(parent.inflate(R.layout.product_item))
20 | }
21 | else -> {
22 | return CategoryViewHolder(parent.inflate(R.layout.category_item))
23 | }
24 | }
25 | }
26 |
27 | override fun getItemCount(): Int {
28 | Log.d("viewType",section.toString())
29 | section.categories?.let {
30 | return it.size
31 | }
32 | section.products?.let {
33 | return it.size
34 | }
35 | return 0
36 | }
37 |
38 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
39 | when (holder) {
40 | is CategoryViewHolder -> {
41 | val item = section.categories?.get(position)
42 | holder.itemView.categoryView.apply {
43 | item?.let {
44 | setTitle(it.name)
45 | setCover("${IMAGE_BASE_URL}${it.imageUrl}")
46 | }
47 |
48 | }
49 |
50 | }
51 | is ProductViewHolder -> {
52 | val item = section.products?.get(position)
53 | holder.itemView.productView.apply {
54 | item?.let {
55 | setTitle(it.title!!)
56 | setCover("${IMAGE_BASE_URL}${it.imageUrl}")
57 | setPrice("$${it.price}")
58 | }
59 |
60 | }
61 | }
62 | }
63 | }
64 |
65 | class CategoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
66 | class ProductViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
67 |
68 |
69 |
70 |
71 | }
--------------------------------------------------------------------------------
/features/home/src/main/java/com/example/home/presenter/HomeViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.example.home.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.home.data.HomeSection
8 | import com.example.home.domain.GetHomeUserCase
9 | import com.example.remote.data.Resource
10 | import kotlinx.coroutines.flow.collect
11 | import kotlinx.coroutines.launch
12 |
13 | class HomeViewModel(private val useCase: GetHomeUserCase) : ViewModel() {
14 | private val _homeSections = MutableLiveData>>()
15 | val homeSectionsLD: LiveData>> get() = _homeSections
16 | fun gethome() {
17 | viewModelScope.launch {
18 | useCase.invoke().collect { data ->
19 | _homeSections.value = data
20 | }
21 | }
22 |
23 | }
24 | }
--------------------------------------------------------------------------------
/features/home/src/main/res/layout/activity_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
11 |
12 |
17 |
18 |
19 |
20 |
32 |
42 |
43 |
--------------------------------------------------------------------------------
/features/home/src/main/res/layout/category_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
--------------------------------------------------------------------------------
/features/home/src/main/res/layout/home_adapter_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
11 |
14 |
15 |
16 |
19 |
20 |
29 |
30 |
40 |
41 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/features/home/src/main/res/layout/home_inner_view_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/features/home/src/main/res/layout/product_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/features/home/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/features/home/src/test/java/com/example/home/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.home
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/splash/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/features/splash/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 | localRoomDependencies()
11 | implementation(project(ModulesDependency.COMMON))
12 | implementation(project(ModulesDependency.REMOTE))
13 | implementation(project(ModulesDependency.MODEL))
14 | implementation(project(ModulesDependency.LOCAL))
15 |
16 | addTestDependencies()
17 |
18 |
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/features/splash/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/features/splash/consumer-rules.pro
--------------------------------------------------------------------------------
/features/splash/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/splash/src/androidTest/java/com/example/splash/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.splash
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.splash.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/features/splash/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/features/splash/src/main/java/com/example/splash/SplashActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.splash
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import android.view.View
6 | import androidx.lifecycle.Observer
7 | import com.example.common.BaseActivity
8 | import com.example.remote.data.Resource
9 | import com.example.splash.databinding.ActivitySplashBinding
10 | import com.example.splash.presenter.SplashViewModel
11 | import org.koin.androidx.viewmodel.ext.android.viewModel
12 |
13 | class SplashActivity : BaseActivity() {
14 | private val TAG = "Splash Activity"
15 | val splashViewModel: SplashViewModel by viewModel()
16 | override fun onCreate(
17 | instance: Bundle?,
18 | binding: ActivitySplashBinding
19 | ) {
20 | binding.centerTextTv.text = "this is center center data from binding......"
21 | binding.centerTextTv.setOnClickListener {
22 | test()
23 | }
24 |
25 |
26 | splashViewModel.postsLD.observe(this, Observer { dataState ->
27 | dataState?.let {
28 | if(dataState.status == Resource.Status.LOADING){
29 | binding.loader.visibility = View.VISIBLE
30 | dataState.data?.let {
31 | binding.loadingData.text = dataState.data?.get(0)?.title
32 | }
33 | }
34 |
35 | if(dataState.status == Resource.Status.SUCCESS){
36 | binding.loader.visibility = View.GONE
37 | binding.centerTextTv.text = dataState.data?.get(0)?.title
38 | }
39 | }
40 |
41 | })
42 |
43 |
44 | }
45 |
46 | private fun test() {
47 | splashViewModel.getBlogPosts()
48 | }
49 |
50 | override val layoutId: Int
51 | get() = R.layout.activity_splash
52 |
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/features/splash/src/main/java/com/example/splash/data/SplashRepository.kt:
--------------------------------------------------------------------------------
1 | package com.example.splash.data
2 |
3 | import com.example.model.BlogPostApi
4 | import com.example.remote.NetworkBoundResource
5 | import com.example.remote.data.Resource
6 | import com.example.splash.data.datasource.local.BlogPostLocal
7 | import com.example.splash.data.datasource.remote.BlogPostRemote
8 | import kotlinx.coroutines.flow.Flow
9 |
10 | class SplashRepository(val remote: BlogPostRemote, val local: BlogPostLocal) {
11 | fun getPosts(): Flow>> {
12 | return object : NetworkBoundResource>() {
13 | override suspend fun saveFetchResult(data: List) {
14 | if (data.isNotEmpty()) {
15 | data.forEach { blog ->
16 | local.insertPost(blog)
17 | }
18 | }
19 | }
20 |
21 | override suspend fun remoteFetch(): List {
22 | return remote.getBlogPosts()
23 | }
24 |
25 | override suspend fun localFetch(): List {
26 | return local.getBlogPosts()
27 | }
28 |
29 | override fun shouldFetchWithLocalData() = true
30 | }.asFlow()
31 |
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/features/splash/src/main/java/com/example/splash/data/SplashServices.kt:
--------------------------------------------------------------------------------
1 | package com.example.splash.data
2 |
3 | import com.example.model.BlogPostApi
4 | import kotlinx.coroutines.flow.Flow
5 | import retrofit2.http.GET
6 | import retrofit2.http.POST
7 |
8 |
9 | interface SplashServices {
10 | @GET("placeholder/blogs")
11 | suspend fun getBlogPost(): List
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/features/splash/src/main/java/com/example/splash/data/datasource/local/BlogPostLocal.kt:
--------------------------------------------------------------------------------
1 | package com.example.splash.data.datasource.local
2 |
3 | import com.example.model.BlogPostApi
4 |
5 | interface BlogPostLocal {
6 | suspend fun getBlogPosts(): List
7 |
8 | suspend fun insertPost(blog:BlogPostApi)
9 |
10 | }
--------------------------------------------------------------------------------
/features/splash/src/main/java/com/example/splash/data/datasource/local/BlogPostLocalImpl.kt:
--------------------------------------------------------------------------------
1 | package com.example.splash.data.datasource.local
2 |
3 | import com.example.dao.BlogPostDao
4 | import com.example.model.BlogPostApi
5 |
6 | class BlogPostLocalImpl(val local: BlogPostDao) : BlogPostLocal {
7 | override suspend fun getBlogPosts(): List {
8 | return local.getAllPosts()
9 | }
10 |
11 | override suspend fun insertPost(blog: BlogPostApi) {
12 | local.insert(blog)
13 | }
14 | }
--------------------------------------------------------------------------------
/features/splash/src/main/java/com/example/splash/data/datasource/remote/BlogPostRemote.kt:
--------------------------------------------------------------------------------
1 | package com.example.splash.data.datasource.remote
2 |
3 | import com.example.model.BlogPostApi
4 |
5 | interface BlogPostRemote {
6 | suspend fun getBlogPosts(): List
7 | }
--------------------------------------------------------------------------------
/features/splash/src/main/java/com/example/splash/data/datasource/remote/BlogPostRemoteImpl.kt:
--------------------------------------------------------------------------------
1 | package com.example.splash.data.datasource.remote
2 |
3 | import com.example.model.BlogPostApi
4 | import com.example.splash.data.SplashServices
5 |
6 | class BlogPostRemoteImpl(val api: SplashServices) : BlogPostRemote{
7 | override suspend fun getBlogPosts(): List {
8 | return api.getBlogPost()
9 | }
10 | }
--------------------------------------------------------------------------------
/features/splash/src/main/java/com/example/splash/di/di.kt:
--------------------------------------------------------------------------------
1 | package com.example.splash.di
2 |
3 | import com.example.splash.data.SplashRepository
4 | import com.example.splash.data.SplashServices
5 | import com.example.splash.data.datasource.local.BlogPostLocal
6 | import com.example.splash.data.datasource.local.BlogPostLocalImpl
7 | import com.example.splash.data.datasource.remote.BlogPostRemote
8 | import com.example.splash.data.datasource.remote.BlogPostRemoteImpl
9 | import com.example.splash.domain.GetBlogPostsUseCase
10 | import com.example.splash.presenter.SplashViewModel
11 | import org.koin.androidx.viewmodel.dsl.viewModel
12 | import org.koin.dsl.module
13 | import retrofit2.Retrofit
14 |
15 | val splashModule = module {
16 | viewModel { SplashViewModel(get()) }
17 | factory {
18 | SplashRepository(get(), get())
19 | }
20 |
21 | factory {
22 | GetBlogPostsUseCase(get())
23 | }
24 |
25 | factory {
26 | BlogPostLocalImpl(get())
27 | }
28 |
29 | factory {
30 | BlogPostRemoteImpl(get())
31 | }
32 |
33 |
34 | single {
35 | get().create(SplashServices::class.java)
36 | }
37 | }
--------------------------------------------------------------------------------
/features/splash/src/main/java/com/example/splash/domain/GetBlogPostsUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.example.splash.domain
2 |
3 | import com.example.model.BlogPost
4 | import com.example.model.BlogPostApi
5 | import com.example.remote.data.Resource
6 | import com.example.splash.data.SplashRepository
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.flatMapLatest
9 | import kotlinx.coroutines.flow.flow
10 |
11 | class GetBlogPostsUseCase(val repo: SplashRepository) {
12 | fun getPosts(): Flow>> {
13 | return repo.getPosts().flatMapLatest { data -> map(data) }
14 | }
15 |
16 | suspend fun map(data: Resource>): Flow>> {
17 | return flow {
18 | val list = data.data?.map { BlogPost(it.pk, title = it.title) }
19 | emit(Resource(status = data.status, data = list, messageType = data.messageType))
20 | }
21 | }
22 | }
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/features/splash/src/main/java/com/example/splash/presenter/SplashViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.example.splash.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.dao.BlogPostDao
8 | import com.example.model.BlogPost
9 | import com.example.model.BlogPostApi
10 | import com.example.remote.data.Resource
11 | import com.example.splash.data.SplashRepository
12 | import kotlinx.coroutines.flow.collect
13 | import kotlinx.coroutines.launch
14 | import com.example.safeApiCall
15 | import com.example.splash.domain.GetBlogPostsUseCase
16 |
17 |
18 | class SplashViewModel(val useCase: GetBlogPostsUseCase) : ViewModel() {
19 |
20 |
21 | private val _posts = MutableLiveData>>()
22 | val postsLD: LiveData>> get() = _posts
23 | fun getBlogPosts() {
24 | viewModelScope.launch {
25 | useCase.getPosts().collect { data ->
26 | _posts.value = data
27 | }
28 | }
29 |
30 | }
31 | }
--------------------------------------------------------------------------------
/features/splash/src/main/res/layout/activity_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
18 |
19 |
32 |
33 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/features/splash/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/features/splash/src/test/java/com/example/CoroutinesMainDispatcherRule.kt:
--------------------------------------------------------------------------------
1 | package com.example
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.ExperimentalCoroutinesApi
5 | import kotlinx.coroutines.asCoroutineDispatcher
6 | import kotlinx.coroutines.test.resetMain
7 | import kotlinx.coroutines.test.setMain
8 | import org.junit.rules.TestWatcher
9 | import org.junit.runner.Description
10 | import java.util.concurrent.Executors
11 |
12 | @ExperimentalCoroutinesApi
13 | class CoroutinesMainDispatcherRule : TestWatcher() {
14 |
15 | private val singleThreadExecutor = Executors.newSingleThreadExecutor()
16 |
17 | override fun starting(description: Description?) {
18 | super.starting(description)
19 | Dispatchers.setMain(singleThreadExecutor.asCoroutineDispatcher())
20 | }
21 |
22 | override fun finished(description: Description?) {
23 | super.finished(description)
24 | singleThreadExecutor.shutdownNow()
25 | Dispatchers.resetMain()
26 | }
27 | }
--------------------------------------------------------------------------------
/features/splash/src/test/java/com/example/splash/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.splash
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/splash/src/test/java/com/example/splash/data/SplashRepositoryTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.splash.data
2 |
3 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule
4 | import androidx.lifecycle.Observer
5 | import androidx.lifecycle.asLiveData
6 | import com.example.CoroutinesMainDispatcherRule
7 | import com.example.dao.BlogPostDao
8 | import com.example.model.BlogPost
9 | import com.example.model.BlogPostApi
10 | import com.example.remote.data.Resource
11 | import com.example.splash.data.datasource.local.BlogPostLocalImpl
12 | import com.example.splash.data.datasource.remote.BlogPostRemoteImpl
13 | import io.mockk.*
14 | import kotlinx.coroutines.flow.FlowCollector
15 | import kotlinx.coroutines.flow.collect
16 | import kotlinx.coroutines.runBlocking
17 | import org.junit.Assert.*
18 | import org.junit.Before
19 | import org.junit.Rule
20 | import org.junit.Test
21 | import java.util.*
22 |
23 | class SplashRepositoryTest {
24 | @get:Rule
25 | val instantExecutorRule = InstantTaskExecutorRule()
26 |
27 | @get:Rule
28 | var coroutinesMainDispatcherRule = CoroutinesMainDispatcherRule()
29 |
30 | lateinit var SUT: SplashRepository
31 | var api = spyk()
32 | var dao = spyk()
33 | var blogLocalDataSource = spyk(BlogPostLocalImpl(dao))
34 | var remoteDataSource = spyk(BlogPostRemoteImpl(api))
35 | private lateinit var collector: FlowCollector>>
36 |
37 | @Before
38 | fun setUp() {
39 | collector = mockk(relaxed = true)
40 | SUT = SplashRepository(remoteDataSource, blogLocalDataSource)
41 | }
42 |
43 | @Test
44 | fun getBlogsAndSuccess() {
45 | //arrange
46 | val fakePosts = FakeData.createFakePostsApi(10)
47 | coEvery { remoteDataSource.getBlogPosts() } returns fakePosts
48 | coEvery { blogLocalDataSource.getBlogPosts() } returns fakePosts
49 | runBlocking {
50 | SUT.getPosts().collect(collector)
51 | }
52 |
53 | coVerifyOrder {
54 | collector.emit(Resource.loading(null))
55 | collector.emit(Resource.loading(data = fakePosts))
56 | collector.emit(Resource.success(data = fakePosts))
57 | }
58 | }
59 |
60 |
61 | object FakeData {
62 | fun createFakePostsApi(count: Int): List {
63 | return (0 until count).map {
64 | createFakePostApi(it.toString())
65 | }
66 | }
67 |
68 | fun createFakePostApi(id: String): BlogPostApi {
69 | return BlogPostApi(pk = id, title = "title$id")
70 |
71 | }
72 |
73 |
74 | fun createFakePosts(count: Int): List {
75 | return (0 until count).map {
76 | createFakePost(it.toString())
77 | }
78 | }
79 |
80 | fun createFakePost(id: String): BlogPost {
81 | return BlogPost(pk = id, title = "title$id")
82 |
83 | }
84 |
85 | }
86 | }
--------------------------------------------------------------------------------
/features/splash/src/test/java/com/example/splash/domain/GetBlogPostsUseCaseTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.splash.domain
2 |
3 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule
4 | import com.example.CoroutinesMainDispatcherRule
5 | import com.example.dao.BlogPostDao
6 | import com.example.model.BlogPost
7 | import com.example.model.BlogPostApi
8 | import com.example.remote.data.Resource
9 | import com.example.splash.data.SplashRepository
10 | import com.example.splash.data.SplashRepositoryTest
11 | import com.example.splash.data.SplashServices
12 | import com.example.splash.data.datasource.local.BlogPostLocalImpl
13 | import com.example.splash.data.datasource.remote.BlogPostRemoteImpl
14 | import io.mockk.coEvery
15 | import io.mockk.coVerifyOrder
16 | import io.mockk.mockk
17 | import io.mockk.spyk
18 | import kotlinx.coroutines.flow.FlowCollector
19 | import kotlinx.coroutines.flow.flow
20 | import kotlinx.coroutines.runBlocking
21 | import org.junit.Assert.*
22 | import org.junit.Before
23 | import org.junit.Rule
24 | import org.junit.Test
25 |
26 | class GetBlogPostsUseCaseTest{
27 | @get:Rule
28 | val instantExecutorRule = InstantTaskExecutorRule()
29 |
30 | @get:Rule
31 | var coroutinesMainDispatcherRule = CoroutinesMainDispatcherRule()
32 |
33 | lateinit var SUT: GetBlogPostsUseCase
34 | var api = spyk()
35 | var dao = spyk()
36 | var blogLocalDataSource = spyk(BlogPostLocalImpl(dao))
37 | var remoteDataSource = spyk(BlogPostRemoteImpl(api))
38 | var repo = spyk(SplashRepository(remoteDataSource,blogLocalDataSource))
39 |
40 | private lateinit var collector: FlowCollector>>
41 |
42 |
43 |
44 | @Before
45 | fun setUp() {
46 | collector = mockk(relaxed = true)
47 | SUT = GetBlogPostsUseCase(repo)
48 | }
49 |
50 |
51 | @Test
52 | fun getBlogPostsAndSuccess() {
53 | val fakePostsApi = SplashRepositoryTest.FakeData.createFakePostsApi(10)
54 | val fakePosts = SplashRepositoryTest.FakeData.createFakePosts(10)
55 |
56 | coEvery { repo.getPosts() } returns flow { emit(Resource.success(data = fakePostsApi)) }
57 |
58 | runBlocking {
59 | SUT.getPosts().collect(collector = collector)
60 | }
61 | coVerifyOrder{
62 | collector.emit(Resource.success(data = fakePosts))
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/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/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Mar 04 15:20:11 EET 2020
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.6.4-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 |
--------------------------------------------------------------------------------
/libraries/common/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/libraries/common/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 | // support
12 | commonDevelopmentDependencies()
13 |
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/libraries/common/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/libraries/common/consumer-rules.pro
--------------------------------------------------------------------------------
/libraries/common/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 |
--------------------------------------------------------------------------------
/libraries/common/src/androidTest/java/com/example/common/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.common
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.common.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/libraries/common/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/libraries/common/src/main/java/com/example/common/ApiConfigs.kt:
--------------------------------------------------------------------------------
1 | package com.example.common
2 |
3 | object ApiConfigs {
4 |
5 | val IMAGE_BASE_URL = "http://192.168.1.6/blog/public/assets/img/"
6 | }
--------------------------------------------------------------------------------
/libraries/common/src/main/java/com/example/common/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.common
2 |
3 | import android.app.ProgressDialog
4 | import android.os.Bundle
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.databinding.DataBindingUtil
7 | import androidx.databinding.ViewDataBinding
8 |
9 | abstract class BaseActivity : AppCompatActivity(), UiCommunicator {
10 |
11 | private lateinit var currentViewBinding: B
12 | private var progress: ProgressDialog? = null
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | currentViewBinding = DataBindingUtil.setContentView(this, layoutId) as B
16 | onCreate(savedInstanceState, binding = currentViewBinding)
17 | }
18 |
19 |
20 | override fun showLoading() {
21 | progress?.dismiss() ?: kotlin.run {
22 | progress = ProgressDialog(this)
23 | }
24 | progress?.show()
25 | }
26 |
27 | override fun hideLoading() {
28 | progress?.dismiss()
29 | }
30 |
31 | override fun handleMessages(messageType: MessageType) {
32 | var message = ErrorMessageHelper.getMessage(messageType.code)
33 | messageType.message?.let { message = it }
34 | when (messageType) {
35 | is MessageType.SnackBar -> {
36 | showSnackbar(message)
37 | }
38 | }
39 |
40 | }
41 |
42 |
43 | //all of this function needs future impl
44 |
45 | protected abstract fun onCreate(instance: Bundle?, binding: B)
46 | protected abstract val layoutId: Int
47 |
48 | protected fun getCurrentViewBinding(): B {
49 | return currentViewBinding
50 | }
51 | }
--------------------------------------------------------------------------------
/libraries/common/src/main/java/com/example/common/BaseFragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.common
2 |
3 | import android.content.Context
4 | import androidx.fragment.app.Fragment
5 |
6 | open class BaseFragment(layoutID:Int) : Fragment(layoutID) {
7 |
8 | var uiCommunicator: UiCommunicator? = null
9 |
10 | override fun onAttach(context: Context) {
11 | super.onAttach(context)
12 | if (context is UiCommunicator) {
13 | uiCommunicator = context
14 | } else {
15 | throw Exception("you must implement uiCommunicator listener ")
16 | }
17 | }
18 |
19 | override fun onDetach() {
20 | super.onDetach()
21 | uiCommunicator = null
22 | }
23 | }
--------------------------------------------------------------------------------
/libraries/common/src/main/java/com/example/common/ErrorMessageHelper.kt:
--------------------------------------------------------------------------------
1 | package com.example.common
2 |
3 | object ErrorMessageHelper {
4 |
5 | fun getMessage(code:Int): String {
6 | return when(code){
7 | NetworkCodes.CONNECTION_ERROR->{
8 | "Your connection lost check it"
9 | }
10 | else->{
11 | "unknown error"
12 | }
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/libraries/common/src/main/java/com/example/common/MessageType.kt:
--------------------------------------------------------------------------------
1 | package com.example.common
2 |
3 | sealed class MessageType(var code:Int,var message:String? = null){
4 | class SnackBar(code:Int, message:String? = null) : MessageType(code,message)
5 | class Dialog(code:Int,message:String? = null) : MessageType(code,message)
6 | class Toast(code:Int,message:String? = null): MessageType(code,message)
7 | class None(code:Int,message:String? = null): MessageType(code,message)
8 | }
--------------------------------------------------------------------------------
/libraries/common/src/main/java/com/example/common/NetworkCodes.kt:
--------------------------------------------------------------------------------
1 | package com.example.common
2 |
3 | object NetworkCodes {
4 | const val CONNECTION_ERROR = 100
5 | const val TIMEOUT_ERROR = 408
6 | const val GENERAL_ERROR = 999
7 |
8 | }
--------------------------------------------------------------------------------
/libraries/common/src/main/java/com/example/common/UiCommunicator.kt:
--------------------------------------------------------------------------------
1 | package com.example.common
2 |
3 | interface UiCommunicator {
4 | fun showLoading()
5 | fun hideLoading()
6 | fun handleMessages(messageType: MessageType)
7 | }
--------------------------------------------------------------------------------
/libraries/common/src/main/java/com/example/common/extensions.kt:
--------------------------------------------------------------------------------
1 | package com.example.common
2 |
3 | import android.view.View
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.fragment.app.Fragment
6 | import com.google.android.material.snackbar.Snackbar
7 |
8 | fun AppCompatActivity.showSnackbar(snackbarText: String, timeLength: Int = 2000) {
9 | Snackbar.make(findViewById(android.R.id.content), snackbarText, timeLength).show()
10 | }
11 |
12 | fun Fragment.showSnackbar(snackbarText: String, timeLength: Int = 2000) {
13 | (activity as AppCompatActivity).showSnackbar(snackbarText, timeLength)
14 | }
--------------------------------------------------------------------------------
/libraries/common/src/test/java/com/example/common/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.common
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 |
--------------------------------------------------------------------------------
/libraries/uicomponents/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/libraries/uicomponents/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 | // support
12 | commonDevelopmentDependencies()
13 |
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/libraries/uicomponents/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/libraries/uicomponents/consumer-rules.pro
--------------------------------------------------------------------------------
/libraries/uicomponents/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 |
--------------------------------------------------------------------------------
/libraries/uicomponents/src/androidTest/java/com/example/uicomponents/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.uicomponents
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.uicomponents.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/java/com/example/uicomponents/category/CategoryView.kt:
--------------------------------------------------------------------------------
1 | package com.example.uicomponents.category
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.View
6 | import androidx.constraintlayout.widget.ConstraintLayout
7 | import com.example.uicomponents.R
8 | import com.example.uicomponents.helpers.loadImageFromUrl
9 | import kotlinx.android.synthetic.main.category_view.view.*
10 |
11 | class CategoryView:ConstraintLayout
12 | {
13 | constructor(context: Context) : this(context, null)
14 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
15 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
16 | init(context)
17 |
18 | }
19 |
20 | private fun init(context: Context) {
21 | View.inflate(context, R.layout.category_view, this)
22 | }
23 |
24 | fun setCover(res:Any){
25 | cover.loadImageFromUrl(res)
26 | }
27 |
28 | fun setTitle(text:String){
29 | titleTv.text = text
30 | }
31 | }
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/java/com/example/uicomponents/helpers/UiExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.example.uicomponents.helpers
2 |
3 | import android.content.Context
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.ImageView
8 | import android.widget.Toast
9 | import androidx.core.content.ContextCompat
10 | import com.squareup.picasso.Picasso
11 |
12 | fun ImageView.loadImageFromUrl(url: Any?) {
13 | url?.let {
14 | if (url is Int) {
15 | Picasso.get()
16 | .load(url)
17 | .into(this)
18 |
19 | setImageResource(url)
20 | }
21 |
22 |
23 | if (url is String)
24 | Picasso.get()
25 | .load(url)
26 | .into(this)
27 |
28 | }
29 |
30 |
31 | }
32 |
33 |
34 | fun View.gone() {
35 | if (visibility != View.GONE) {
36 | visibility = View.GONE
37 | }
38 | }
39 |
40 | /**
41 | * Extension method to provide show keyboard for View.
42 | */
43 | fun View.visible() {
44 | if (visibility != View.VISIBLE) {
45 | visibility = View.VISIBLE
46 | }
47 | }
48 |
49 | fun View.invisible() {
50 | if (visibility != View.INVISIBLE) {
51 | visibility = View.INVISIBLE
52 | }
53 | }
54 |
55 | fun ViewGroup.inflate(id: Int): View {
56 | return LayoutInflater.from(context).inflate(id, this, false)
57 | }
58 |
59 | fun Context.toast(message: String, duration: Int = Toast.LENGTH_SHORT) {
60 | Toast.makeText(this, message, duration).show()
61 | }
62 |
63 |
64 | fun Context.getColorRes(resId: Int): Int {
65 | return ContextCompat.getColor(this, resId)
66 | }
67 |
68 | fun Context.dp(dp: Int): Int = (dp * resources.displayMetrics.density).toInt()
69 |
70 |
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/java/com/example/uicomponents/product/ProductView.kt:
--------------------------------------------------------------------------------
1 | package com.example.uicomponents.product
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.View
6 | import androidx.constraintlayout.widget.ConstraintLayout
7 | import com.example.uicomponents.R
8 | import com.example.uicomponents.helpers.loadImageFromUrl
9 | import kotlinx.android.synthetic.main.product_view.view.*
10 |
11 | class ProductView : ConstraintLayout {
12 | constructor(context: Context) : this(context, null)
13 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
14 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
15 | context,
16 | attrs,
17 | defStyleAttr
18 | ) {
19 | init(context)
20 |
21 | }
22 |
23 | private fun init(context: Context) {
24 | View.inflate(context, R.layout.product_view, this)
25 | }
26 |
27 | fun setCover(res: Any) {
28 | cover.loadImageFromUrl(res)
29 | }
30 |
31 | fun setTitle(text: String) {
32 | titleTv.text = text
33 | }
34 |
35 | fun setPrice(text: String) {
36 | priceTv.text = text
37 | }
38 | }
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/res/anim/fade_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/res/anim/fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/res/anim/slide_in_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/res/anim/slide_in_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/res/anim/slide_out_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/res/anim/slide_out_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/res/drawable/rounded_overlay.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/res/font/raleway_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/libraries/uicomponents/src/main/res/font/raleway_bold.ttf
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/res/font/raleway_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/libraries/uicomponents/src/main/res/font/raleway_italic.ttf
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/res/font/raleway_medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/libraries/uicomponents/src/main/res/font/raleway_medium.ttf
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/res/font/raleway_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/libraries/uicomponents/src/main/res/font/raleway_regular.ttf
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/res/font/raleway_semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/App-Modularization-Clean-Code-Exmple/4425b7fb5638b7e89413b9b1615ef32b03baffac/libraries/uicomponents/src/main/res/font/raleway_semibold.ttf
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/res/layout/category_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
19 |
23 |
24 |
25 |
37 |
38 |
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/res/layout/product_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
28 |
29 |
40 |
41 |
--------------------------------------------------------------------------------
/libraries/uicomponents/src/main/res/values/color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #2A2A2A
4 | #434343
5 | #6200EE
6 | #3700B3
7 | #673AB7
8 |
--------------------------------------------------------------------------------
/libraries/uicomponents/src/test/java/com/example/uicomponents/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.uicomponents
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 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name='App Modularization example'
2 | include ':app'
3 | include ':libraries:common'
4 | include ':features:splash'
5 | include ':data:remote'
6 | include ':data:local'
7 | include ':data:model'
8 | include ':libraries:uicomponents'
9 | include ':features:home'
10 | include ':features:auth'
11 |
--------------------------------------------------------------------------------