├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── iamkamrul
│ │ └── githubrepo
│ │ └── ExampleInstrumentedTest.kt
│ ├── debug
│ └── java
│ │ └── com
│ │ └── iamkamrul
│ │ └── githubrepo
│ │ └── BaseUrlModule.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── iamkamrul
│ │ │ └── githubrepo
│ │ │ ├── MainActivity.kt
│ │ │ ├── application
│ │ │ └── GithubRepoApplication.kt
│ │ │ └── di
│ │ │ ├── ApplicationModule.kt
│ │ │ └── RepositoryModule.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ └── content_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ ├── release
│ └── java
│ │ └── com
│ │ └── iamkamrul
│ │ └── githubrepo
│ │ └── BaseUrlModule.kt
│ └── test
│ └── java
│ └── com
│ └── iamkamrul
│ └── githubrepo
│ └── ExampleUnitTest.kt
├── build.gradle.kts
├── buildSrc
├── build.gradle.kts
└── src
│ └── main
│ └── kotlin
│ ├── ProjectConfigs.kt
│ ├── core
│ ├── Dependencies.kt
│ ├── ModulesDep.kt
│ └── Versions.kt
│ ├── dependencies
│ ├── GroupedDepHandlerExtension.kt
│ ├── GroupedDependencies.kt
│ ├── ModuleGroupedDepHandlerExtension.kt
│ └── ModuleGroupedDependencies.kt
│ └── plugins
│ ├── android-base-library-core.gradle.kts
│ ├── android-base-library-feature.gradle.kts
│ └── android-base-library.gradle.kts
├── common
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── iamkamrul
│ │ └── common
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── iamkamrul
│ │ │ └── common
│ │ │ ├── adapter
│ │ │ ├── DataBoundListAdapter.kt
│ │ │ └── DataBoundViewHolder.kt
│ │ │ ├── base
│ │ │ ├── BaseFragment.kt
│ │ │ └── BaseViewModel.kt
│ │ │ └── extension
│ │ │ └── RecyclerviewExtension.kt
│ └── res
│ │ ├── drawable
│ │ ├── ic_baseline_circle_24.xml
│ │ ├── ic_baseline_fork_24.xml
│ │ └── ic_baseline_star_24.xml
│ │ ├── values-land
│ │ └── dimens.xml
│ │ ├── values-night
│ │ ├── colors.xml
│ │ └── themes.xml
│ │ ├── values-v29
│ │ └── themes.xml
│ │ ├── values-w1240dp
│ │ └── dimens.xml
│ │ ├── values-w600dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── test
│ └── java
│ └── com
│ └── iamkamrul
│ └── common
│ └── ExampleUnitTest.kt
├── data
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── iamkamrul
│ │ └── data
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── iamkamrul
│ │ └── data
│ │ ├── apiservice
│ │ ├── ApiService.kt
│ │ ├── ApiServiceModule.kt
│ │ └── NetworkBoundResources.kt
│ │ ├── mapper
│ │ ├── BaseMapper.kt
│ │ ├── RepositoryListItemMapper.kt
│ │ └── UserProfileMapper.kt
│ │ └── repoimpl
│ │ └── GithubRepoImpl.kt
│ └── test
│ └── java
│ └── com
│ └── iamkamrul
│ └── data
│ └── ExampleUnitTest.kt
├── di
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── iamkamrul
│ │ └── di
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── iamkamrul
│ │ └── di
│ │ ├── module
│ │ ├── OkHttpModule.kt
│ │ └── RetrofitModule.kt
│ │ └── qualifier
│ │ └── BaseUrl.kt
│ └── test
│ └── java
│ └── com
│ └── iamkamrul
│ └── di
│ └── ExampleUnitTest.kt
├── domain
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── iamkamrul
│ │ └── domain
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── iamkamrul
│ │ └── domain
│ │ ├── base
│ │ ├── BaseUsecase.kt
│ │ └── Result.kt
│ │ ├── repository
│ │ └── GithubRepository.kt
│ │ └── usecase
│ │ ├── RepositoryListUsecase.kt
│ │ └── UserProfileUsecase.kt
│ └── test
│ └── java
│ └── com
│ └── iamkamrul
│ └── domain
│ └── ExampleUnitTest.kt
├── features
├── profile
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── iamkamrul
│ │ │ └── profile
│ │ │ └── ExampleInstrumentedTest.kt
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── iamkamrul
│ │ │ │ └── profile
│ │ │ │ ├── ProfileFragment.kt
│ │ │ │ └── ProfileViewModel.kt
│ │ └── res
│ │ │ └── layout
│ │ │ └── fragment_profile.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── iamkamrul
│ │ └── profile
│ │ └── ExampleUnitTest.kt
└── repolist
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── iamkamrul
│ │ └── repolist
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── iamkamrul
│ │ │ └── repolist
│ │ │ ├── RepoListAdapter.kt
│ │ │ ├── RepoListFragment.kt
│ │ │ └── RepoListViewModel.kt
│ └── res
│ │ └── layout
│ │ ├── fragment_repo_list.xml
│ │ └── item_repo_list.xml
│ └── test
│ └── java
│ └── com
│ └── iamkamrul
│ └── repolist
│ └── ExampleUnitTest.kt
├── gradle.properties
├── gradle
└── wrapper
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── model
├── apiresponse
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── iamkamrul
│ │ └── apiresponse
│ │ ├── RepositoryListItemApiResponse.kt
│ │ └── UserProfileApiResponse.kt
└── entity
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ └── main
│ └── java
│ └── com
│ └── iamkamrul
│ └── entity
│ ├── RepositoryListItemEntity.kt
│ └── UserProfileEntity.kt
├── navigation
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── iamkamrul
│ │ └── navigation
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── res
│ │ └── navigation
│ │ └── nav_graph.xml
│ └── test
│ └── java
│ └── com
│ └── iamkamrul
│ └── navigation
│ └── ExampleUnitTest.kt
└── settings.gradle.kts
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/java,linux,macos,windows,android,intellij,androidstudio
2 | # Edit at https://www.gitignore.io/?templates=java,linux,macos,windows,android,intellij,androidstudio
3 |
4 | ### Android ###
5 | # Built application files
6 | *.apk
7 | *.ap_
8 | *.aab
9 |
10 | # Files for the ART/Dalvik VM
11 | *.dex
12 |
13 | # Java class files
14 | *.class
15 |
16 | # Generated files
17 | bin/
18 | gen/
19 | out/
20 |
21 | # Gradle files
22 | .gradle/
23 | build/
24 |
25 | # Local configuration file (sdk path, etc)
26 | local.properties
27 |
28 | # Proguard folder generated by Eclipse
29 | proguard/
30 |
31 | # Log Files
32 | *.log
33 |
34 | # Android Studio Navigation editor temp files
35 | .navigation/
36 |
37 | # Android Studio captures folder
38 | captures/
39 |
40 | # IntelliJ
41 | *.iml
42 | .idea/workspace.xml
43 | .idea/tasks.xml
44 | .idea/gradle.xml
45 | .idea/assetWizardSettings.xml
46 | .idea/dictionaries
47 | .idea/libraries
48 | .idea/caches
49 |
50 | # Keystore files
51 | # Uncomment the following lines if you do not want to check your keystore files in.
52 | #*.jks
53 | #*.keystore
54 |
55 | # External native build folder generated in Android Studio 2.2 and later
56 | .externalNativeBuild
57 |
58 | # Google Services (e.g. APIs or Firebase)
59 | google-services.json
60 |
61 | # Freeline
62 | freeline.py
63 | freeline/
64 | freeline_project_description.json
65 |
66 | # fastlane
67 | fastlane/report.xml
68 | fastlane/Preview.html
69 | fastlane/screenshots
70 | fastlane/test_output
71 | fastlane/readme.md
72 |
73 | ### Android Patch ###
74 | gen-external-apklibs
75 |
76 | ### AndroidStudio ###
77 | # Covers files to be ignored for android development using Android Studio.
78 |
79 | # Built application files
80 |
81 | # Files for the ART/Dalvik VM
82 |
83 | # Java class files
84 |
85 | # Generated files
86 |
87 | # Gradle files
88 | .gradle
89 |
90 | # Signing files
91 | .signing/
92 |
93 | # Local configuration file (sdk path, etc)
94 |
95 | # Proguard folder generated by Eclipse
96 |
97 | # Log Files
98 |
99 | # Android Studio
100 | /*/build/
101 | /*/local.properties
102 | /*/out
103 | /*/*/build
104 | /*/*/production
105 | *.ipr
106 | *~
107 | *.swp
108 |
109 | # Android Patch
110 |
111 | # External native build folder generated in Android Studio 2.2 and later
112 |
113 | # NDK
114 | obj/
115 |
116 | # IntelliJ IDEA
117 | *.iws
118 | /out/
119 |
120 | # User-specific configurations
121 | .idea/caches/
122 | .idea/libraries/
123 | .idea/shelf/
124 | .idea/.name
125 | .idea/compiler.xml
126 | .idea/copyright/profiles_settings.xml
127 | .idea/encodings.xml
128 | .idea/misc.xml
129 | .idea/modules.xml
130 | .idea/scopes/scope_settings.xml
131 | .idea/vcs.xml
132 | .idea/jsLibraryMappings.xml
133 | .idea/datasources.xml
134 | .idea/dataSources.ids
135 | .idea/sqlDataSources.xml
136 | .idea/dynamic.xml
137 | .idea/uiDesigner.xml
138 |
139 | # OS-specific files
140 | .DS_Store
141 | .DS_Store?
142 | ._*
143 | .Spotlight-V100
144 | .Trashes
145 | ehthumbs.db
146 | Thumbs.db
147 |
148 | # Legacy Eclipse project files
149 | .classpath
150 | .project
151 | .cproject
152 | .settings/
153 |
154 | # Mobile Tools for Java (J2ME)
155 | .mtj.tmp/
156 |
157 | # Package Files #
158 | *.war
159 | *.ear
160 |
161 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
162 | hs_err_pid*
163 |
164 | ## Plugin-specific files:
165 |
166 | # mpeltonen/sbt-idea plugin
167 | .idea_modules/
168 |
169 | # JIRA plugin
170 | atlassian-ide-plugin.xml
171 |
172 | # Mongo Explorer plugin
173 | .idea/mongoSettings.xml
174 |
175 | # Crashlytics plugin (for Android Studio and IntelliJ)
176 | com_crashlytics_export_strings.xml
177 | crashlytics.properties
178 | crashlytics-build.properties
179 | fabric.properties
180 |
181 | ### AndroidStudio Patch ###
182 |
183 | !/gradle/wrapper/gradle-wrapper.jar
184 |
185 | ### Intellij ###
186 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
187 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
188 |
189 | # User-specific stuff
190 | .idea/**/workspace.xml
191 | .idea/**/tasks.xml
192 | .idea/**/usage.statistics.xml
193 | .idea/**/dictionaries
194 | .idea/**/shelf
195 |
196 | # Generated files
197 | .idea/**/contentModel.xml
198 |
199 | # Sensitive or high-churn files
200 | .idea/**/dataSources/
201 | .idea/**/dataSources.ids
202 | .idea/**/dataSources.local.xml
203 | .idea/**/sqlDataSources.xml
204 | .idea/**/dynamic.xml
205 | .idea/**/uiDesigner.xml
206 | .idea/**/dbnavigator.xml
207 |
208 | # Gradle
209 | .idea/**/gradle.xml
210 | .idea/**/libraries
211 |
212 | # Gradle and Maven with auto-import
213 | # When using Gradle or Maven with auto-import, you should exclude module files,
214 | # since they will be recreated, and may cause churn. Uncomment if using
215 | # auto-import.
216 | # .idea/modules.xml
217 | # .idea/*.iml
218 | # .idea/modules
219 |
220 | # CMake
221 | cmake-build-*/
222 |
223 | # Mongo Explorer plugin
224 | .idea/**/mongoSettings.xml
225 |
226 | # File-based project format
227 |
228 | # IntelliJ
229 |
230 | # mpeltonen/sbt-idea plugin
231 |
232 | # JIRA plugin
233 |
234 | # Cursive Clojure plugin
235 | .idea/replstate.xml
236 |
237 | # Crashlytics plugin (for Android Studio and IntelliJ)
238 |
239 | # Editor-based Rest Client
240 | .idea/httpRequests
241 |
242 | # Android studio 3.1+ serialized cache file
243 | .idea/caches/build_file_checksums.ser
244 |
245 | ### Intellij Patch ###
246 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
247 |
248 | # *.iml
249 | # modules.xml
250 | # .idea/misc.xml
251 | # *.ipr
252 |
253 | # Sonarlint plugin
254 | .idea/sonarlint
255 |
256 | ### Java ###
257 | # Compiled class file
258 |
259 | # Log file
260 |
261 | # BlueJ files
262 | *.ctxt
263 |
264 | # Mobile Tools for Java (J2ME)
265 |
266 | # Package Files #
267 | *.jar
268 | *.nar
269 | *.zip
270 | *.tar.gz
271 | *.rar
272 |
273 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
274 |
275 | ### Linux ###
276 |
277 | # temporary files which can be created if a process still has a handle open of a deleted file
278 | .fuse_hidden*
279 |
280 | # KDE directory preferences
281 | .directory
282 |
283 | # Linux trash folder which might appear on any partition or disk
284 | .Trash-*
285 |
286 | # .nfs files are created when an open file is removed but is still being accessed
287 | .nfs*
288 |
289 | ### macOS ###
290 | # General
291 | .AppleDouble
292 | .LSOverride
293 |
294 | # Icon must end with two \r
295 | Icon
296 |
297 | # Thumbnails
298 |
299 | # Files that might appear in the root of a volume
300 | .DocumentRevisions-V100
301 | .fseventsd
302 | .TemporaryItems
303 | .VolumeIcon.icns
304 | .com.apple.timemachine.donotpresent
305 |
306 | # Directories potentially created on remote AFP share
307 | .AppleDB
308 | .AppleDesktop
309 | Network Trash Folder
310 | Temporary Items
311 | .apdisk
312 |
313 | ### Windows ###
314 | # Windows thumbnail cache files
315 | ehthumbs_vista.db
316 |
317 | # Dump file
318 | *.stackdump
319 |
320 | # Folder config file
321 | [Dd]esktop.ini
322 |
323 | # Recycle Bin used on file shares
324 | $RECYCLE.BIN/
325 |
326 | # Windows Installer files
327 | *.cab
328 | *.msi
329 | *.msix
330 | *.msm
331 | *.msp
332 |
333 | # Windows shortcuts
334 | *.lnk
335 |
336 | # End of https://www.gitignore.io/api/java,linux,macos,windows,android,intellij,androidstudio
337 | /.idea/jarRepositories.xml
338 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MVVM-Clean-Architecture-Modularization
2 | A clean architecture approach (multi-module) using Kotlin, Navigation Component, MVVM, Hilt, Kotlin Coroutines,Flow and Retrofit.
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import dependencies.*
2 |
3 | plugins {
4 | id("com.android.application")
5 | id("kotlin-android")
6 | id("kotlin-kapt")
7 | id("dagger.hilt.android.plugin")
8 | id("androidx.navigation.safeargs")
9 | }
10 |
11 | android {
12 | compileSdk = ProjectConfigs.compileSdkVersion
13 | defaultConfig {
14 | applicationId = ProjectConfigs.applicationId
15 | minSdk = ProjectConfigs.minSdkVersion
16 | targetSdk = ProjectConfigs.targetSdkVersion
17 | versionCode = 1
18 | versionName = "1.0.0"
19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20 | }
21 |
22 | buildTypes {
23 | release {
24 | isMinifyEnabled = false
25 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
26 | }
27 | }
28 | compileOptions {
29 | sourceCompatibility = JavaVersion.VERSION_11
30 | targetCompatibility = JavaVersion.VERSION_11
31 | }
32 | kotlinOptions {
33 | jvmTarget = "11"
34 | }
35 |
36 | buildFeatures {
37 | viewBinding = true
38 | }
39 | }
40 |
41 | dependencies {
42 | addFeatureModules()
43 | addDataModule()
44 | addDomainModule()
45 | addCommonModule()
46 |
47 | addAndroidxCoreDependencies()
48 | addAndroidxNavigationDependencies()
49 | addAndroidxLifeCycleDependencies()
50 | addViewStateDependencies()
51 | addHiltDependencies()
52 | addAndroidTestsDependencies()
53 | }
54 | kapt {
55 | correctErrorTypes = true
56 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.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
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/iamkamrul/githubrepo/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.githubrepo
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.iamkamrul.githubrepo", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/debug/java/com/iamkamrul/githubrepo/BaseUrlModule.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.githubrepo
2 |
3 | import com.iamkamrul.di.qualifier.BaseUrl
4 | import dagger.Module
5 | import dagger.Provides
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.components.SingletonComponent
8 |
9 | @Module
10 | @InstallIn(SingletonComponent::class)
11 | class BaseUrlModule {
12 | @Provides
13 | @BaseUrl
14 | fun provideBaseUrl():String = "https://api.github.com/"
15 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/iamkamrul/githubrepo/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.githubrepo
2 |
3 | import android.os.Bundle
4 | import com.google.android.material.snackbar.Snackbar
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.core.view.WindowCompat
7 | import androidx.navigation.findNavController
8 | import androidx.navigation.ui.AppBarConfiguration
9 | import androidx.navigation.ui.navigateUp
10 | import androidx.navigation.ui.setupActionBarWithNavController
11 | import android.view.Menu
12 | import android.view.MenuItem
13 | import com.iamkamrul.githubrepo.databinding.ActivityMainBinding
14 | import dagger.hilt.android.AndroidEntryPoint
15 |
16 | @AndroidEntryPoint
17 | class MainActivity : AppCompatActivity() {
18 |
19 | private lateinit var appBarConfiguration: AppBarConfiguration
20 | private lateinit var binding: ActivityMainBinding
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | WindowCompat.setDecorFitsSystemWindows(window, false)
24 | super.onCreate(savedInstanceState)
25 |
26 | binding = ActivityMainBinding.inflate(layoutInflater)
27 | setContentView(binding.root)
28 |
29 | setSupportActionBar(binding.toolbar)
30 |
31 | val navController = findNavController(R.id.nav_host_fragment_content_main)
32 | appBarConfiguration = AppBarConfiguration(navController.graph)
33 | setupActionBarWithNavController(navController, appBarConfiguration)
34 |
35 | }
36 |
37 | override fun onSupportNavigateUp(): Boolean {
38 | val navController = findNavController(R.id.nav_host_fragment_content_main)
39 | return navController.navigateUp(appBarConfiguration)
40 | || super.onSupportNavigateUp()
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/iamkamrul/githubrepo/application/GithubRepoApplication.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.githubrepo.application
2 |
3 | import android.app.Application
4 | import com.iamkamrul.githubrepo.BuildConfig
5 | import dagger.hilt.android.HiltAndroidApp
6 | import timber.log.Timber
7 |
8 | @HiltAndroidApp
9 | class GithubRepoApplication : Application(){
10 | override fun onCreate() {
11 | super.onCreate()
12 | if (BuildConfig.DEBUG){
13 | Timber.plant(Timber.DebugTree())
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/iamkamrul/githubrepo/di/ApplicationModule.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.githubrepo.di
2 |
3 | import dagger.Module
4 | import dagger.hilt.InstallIn
5 | import dagger.hilt.components.SingletonComponent
6 |
7 | @Module
8 | @InstallIn(SingletonComponent::class)
9 | class ApplicationModule {
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/iamkamrul/githubrepo/di/RepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.githubrepo.di
2 |
3 | import com.iamkamrul.data.repoimpl.GithubRepoImpl
4 | import com.iamkamrul.domain.repository.GithubRepository
5 | import dagger.Binds
6 | import dagger.Module
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 |
10 | @Module
11 | @InstallIn(SingletonComponent::class)
12 | interface RepositoryModule {
13 |
14 | @Binds
15 | fun bindGithubRepository(githubRepoImpl: GithubRepoImpl):GithubRepository
16 |
17 | }
--------------------------------------------------------------------------------
/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 |
9 |
10 |
14 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
19 |
20 |
--------------------------------------------------------------------------------
/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.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/release/java/com/iamkamrul/githubrepo/BaseUrlModule.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.githubrepo
2 |
3 | import com.iamkamrul.di.qualifier.BaseUrl
4 | import dagger.Module
5 | import dagger.Provides
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.components.SingletonComponent
8 |
9 | @Module
10 | @InstallIn(SingletonComponent::class)
11 | class BaseUrlModule {
12 | @Provides
13 | @BaseUrl
14 | fun provideBaseUrl():String = "https://api.github.com/"
15 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/iamkamrul/githubrepo/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.githubrepo
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 | }
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | mavenLocal()
4 | mavenCentral()
5 | google()
6 | maven ("https://jitpack.io")
7 | maven ("https://oss.jfrog.org/libs-snapshot")
8 | }
9 | dependencies {
10 | classpath(ProjectConfigs.buildGradle)
11 | classpath(ProjectConfigs.kotlinGradlePlugin)
12 | classpath(ProjectConfigs.hiltAndroidGradlePlugin)
13 | classpath(ProjectConfigs.navSafeArgs)
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | mavenLocal()
20 | mavenCentral()
21 | google()
22 | maven ("https://jitpack.io")
23 | maven ("https://oss.jfrog.org/libs-snapshot")
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.gradle.kotlin.dsl.`kotlin-dsl`
2 | plugins {
3 | `kotlin-dsl`
4 | }
5 |
6 | buildscript {
7 | repositories {
8 | mavenLocal()
9 | mavenCentral()
10 | google()
11 | maven ("https://jitpack.io")
12 | maven ("https://oss.jfrog.org/libs-snapshot")
13 | }
14 | }
15 | repositories {
16 | mavenLocal()
17 | mavenCentral()
18 | google()
19 | maven ("https://jitpack.io")
20 | maven ("https://oss.jfrog.org/libs-snapshot")
21 | }
22 | dependencies {
23 | compileOnly(gradleKotlinDsl())
24 | implementation("com.android.tools.build:gradle:7.2.1")
25 | implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21")
26 | implementation("com.google.dagger:hilt-android-gradle-plugin:2.39.1")
27 | implementation("androidx.navigation:navigation-safe-args-gradle-plugin:2.4.2")
28 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/ProjectConfigs.kt:
--------------------------------------------------------------------------------
1 | import core.Versions
2 |
3 | object ProjectConfigs {
4 | const val compileSdkVersion = 32
5 | const val minSdkVersion = 21
6 | const val targetSdkVersion = 32
7 | const val applicationId = "com.iamkamrul.githubrepo"
8 | const val buildGradle = "com.android.tools.build:gradle:${Versions.buildGradle_version}"
9 | const val kotlinGradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin_version}"
10 | const val hiltAndroidGradlePlugin = "com.google.dagger:hilt-android-gradle-plugin:${Versions.hilt_android_version}"
11 | const val navSafeArgs = "androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.nav_version}"
12 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/core/Dependencies.kt:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | internal object Dependencies {
4 | const val appcompat = "androidx.appcompat:appcompat:${Versions.appcompat_version}"
5 | const val coreKtx = "androidx.core:core-ktx:${Versions.coreKtx_version}"
6 | const val material = "com.google.android.material:material:${Versions.material_version}"
7 | const val constraintLayout = "androidx.constraintlayout:constraintlayout:${Versions.constraintLayout_version}"
8 | const val activityKtx = "androidx.activity:activity-ktx:${Versions.activity_version}"
9 | const val fragmentKtx = "androidx.fragment:fragment-ktx:${Versions.fragment_version}"
10 | const val recyclerview = "androidx.recyclerview:recyclerview:${Versions.recyclerview_version}"
11 | const val cardview = "androidx.cardview:cardview:${Versions.card_view_version}"
12 | const val preference = "androidx.preference:preference:${Versions.preference_version}"
13 | const val swipeToRefreshLayout = "androidx.swiperefreshlayout:swiperefreshlayout:${Versions.swipeLayout_version}"
14 |
15 | const val viewModel = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycle_version}"
16 | const val livedata = "androidx.lifecycle:lifecycle-livedata-ktx:${Versions.lifecycle_version}"
17 | const val lifecycleRuntime = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle_version}"
18 | const val viewModelSaveState = "androidx.lifecycle:lifecycle-viewmodel-savedstate:${Versions.lifecycle_version}"
19 | const val lifecycleService = "androidx.lifecycle:lifecycle-service:${Versions.lifecycle_version}"
20 |
21 | const val hiltAndroid = "com.google.dagger:hilt-android:${Versions.hilt_android_version}"
22 | const val hiltCompiler = "com.google.dagger:hilt-android-compiler:${Versions.hilt_android_version}"
23 |
24 | const val navigationFragmentKtx = "androidx.navigation:navigation-fragment-ktx:${Versions.nav_version}"
25 | const val navigationUiKtx = "androidx.navigation:navigation-ui-ktx:${Versions.nav_version}"
26 |
27 | const val kotlinxCoroutinesAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines_version}"
28 |
29 | const val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit_version}"
30 | const val rxJava3adapter = "com.squareup.retrofit2:adapter-rxjava3:${Versions.retrofit_version}"
31 | const val converterGson = "com.squareup.retrofit2:converter-gson:${Versions.retrofit_version}"
32 | const val gson = "com.google.code.gson:gson:${Versions.retrofit_version}"
33 |
34 | const val okhttp3 = "com.squareup.okhttp3:okhttp:${Versions.okhttp3_version}"
35 | const val okhttp3logging = "com.squareup.okhttp3:logging-interceptor:${Versions.okhttp3_version}"
36 |
37 | const val rxJava3 = "io.reactivex.rxjava3:rxjava:${Versions.rxJava3_version}"
38 | const val rxJava3Android = "io.reactivex.rxjava3:rxandroid:${Versions.rx_android3_version}"
39 |
40 | const val sdp = "com.intuit.sdp:sdp-android:${Versions.sdp_version}"
41 | const val ssp = "com.intuit.ssp:ssp-android:${Versions.sdp_version}"
42 |
43 | const val playStoreCore = "com.google.android.play:core:${Versions.play_core_version}"
44 | const val playStoreKtx = "com.google.android.play:core-ktx:${Versions.play_ktx_version}"
45 |
46 | const val facebookAndroid = "com.facebook.android:facebook-android-sdk:${Versions.facebook_version}"
47 | const val facebookMarketing = "com.facebook.android:facebook-marketing:${Versions.facebook_marketing_version}"
48 |
49 | const val mapCore = "com.google.android.gms:play-services-maps:${Versions.map_version}"
50 | const val mapLocation = "com.google.android.gms:play-services-location:${Versions.map_location_version}"
51 | const val mapPlaces = "com.google.android.libraries.places:places:${Versions.map_place_version}"
52 |
53 | const val firebaseBom = "com.google.firebase:firebase-bom:${Versions.firebase_bom_version}"
54 | const val firebaseCore = "com.google.firebase:firebase-core"
55 | const val firebaseMessaging = "com.google.firebase:firebase-messaging-ktx"
56 | const val firebaseAnalytics = "com.google.firebase:firebase-analytics-ktx"
57 | const val firebaseCrashlytics = "com.google.firebase:firebase-crashlytics-ktx"
58 |
59 | const val roomKtx = "androidx.room:room-ktx:${Versions.room_version}"
60 | const val roomRuntime = "androidx.room:room-runtime:${Versions.room_version}"
61 | const val roomCompiler = "androidx.room:room-compiler:${Versions.room_version}"
62 | const val roomCommon = "androidx.room:room-common:${Versions.room_version}"
63 |
64 | const val leakcanary = "com.squareup.leakcanary:leakcanary-android:${Versions.leakcanary_version}"
65 | const val timber = "com.jakewharton.timber:timber:${Versions.timberVersion}"
66 | const val viewStateLayout = "com.github.kamrul3288:ViewStateLayout:${Versions.viewStateLayoutVersion}"
67 | const val circleImageview = "de.hdodenhof:circleimageview:${Versions.circleImageViewVersion}"
68 | const val picasso = "com.squareup.picasso:picasso:${Versions.picassoVersion}"
69 | const val dotIndicator = "com.tbuonomo:dotsindicator:${Versions.dots_indicator_version}"
70 | const val alerter = "com.github.tapadoo:alerter:${Versions.alerter_version}"
71 | const val imageZoomView = "com.github.chrisbanes:PhotoView:${Versions.image_zoom_version}"
72 | const val epoxyCore = "com.airbnb.android:epoxy:${Versions.epoxy_version}"
73 |
74 | const val junit = "junit:junit:${Versions.junit}"
75 | const val ext_junit = "androidx.test.ext:junit:${Versions.ext_junit}"
76 | const val espresso_core = "androidx.test.espresso:espresso-core:${Versions.espresso}"
77 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/core/ModulesDep.kt:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | object ModulesDep {
4 | const val apiResponse = ":model:apiresponse"
5 | const val entity = ":model:entity"
6 | const val di = ":di"
7 | const val data = ":data"
8 | const val domain = ":domain"
9 | const val common = ":common"
10 | const val navigation = ":navigation"
11 |
12 | const val repoList = ":features:repolist"
13 | const val profile = ":features:profile"
14 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/core/Versions.kt:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | internal object Versions {
4 | const val buildGradle_version = "7.2.1"
5 | const val kotlin_version = "1.6.21"
6 | const val firebaseCrashAnalyticGradleVersion = "2.7.1"
7 | const val googleServicesVersion = "4.3.8"
8 |
9 | const val appcompat_version = "1.4.2"
10 | const val preference_version = "1.2.0"
11 | const val swipeLayout_version = "1.1.0"
12 | const val coreKtx_version = "1.8.0"
13 | const val constraintLayout_version = "2.1.4"
14 | const val material_version = "1.7.0-alpha02"
15 | const val activity_version = "1.4.0"
16 | const val fragment_version = "1.4.1"
17 | const val lifecycle_version = "2.4.0"
18 | const val recyclerview_version = "1.2.1"
19 | const val card_view_version = "1.0.0"
20 | const val hilt_android_version = "2.39.1"
21 | const val nav_version = "2.4.2"
22 | const val coroutines_version = "1.6.2"
23 | const val retrofit_version = "2.9.0"
24 | const val okhttp3_version = "4.9.3"
25 | const val timberVersion = "5.0.1"
26 | const val viewStateLayoutVersion = "1.0.4"
27 | const val circleImageViewVersion = "3.1.0"
28 | const val picassoVersion = "2.8"
29 | const val rxJava3_version = "3.1.5"
30 | const val rx_android3_version = "3.0.0"
31 | const val sdp_version = "1.0.6"
32 | const val leakcanary_version = "2.7"
33 | const val dots_indicator_version = "4.3"
34 | const val play_core_version = "1.10.3"
35 | const val play_ktx_version = "1.8.1"
36 | const val facebook_version = "12.0.1"
37 | const val facebook_marketing_version = "7.0.1"
38 | const val map_version = "18.0.2"
39 | const val map_location_version = "19.0.0"
40 | const val map_place_version = "2.6.0"
41 | const val firebase_bom_version= "30.1.0"
42 | const val room_version = "2.4.2"
43 | const val alerter_version = "7.2.4"
44 | const val epoxy_version = "5.0.0-beta03"
45 | const val image_zoom_version = "2.3.0"
46 |
47 | const val junit = "4.13.2"
48 | const val ext_junit = "1.1.3"
49 | const val espresso = "3.4.0"
50 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/dependencies/GroupedDepHandlerExtension.kt:
--------------------------------------------------------------------------------
1 | package dependencies
2 | import core.Dependencies
3 | import org.gradle.api.artifacts.dsl.DependencyHandler
4 |
5 | fun DependencyHandler.addAndroidxCoreDependencies(){
6 | androidxCoreDependencies.forEach {
7 | add("implementation",it)
8 | }
9 | }
10 |
11 | fun DependencyHandler.addAndroidxSwipeRefreshLayout(){
12 | androidxSwipeRefreshLayout.forEach {
13 | add("implementation",it)
14 | }
15 | }
16 |
17 | fun DependencyHandler.addAndroidxLifeCycleDependencies(){
18 | androidxLifeCycleDependencies.forEach {
19 | add("implementation",it)
20 | }
21 | }
22 |
23 | fun DependencyHandler.addLiveDataDependencies(){
24 | add("implementation",Dependencies.livedata)
25 | }
26 |
27 |
28 | fun DependencyHandler.addAndroidxNavigationDependencies(){
29 | androidxNavigationDependencies.forEach {
30 | add("implementation",it)
31 | }
32 | }
33 |
34 | fun DependencyHandler.addCoroutinesAndroidDependencies(){
35 | coroutinesAndroidDependencies.forEach {
36 | add("implementation",it)
37 | }
38 | }
39 |
40 | fun DependencyHandler.addNetworkDependencies(configurationName:String = "implementation"){
41 | networkDependencies.forEach {
42 | add(configurationName,it)
43 | }
44 | }
45 |
46 | fun DependencyHandler.addHiltDependencies() {
47 | add("implementation",Dependencies.hiltAndroid)
48 | add("kapt",Dependencies.hiltCompiler)
49 | }
50 |
51 | fun DependencyHandler.addRxjava3Dependencies(){
52 | rxjava3Dependencies.forEach {
53 | add("implementation",it)
54 | }
55 | }
56 |
57 | fun DependencyHandler.addTimberDependencies(configurationName:String = "implementation"){
58 | add(configurationName,Dependencies.timber)
59 | }
60 |
61 | fun DependencyHandler.addGsonDependencies(configurationName:String = "implementation"){
62 | add(configurationName,Dependencies.gson)
63 | }
64 |
65 | fun DependencyHandler.addAndroidResponsiveSizeDependenciesDependencies(){
66 | androidResponsiveSizeDependencies.forEach {
67 | add("implementation",it)
68 | }
69 | }
70 |
71 | fun DependencyHandler.addAlerterDependencies() {
72 | add("implementation",Dependencies.alerter)
73 | }
74 |
75 | fun DependencyHandler.addEPoxyDependencies(){
76 | add("implementation",Dependencies.epoxyCore)
77 | }
78 |
79 | fun DependencyHandler.addCircleImageviewDependencies(){
80 | add("implementation",Dependencies.circleImageview)
81 | }
82 |
83 | fun DependencyHandler.addPicassoDependencies(){
84 | add("implementation",Dependencies.picasso)
85 | }
86 |
87 | fun DependencyHandler.addViewStateDependencies(){
88 | add("implementation",Dependencies.viewStateLayout)
89 | }
90 |
91 | fun DependencyHandler.addFirebaseDependencies(){
92 | firebaseDependencies.forEachIndexed { index, it ->
93 | when (index) {
94 | 0 -> add("implementation", platform(it))
95 | else -> add("implementation", it)
96 | }
97 | }
98 | }
99 |
100 | fun DependencyHandler.addPlayCoreDependencies(){
101 | playCoreDependencies.forEach {
102 | add("implementation",it)
103 | }
104 | }
105 |
106 | fun DependencyHandler.addFacebookDependencies(){
107 | facebookDependencies.forEach {
108 | add("implementation",it)
109 | }
110 | }
111 |
112 | fun DependencyHandler.addGoogleMapDependencies(){
113 | googleMapDependencies.forEach {
114 | add("implementation",it)
115 | }
116 | }
117 |
118 |
119 | fun DependencyHandler.addRoomDependencies(){
120 | add("implementation",Dependencies.roomKtx)
121 | add("implementation",Dependencies.roomRuntime)
122 | add("kapt",Dependencies.roomCompiler)
123 |
124 | }
125 |
126 | fun DependencyHandler.addRoomCommonDependencies(){
127 | add("implementation",Dependencies.roomCommon)
128 | }
129 |
130 | fun DependencyHandler.addImageZoomViewDependencies(){
131 | imageZoomViewDependencies.forEach {
132 | add("implementation",it)
133 | }
134 | }
135 |
136 | fun DependencyHandler.addLeakcanaryDependencies(){
137 | add("debugImplementation",Dependencies.leakcanary)
138 | }
139 |
140 | fun DependencyHandler.addAndroidTestsDependencies() {
141 | add("testImplementation",Dependencies.junit)
142 | add("androidTestImplementation",Dependencies.ext_junit)
143 | add("androidTestImplementation",Dependencies.espresso_core)
144 | }
145 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/dependencies/GroupedDependencies.kt:
--------------------------------------------------------------------------------
1 | package dependencies
2 | import core.Dependencies
3 |
4 | internal val androidxCoreDependencies = listOf(
5 | Dependencies.appcompat,
6 | Dependencies.coreKtx,
7 | Dependencies.material,
8 | Dependencies.constraintLayout,
9 | Dependencies.activityKtx,
10 | Dependencies.fragmentKtx,
11 | Dependencies.recyclerview,
12 | Dependencies.cardview,
13 | Dependencies.preference,
14 | )
15 |
16 | internal val androidxSwipeRefreshLayout = listOf(
17 | Dependencies.swipeToRefreshLayout
18 | )
19 |
20 | internal val androidxLifeCycleDependencies = listOf(
21 | Dependencies.viewModel,
22 | Dependencies.livedata,
23 | Dependencies.lifecycleRuntime,
24 | Dependencies.viewModelSaveState,
25 | Dependencies.lifecycleService,
26 | )
27 |
28 | internal val androidxNavigationDependencies = listOf(
29 | Dependencies.navigationFragmentKtx,
30 | Dependencies.navigationUiKtx,
31 | )
32 |
33 | internal val coroutinesAndroidDependencies = listOf(
34 | Dependencies.kotlinxCoroutinesAndroid,
35 | )
36 |
37 | internal val rxjava3Dependencies = listOf(
38 | Dependencies.rxJava3,
39 | Dependencies.rxJava3Android
40 | )
41 |
42 |
43 |
44 | internal val firebaseDependencies = listOf(
45 | Dependencies.firebaseBom,
46 | Dependencies.firebaseCore,
47 | Dependencies.firebaseMessaging,
48 | Dependencies.firebaseAnalytics,
49 | Dependencies.firebaseCrashlytics,
50 | )
51 |
52 | internal val playCoreDependencies = listOf(
53 | Dependencies.playStoreCore,
54 | Dependencies.playStoreKtx
55 | )
56 |
57 | internal val facebookDependencies = listOf(
58 | Dependencies.facebookAndroid,
59 | Dependencies.facebookMarketing
60 | )
61 |
62 | internal val googleMapDependencies = listOf(
63 | Dependencies.mapCore,
64 | Dependencies.mapLocation,
65 | Dependencies.mapPlaces,
66 | )
67 |
68 |
69 |
70 | internal val imageZoomViewDependencies = listOf(
71 | Dependencies.imageZoomView
72 | )
73 |
74 |
75 | internal val networkDependencies = listOf(
76 | Dependencies.retrofit,
77 | Dependencies.converterGson,
78 | Dependencies.gson,
79 | Dependencies.okhttp3,
80 | Dependencies.okhttp3logging,
81 | Dependencies.rxJava3adapter,
82 | )
83 |
84 |
85 | internal val androidResponsiveSizeDependencies = listOf(
86 | Dependencies.sdp,
87 | Dependencies.ssp,
88 | )
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/dependencies/ModuleGroupedDepHandlerExtension.kt:
--------------------------------------------------------------------------------
1 | package dependencies
2 | import core.ModulesDep
3 | import org.gradle.api.artifacts.dsl.DependencyHandler
4 | import org.gradle.kotlin.dsl.project
5 |
6 | fun DependencyHandler.addDiModule(configurationName:String = "implementation"){
7 | add(configurationName, project(ModulesDep.di))
8 | }
9 |
10 | fun DependencyHandler.addDomainModule(){
11 | add("implementation", project(ModulesDep.domain))
12 | }
13 |
14 | fun DependencyHandler.addDataModule(){
15 | add("implementation", project(ModulesDep.data))
16 | }
17 |
18 | fun DependencyHandler.addApiResponseModule(configurationName:String = "implementation"){
19 | add(configurationName, project(ModulesDep.apiResponse))
20 | }
21 |
22 | fun DependencyHandler.addEntityModule(configurationName:String = "implementation"){
23 | add(configurationName, project(ModulesDep.entity))
24 | }
25 |
26 | fun DependencyHandler.addCommonModule(){
27 | add("implementation", project(ModulesDep.common))
28 | }
29 |
30 | fun DependencyHandler.addFeatureModules(){
31 | featureModuleDependencies.forEach{
32 | add("implementation", project(it))
33 | }
34 | }
35 |
36 |
37 | fun DependencyHandler.addFeatureModuleDependantDependencies(){
38 | featureModuleDependantDependencies.forEach{
39 | add("implementation", project(it))
40 | }
41 | }
42 |
43 |
44 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/dependencies/ModuleGroupedDependencies.kt:
--------------------------------------------------------------------------------
1 | package dependencies
2 | import core.ModulesDep
3 |
4 | internal val featureModuleDependencies = listOf(
5 | ModulesDep.repoList,
6 | ModulesDep.profile
7 | )
8 |
9 | internal val featureModuleDependantDependencies = listOf(
10 | ModulesDep.di,
11 | ModulesDep.domain,
12 | ModulesDep.common,
13 | ModulesDep.navigation
14 | )
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/plugins/android-base-library-core.gradle.kts:
--------------------------------------------------------------------------------
1 | package plugins
2 | import dependencies.addAndroidTestsDependencies
3 | import dependencies.addHiltDependencies
4 | import dependencies.addLiveDataDependencies
5 |
6 | plugins {
7 | id("com.android.library")
8 | kotlin("android")
9 | kotlin("kapt")
10 | id("dagger.hilt.android.plugin")
11 | }
12 |
13 | android{
14 | compileSdk = ProjectConfigs.compileSdkVersion
15 |
16 | defaultConfig {
17 | minSdk = ProjectConfigs.minSdkVersion
18 | targetSdk = ProjectConfigs.targetSdkVersion
19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20 | consumerProguardFiles("consumer-rules.pro")
21 | }
22 | compileOptions {
23 | sourceCompatibility = JavaVersion.VERSION_11
24 | targetCompatibility = JavaVersion.VERSION_11
25 | }
26 | kotlinOptions {
27 | jvmTarget = "11"
28 | }
29 | }
30 |
31 |
32 | dependencies {
33 | addHiltDependencies()
34 | addLiveDataDependencies()
35 | addAndroidTestsDependencies()
36 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/plugins/android-base-library-feature.gradle.kts:
--------------------------------------------------------------------------------
1 | package plugins
2 | import dependencies.addAndroidTestsDependencies
3 | import dependencies.addHiltDependencies
4 | import dependencies.addAndroidxCoreDependencies
5 | import dependencies.addFeatureModuleDependantDependencies
6 | import dependencies.addAndroidxNavigationDependencies
7 | import dependencies.addCoroutinesAndroidDependencies
8 | import dependencies.addViewStateDependencies
9 | import dependencies.addAndroidResponsiveSizeDependenciesDependencies
10 | import gradle.kotlin.dsl.accessors._f28201936ac5dc93fbe24b62cb4541cf.android
11 | import gradle.kotlin.dsl.accessors._f28201936ac5dc93fbe24b62cb4541cf.kotlinOptions
12 |
13 | plugins {
14 | id("com.android.library")
15 | kotlin("android")
16 | kotlin("kapt")
17 | id("dagger.hilt.android.plugin")
18 | }
19 |
20 | android{
21 | compileSdk = ProjectConfigs.compileSdkVersion
22 |
23 | defaultConfig {
24 | minSdk = ProjectConfigs.minSdkVersion
25 | targetSdk = ProjectConfigs.targetSdkVersion
26 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
27 | consumerProguardFiles("consumer-rules.pro")
28 | }
29 | compileOptions {
30 | sourceCompatibility = JavaVersion.VERSION_11
31 | targetCompatibility = JavaVersion.VERSION_11
32 | }
33 | kotlinOptions {
34 | jvmTarget = "11"
35 | }
36 |
37 | buildFeatures{
38 | viewBinding = true
39 | }
40 | }
41 |
42 |
43 | dependencies {
44 | addFeatureModuleDependantDependencies()
45 | addHiltDependencies()
46 | addAndroidxCoreDependencies()
47 | addAndroidxNavigationDependencies()
48 | addCoroutinesAndroidDependencies()
49 | addAndroidResponsiveSizeDependenciesDependencies()
50 | addViewStateDependencies()
51 | addAndroidTestsDependencies()
52 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/plugins/android-base-library.gradle.kts:
--------------------------------------------------------------------------------
1 | package plugins
2 | plugins {
3 | id("com.android.library")
4 | kotlin("android")
5 | kotlin("kapt")
6 | }
7 |
8 | android{
9 | compileSdk = ProjectConfigs.compileSdkVersion
10 |
11 | defaultConfig {
12 | minSdk = ProjectConfigs.minSdkVersion
13 | targetSdk = ProjectConfigs.targetSdkVersion
14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles("consumer-rules.pro")
16 | }
17 | compileOptions {
18 | sourceCompatibility = JavaVersion.VERSION_11
19 | targetCompatibility = JavaVersion.VERSION_11
20 | }
21 | kotlinOptions {
22 | jvmTarget = "11"
23 | }
24 | buildFeatures {
25 | viewBinding = true
26 | }
27 | }
--------------------------------------------------------------------------------
/common/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/common/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import dependencies.*
2 |
3 | plugins {
4 | plugins.`android-base-library`
5 | }
6 | dependencies {
7 | addCoroutinesAndroidDependencies()
8 | addAndroidxCoreDependencies()
9 | addAndroidxLifeCycleDependencies()
10 | }
11 |
--------------------------------------------------------------------------------
/common/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/common/consumer-rules.pro
--------------------------------------------------------------------------------
/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.
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
--------------------------------------------------------------------------------
/common/src/androidTest/java/com/iamkamrul/common/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.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.iamkamrul.common.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/common/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/common/src/main/java/com/iamkamrul/common/adapter/DataBoundListAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.common.adapter
2 |
3 | import androidx.recyclerview.widget.ListAdapter
4 | import androidx.recyclerview.widget.DiffUtil
5 | import android.view.ViewGroup
6 | import androidx.viewbinding.ViewBinding
7 |
8 | /**
9 | * A generic RecyclerView adapter that uses Data Binding & DiffUtil.
10 | *
11 | * @param Type of the items in the list
12 | * @param The type of the ViewDataBinding
13 | */
14 | abstract class DataBoundListAdapter(
15 | diffCallback: DiffUtil.ItemCallback
16 | ) : ListAdapter>(diffCallback){
17 |
18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DataBoundViewHolder {
19 | val binding = createBinding(parent)
20 | return DataBoundViewHolder(binding)
21 | }
22 |
23 | override fun onBindViewHolder(holder: DataBoundViewHolder, position: Int) {
24 |
25 | bind(holder.binding, getItem(position), position)
26 | }
27 |
28 | protected abstract fun createBinding(parent: ViewGroup): V
29 | protected abstract fun bind(binding: V, item: T, position: Int)
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/common/src/main/java/com/iamkamrul/common/adapter/DataBoundViewHolder.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.common.adapter
2 |
3 | import androidx.recyclerview.widget.RecyclerView
4 | import androidx.viewbinding.ViewBinding
5 |
6 | /**
7 | * A generic ViewHolder that works with a [ViewBinding].
8 | * @param The type of the ViewDataBinding.
9 | */
10 | class DataBoundViewHolder constructor(val binding: T) : RecyclerView.ViewHolder(binding.root)
11 |
--------------------------------------------------------------------------------
/common/src/main/java/com/iamkamrul/common/base/BaseFragment.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.common.base
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.view.WindowManager
8 | import android.widget.Toast
9 | import androidx.fragment.app.Fragment
10 | import androidx.lifecycle.Lifecycle
11 | import androidx.lifecycle.lifecycleScope
12 | import androidx.lifecycle.repeatOnLifecycle
13 | import androidx.viewbinding.ViewBinding
14 | import com.google.android.material.snackbar.Snackbar
15 | import kotlinx.coroutines.flow.Flow
16 | import kotlinx.coroutines.launch
17 |
18 | abstract class BaseFragment : Fragment() {
19 | private var _binding: V? = null
20 | protected val binding get() = _binding!!
21 | protected abstract fun viewBindingLayout(): V
22 | protected abstract fun initializeView(savedInstanceState: Bundle?)
23 |
24 | override fun onCreateView(
25 | inflater: LayoutInflater,
26 | container: ViewGroup?,
27 | savedInstanceState: Bundle?
28 | ): View? {
29 | _binding = viewBindingLayout()
30 | initializeView(savedInstanceState)
31 | return binding.root
32 | }
33 |
34 | protected fun showMessage(message: String?) {
35 | Snackbar.make(
36 | requireActivity().findViewById(android.R.id.content),
37 | "" + message,
38 | Snackbar.LENGTH_LONG
39 | ).show()
40 | }
41 |
42 | protected fun showProgressBar(isLoading: Boolean, view: View) {
43 | if (isLoading) {
44 | view.visibility = View.VISIBLE
45 | requireActivity().window?.setFlags(
46 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
47 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
48 | )
49 | } else {
50 | view.visibility = View.GONE
51 | requireActivity().window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
52 | }
53 | }
54 |
55 | protected fun showToastMessage(message: String) {
56 | Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
57 | }
58 |
59 | override fun onDestroyView() {
60 | super.onDestroyView()
61 | _binding = null
62 | }
63 |
64 | protected inline fun Flow.execute(crossinline action:(T)->Unit){
65 | with(viewLifecycleOwner){
66 | lifecycleScope.launch {
67 | repeatOnLifecycle(Lifecycle.State.STARTED){
68 | collect{
69 | action(it)
70 | }
71 | }
72 | }
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/common/src/main/java/com/iamkamrul/common/base/BaseViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.common.base
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import kotlinx.coroutines.CoroutineDispatcher
6 | import kotlinx.coroutines.Dispatchers
7 | import kotlinx.coroutines.launch
8 |
9 | open class BaseViewModel(
10 | private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO
11 | ) : ViewModel(){
12 | fun execute(job: suspend () -> Unit){
13 | viewModelScope.launch {
14 | job.invoke()
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/common/src/main/java/com/iamkamrul/common/extension/RecyclerviewExtension.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.common.extension
2 |
3 | import android.content.Context
4 | import androidx.recyclerview.widget.GridLayoutManager
5 | import androidx.recyclerview.widget.LinearLayoutManager
6 | import androidx.recyclerview.widget.RecyclerView
7 |
8 | fun> Context.setUpGridRecyclerView(view: RecyclerView, viewAdapter:T, column:Int){
9 | view.setHasFixedSize(true)
10 | view.layoutManager = GridLayoutManager(this,column)
11 | view.adapter = viewAdapter
12 | }
13 |
14 | fun> Context.setUpHorizontalReverseRecyclerView(view: RecyclerView, viewAdapter:T){
15 | view.setHasFixedSize(true)
16 | view.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,false)
17 | view.adapter = viewAdapter
18 | }
19 |
20 | fun> Context.setUpVerticalRecyclerView(view: RecyclerView, viewAdapter:T){
21 | view.setHasFixedSize(true)
22 | view.layoutManager = LinearLayoutManager(this)
23 | view.adapter = viewAdapter
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/common/src/main/res/drawable/ic_baseline_circle_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/common/src/main/res/drawable/ic_baseline_fork_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/common/src/main/res/drawable/ic_baseline_star_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/common/src/main/res/values-land/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 48dp
3 |
--------------------------------------------------------------------------------
/common/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 | #000000
5 | #333333
6 | #212121
7 |
--------------------------------------------------------------------------------
/common/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/common/src/main/res/values-v29/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/common/src/main/res/values-w1240dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 200dp
3 |
--------------------------------------------------------------------------------
/common/src/main/res/values-w600dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 48dp
3 |
--------------------------------------------------------------------------------
/common/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF000000
4 | #FFFFFFFF
5 | #F6F5F5
6 | #FFFFFF
7 |
--------------------------------------------------------------------------------
/common/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
--------------------------------------------------------------------------------
/common/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | GithubRepo
3 | Settings
4 |
5 | First Fragment
6 | Second Fragment
7 | Next
8 | Previous
9 |
10 |
11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam in scelerisque sem. Mauris
12 | volutpat, dolor id interdum ullamcorper, risus dolor egestas lectus, sit amet mattis purus
13 | dui nec risus. Maecenas non sodales nisi, vel dictum dolor. Class aptent taciti sociosqu ad
14 | litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse blandit eleifend
15 | diam, vel rutrum tellus vulputate quis. Aliquam eget libero aliquet, imperdiet nisl a,
16 | ornare ex. Sed rhoncus est ut libero porta lobortis. Fusce in dictum tellus.\n\n
17 | Suspendisse interdum ornare ante. Aliquam nec cursus lorem. Morbi id magna felis. Vivamus
18 | egestas, est a condimentum egestas, turpis nisl iaculis ipsum, in dictum tellus dolor sed
19 | neque. Morbi tellus erat, dapibus ut sem a, iaculis tincidunt dui. Interdum et malesuada
20 | fames ac ante ipsum primis in faucibus. Curabitur et eros porttitor, ultricies urna vitae,
21 | molestie nibh. Phasellus at commodo eros, non aliquet metus. Sed maximus nisl nec dolor
22 | bibendum, vel congue leo egestas.\n\n
23 | Sed interdum tortor nibh, in sagittis risus mollis quis. Curabitur mi odio, condimentum sit
24 | amet auctor at, mollis non turpis. Nullam pretium libero vestibulum, finibus orci vel,
25 | molestie quam. Fusce blandit tincidunt nulla, quis sollicitudin libero facilisis et. Integer
26 | interdum nunc ligula, et fermentum metus hendrerit id. Vestibulum lectus felis, dictum at
27 | lacinia sit amet, tristique id quam. Cras eu consequat dui. Suspendisse sodales nunc ligula,
28 | in lobortis sem porta sed. Integer id ultrices magna, in luctus elit. Sed a pellentesque
29 | est.\n\n
30 | Aenean nunc velit, lacinia sed dolor sed, ultrices viverra nulla. Etiam a venenatis nibh.
31 | Morbi laoreet, tortor sed facilisis varius, nibh orci rhoncus nulla, id elementum leo dui
32 | non lorem. Nam mollis ipsum quis auctor varius. Quisque elementum eu libero sed commodo. In
33 | eros nisl, imperdiet vel imperdiet et, scelerisque a mauris. Pellentesque varius ex nunc,
34 | quis imperdiet eros placerat ac. Duis finibus orci et est auctor tincidunt. Sed non viverra
35 | ipsum. Nunc quis augue egestas, cursus lorem at, molestie sem. Morbi a consectetur ipsum, a
36 | placerat diam. Etiam vulputate dignissim convallis. Integer faucibus mauris sit amet finibus
37 | convallis.\n\n
38 | Phasellus in aliquet mi. Pellentesque habitant morbi tristique senectus et netus et
39 | malesuada fames ac turpis egestas. In volutpat arcu ut felis sagittis, in finibus massa
40 | gravida. Pellentesque id tellus orci. Integer dictum, lorem sed efficitur ullamcorper,
41 | libero justo consectetur ipsum, in mollis nisl ex sed nisl. Donec maximus ullamcorper
42 | sodales. Praesent bibendum rhoncus tellus nec feugiat. In a ornare nulla. Donec rhoncus
43 | libero vel nunc consequat, quis tincidunt nisl eleifend. Cras bibendum enim a justo luctus
44 | vestibulum. Fusce dictum libero quis erat maximus, vitae volutpat diam dignissim.
45 |
46 |
--------------------------------------------------------------------------------
/common/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/common/src/test/java/com/iamkamrul/common/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.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 | }
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import dependencies.*
2 |
3 | plugins {
4 | plugins.`android-base-library-core`
5 | }
6 |
7 | dependencies {
8 | addNetworkDependencies(configurationName = "api")
9 | addDiModule(configurationName = "api")
10 | addApiResponseModule(configurationName = "api")
11 | addDomainModule()
12 | addCoroutinesAndroidDependencies()
13 | addRxjava3Dependencies()
14 | }
15 |
--------------------------------------------------------------------------------
/data/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/data/consumer-rules.pro
--------------------------------------------------------------------------------
/data/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/data/src/androidTest/java/com/iamkamrul/data/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.data
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.iamkamrul.data.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/data/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/data/src/main/java/com/iamkamrul/data/apiservice/ApiService.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.data.apiservice
2 |
3 | import com.iamkamrul.apiresponse.UserProfileApiResponse
4 | import com.iamkamrul.apiresponse.RepositoryListItemApiResponse
5 | import retrofit2.Response
6 | import retrofit2.http.GET
7 | import retrofit2.http.Path
8 |
9 | interface ApiService {
10 | @GET("/users/{username}/repos")
11 | suspend fun fetchOwnerRepositoryList(
12 | @Path("username")username:String
13 | ):Response>
14 |
15 | @GET("/users/{username}")
16 | suspend fun fetchUser(
17 | @Path("username")username:String
18 | ):Response
19 |
20 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/iamkamrul/data/apiservice/ApiServiceModule.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.data.apiservice
2 |
3 | import com.iamkamrul.di.qualifier.BaseUrl
4 | import dagger.Module
5 | import dagger.Provides
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.components.SingletonComponent
8 | import retrofit2.Retrofit
9 | import javax.inject.Singleton
10 |
11 | @Module
12 | @InstallIn(SingletonComponent::class)
13 | class ApiServiceModule {
14 | @Provides
15 | @Singleton
16 | fun provideApiService(@BaseUrl retrofit: Retrofit):ApiService{
17 | return retrofit.create(ApiService::class.java)
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/iamkamrul/data/apiservice/NetworkBoundResources.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.data.apiservice
2 |
3 | import com.google.gson.JsonParser
4 | import com.iamkamrul.domain.base.Result
5 | import kotlinx.coroutines.CoroutineDispatcher
6 | import kotlinx.coroutines.Dispatchers
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.flow
9 | import kotlinx.coroutines.withContext
10 | import okhttp3.ResponseBody
11 | import retrofit2.HttpException
12 | import retrofit2.Response
13 | import java.io.IOException
14 | import java.net.SocketTimeoutException
15 | import javax.inject.Inject
16 |
17 | class NetworkBoundResources @Inject constructor(){
18 | private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
19 |
20 | suspend fun downloadData(api : suspend () -> Response):Flow>{
21 | return withContext(ioDispatcher) {
22 | flow {
23 | try {
24 | emit(Result.Loading(true))
25 | val response:Response = api()
26 | emit(Result.Loading(false))
27 | if (response.isSuccessful){
28 | response.body()?.let {
29 | emit(Result.Success(data = it))
30 | }?: emit(Result.Error(message = "Unknown error occurred", code = 0))
31 | }else{
32 | emit(Result.Error(message = parserErrorBody(response.errorBody()), code = response.code()))
33 | }
34 | }catch (e:Exception){
35 | emit(Result.Error(message = message(e), code = code(e)))
36 | }
37 |
38 | }
39 | }
40 | }
41 |
42 | private fun parserErrorBody(response: ResponseBody?):String{
43 | return response?.let {
44 | val errorMessage = JsonParser.parseString(it.string()).asJsonObject["message"].asString
45 | errorMessage.ifEmpty { "Whoops! Something went wrong" }
46 | errorMessage
47 | }?:"Unknown error occur, please try again"
48 | }
49 | private fun message(throwable: Throwable?):String{
50 | when (throwable) {
51 | is SocketTimeoutException -> return "Whoops! connection time out, try again!"
52 | is IOException -> return "No internet connection, try again!"
53 | is HttpException -> return try {
54 | val errorJsonString = throwable.response()?.errorBody()?.string()
55 | val errorMessage = JsonParser.parseString(errorJsonString).asJsonObject["message"].asString
56 | errorMessage.ifEmpty { "Whoops! Something went wrong" }
57 | }catch (e:Exception){
58 | "Unknown error occur, please try again!"
59 | }
60 | }
61 | return "Unknown error occur, please try again!"
62 | }
63 | private fun code(throwable: Throwable?):Int{
64 | return if (throwable is HttpException) (throwable).code()
65 | else 0
66 | }
67 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/iamkamrul/data/mapper/BaseMapper.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.data.mapper
2 |
3 | import com.iamkamrul.domain.base.Result
4 | import kotlinx.coroutines.flow.Flow
5 | import kotlinx.coroutines.flow.map
6 |
7 | // R means apiResponse type and E means entity
8 | interface Mapper{
9 | fun mapFromApiResponse(type:R):E
10 | }
11 | fun mapFromApiResponse(result: Flow>, mapper: Mapper):Flow>{
12 | return result.map {
13 | when(it){
14 | is Result.Success-> Result.Success(mapper.mapFromApiResponse(it.data))
15 | is Result.Error->Result.Error(it.message,it.code)
16 | is Result.Loading -> Result.Loading(it.loadingState)
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/iamkamrul/data/mapper/RepositoryListItemMapper.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.data.mapper
2 |
3 | import com.iamkamrul.apiresponse.RepositoryListItemApiResponse
4 | import com.iamkamrul.entity.RepositoryListItemEntity
5 | import javax.inject.Inject
6 |
7 | class RepositoryListItemMapper @Inject constructor():Mapper,List>{
8 |
9 |
10 | override fun mapFromApiResponse(type: List): List {
11 | return type.map {
12 | RepositoryListItemEntity(
13 | userAvatarUrl = it.owner?.avatar_url ?: "https://www.pullrequest.com/blog/github-code-review-service/images/github-logo_hub2899c31b6ca7aed8d6a218f0e752fe4_46649_1200x1200_fill_box_center_2.png",
14 | userName = it.owner?.login ?: "NO_USER_NAME_FOUND",
15 | repoName = it.name ?: "EMPTY_REPO_NAME",
16 | repoFullName = it.full_name ?: "EMPTY_REPO_NAME",
17 | repoDescription = it.description ?: "No description found",
18 | language = it.language ?: "Not Found",
19 | forksCount = it.forks_count ?: 0,
20 | stargazers_count = it.stargazers_count ?: 0
21 | )
22 | }
23 | }
24 |
25 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/iamkamrul/data/mapper/UserProfileMapper.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.data.mapper
2 |
3 | import com.iamkamrul.apiresponse.UserProfileApiResponse
4 | import com.iamkamrul.entity.UserProfileEntity
5 | import javax.inject.Inject
6 |
7 | class UserProfileMapper @Inject constructor():Mapper{
8 | override fun mapFromApiResponse(type: UserProfileApiResponse): UserProfileEntity {
9 | return UserProfileEntity(
10 | userAvatar = type.avatar_url ?:"https://www.pullrequest.com/blog/github-code-review-service/images/github-logo_hub2899c31b6ca7aed8d6a218f0e752fe4_46649_1200x1200_fill_box_center_2.png",
11 | userFullName = type.name ?: "NAME_NOT_FOUND",
12 | userName = type.login ?: "",
13 | about = type.bio ?: "BIO_NOT_FOUND",
14 | repoCount = type.public_repos ?: 0,
15 | followerCount = type.followers ?: 0,
16 | followingCount = type.following ?: 0
17 | )
18 | }
19 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/iamkamrul/data/repoimpl/GithubRepoImpl.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.data.repoimpl
2 |
3 | import com.iamkamrul.data.apiservice.ApiService
4 | import com.iamkamrul.data.apiservice.NetworkBoundResources
5 | import com.iamkamrul.data.mapper.RepositoryListItemMapper
6 | import com.iamkamrul.data.mapper.UserProfileMapper
7 | import com.iamkamrul.data.mapper.mapFromApiResponse
8 | import com.iamkamrul.domain.base.Result
9 | import com.iamkamrul.domain.repository.GithubRepository
10 | import com.iamkamrul.domain.usecase.RepositoryListUseCase
11 | import com.iamkamrul.domain.usecase.UserProfileUseCase
12 | import com.iamkamrul.entity.RepositoryListItemEntity
13 | import com.iamkamrul.entity.UserProfileEntity
14 | import kotlinx.coroutines.flow.Flow
15 | import javax.inject.Inject
16 |
17 | class GithubRepoImpl @Inject constructor(
18 | private val apiService: ApiService,
19 | private val networkBoundResources: NetworkBoundResources,
20 | private val repositoryListItemMapper: RepositoryListItemMapper,
21 | private val userProfileMapper: UserProfileMapper
22 | ) : GithubRepository{
23 |
24 | override suspend fun fetchOwnerRepositoryList(params: RepositoryListUseCase.Params): Flow>> {
25 | return mapFromApiResponse(
26 | result = networkBoundResources.downloadData {
27 | apiService.fetchOwnerRepositoryList(params.userName)
28 | },repositoryListItemMapper
29 | )
30 | }
31 |
32 | override suspend fun fetchOwnerProfile(params: UserProfileUseCase.Params): Flow> {
33 | return mapFromApiResponse(
34 | result = networkBoundResources.downloadData {
35 | apiService.fetchUser(params.userName)
36 | },userProfileMapper
37 | )
38 | }
39 |
40 |
41 | }
--------------------------------------------------------------------------------
/data/src/test/java/com/iamkamrul/data/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.data
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 | }
--------------------------------------------------------------------------------
/di/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/di/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import dependencies.*
2 |
3 | plugins {
4 | plugins.`android-base-library-core`
5 | }
6 |
7 | dependencies {
8 | addNetworkDependencies()
9 | addTimberDependencies(configurationName = "api")
10 | }
--------------------------------------------------------------------------------
/di/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/di/consumer-rules.pro
--------------------------------------------------------------------------------
/di/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/di/src/androidTest/java/com/iamkamrul/di/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.di
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.iamkamrul.di.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/di/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/di/src/main/java/com/iamkamrul/di/module/OkHttpModule.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.di.module
2 |
3 |
4 | import dagger.Module
5 | import dagger.Provides
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.components.SingletonComponent
8 | import okhttp3.OkHttpClient
9 | import okhttp3.logging.HttpLoggingInterceptor
10 | import timber.log.Timber
11 | import java.util.concurrent.TimeUnit
12 | import javax.inject.Singleton
13 |
14 | @Module
15 | @InstallIn(SingletonComponent::class)
16 | object OkHttpModule {
17 |
18 | @Provides
19 | @Singleton
20 | fun provideLoggerInterceptor(): HttpLoggingInterceptor {
21 | val interceptor = HttpLoggingInterceptor { message -> Timber.e(message) }
22 | interceptor.apply { interceptor.level = HttpLoggingInterceptor.Level.HEADERS }
23 | interceptor.apply { interceptor.level = HttpLoggingInterceptor.Level.BODY }
24 | return interceptor
25 | }
26 |
27 |
28 | @Provides
29 | @Singleton
30 | fun provideOkHttpClient(
31 | loggerInterceptor: HttpLoggingInterceptor,
32 | ): OkHttpClient {
33 | val timeOut = 30
34 | val httpClient = OkHttpClient().newBuilder()
35 | .connectTimeout(timeOut.toLong(), TimeUnit.SECONDS)
36 | .readTimeout(timeOut.toLong(), TimeUnit.SECONDS)
37 | .writeTimeout(timeOut.toLong(), TimeUnit.SECONDS)
38 |
39 | httpClient.addInterceptor(loggerInterceptor)
40 | httpClient.addInterceptor { chain ->
41 | val original = chain.request()
42 | val requestBuilder = original.newBuilder()
43 | .addHeader("Accept", "application/json")
44 | val request = requestBuilder.build()
45 | chain.proceed(request)
46 | }
47 | return httpClient.build()
48 | }
49 | }
--------------------------------------------------------------------------------
/di/src/main/java/com/iamkamrul/di/module/RetrofitModule.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.di.module
2 |
3 | import com.google.gson.Gson
4 | import com.google.gson.GsonBuilder
5 | import com.iamkamrul.di.qualifier.BaseUrl
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.components.SingletonComponent
10 | import okhttp3.OkHttpClient
11 | import retrofit2.Retrofit
12 | import retrofit2.converter.gson.GsonConverterFactory
13 | import javax.inject.Singleton
14 |
15 |
16 | @Module
17 | @InstallIn(SingletonComponent::class)
18 | object RetrofitModule {
19 |
20 | @Singleton
21 | @Provides
22 | @BaseUrl
23 | fun provideRetrofit(
24 | @BaseUrl baseUrl: String,
25 | okHttpClient: OkHttpClient,
26 | factory: GsonConverterFactory
27 | ): Retrofit {
28 | return Retrofit.Builder()
29 | .baseUrl(baseUrl)
30 | .client(okHttpClient)
31 | .addConverterFactory(factory)
32 | .build()
33 | }
34 |
35 | @Singleton
36 | @Provides
37 | fun provideConverterFactory(gson: Gson): GsonConverterFactory {
38 | return GsonConverterFactory.create(gson)
39 | }
40 |
41 | @Singleton
42 | @Provides
43 | fun provideGson(): Gson {
44 | val gsonBuilder = GsonBuilder()
45 | return gsonBuilder.create()
46 | }
47 | }
--------------------------------------------------------------------------------
/di/src/main/java/com/iamkamrul/di/qualifier/BaseUrl.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.di.qualifier
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class BaseUrl
--------------------------------------------------------------------------------
/di/src/test/java/com/iamkamrul/di/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.di
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 | }
--------------------------------------------------------------------------------
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import dependencies.*
2 |
3 | plugins {
4 | plugins.`android-base-library-core`
5 | }
6 | dependencies {
7 | addCoroutinesAndroidDependencies()
8 | addEntityModule(configurationName = "api")
9 | }
--------------------------------------------------------------------------------
/domain/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/domain/consumer-rules.pro
--------------------------------------------------------------------------------
/domain/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/domain/src/androidTest/java/com/iamkamrul/domain/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.domain
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.iamkamrul.domain.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/domain/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/iamkamrul/domain/base/BaseUsecase.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.domain.base
2 |
3 | import kotlinx.coroutines.flow.Flow
4 |
5 | interface BaseUseCase
6 | interface ApiUseCase:BaseUseCase{
7 | suspend fun execute(params: Params):Flow>
8 | }
9 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/iamkamrul/domain/base/Result.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.domain.base
2 |
3 | sealed class Result {
4 | data class Success(val data: T) : Result()
5 | data class Loading(val loadingState: Boolean) : Result()
6 | data class Error(val message: String,val code:Int) : Result()
7 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/iamkamrul/domain/repository/GithubRepository.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.domain.repository
2 |
3 | import com.iamkamrul.domain.base.Result
4 | import com.iamkamrul.domain.usecase.RepositoryListUseCase
5 | import com.iamkamrul.domain.usecase.UserProfileUseCase
6 | import com.iamkamrul.entity.RepositoryListItemEntity
7 | import com.iamkamrul.entity.UserProfileEntity
8 | import kotlinx.coroutines.flow.Flow
9 |
10 | interface GithubRepository {
11 | suspend fun fetchOwnerRepositoryList(params: RepositoryListUseCase.Params):Flow>>
12 | suspend fun fetchOwnerProfile(params: UserProfileUseCase.Params):Flow>
13 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/iamkamrul/domain/usecase/RepositoryListUsecase.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.domain.usecase
2 |
3 | import com.iamkamrul.domain.base.ApiUseCase
4 | import com.iamkamrul.domain.base.Result
5 | import com.iamkamrul.domain.repository.GithubRepository
6 | import com.iamkamrul.entity.RepositoryListItemEntity
7 | import kotlinx.coroutines.flow.Flow
8 | import javax.inject.Inject
9 |
10 | class RepositoryListUseCase @Inject constructor(
11 | private val repository: GithubRepository
12 | ):ApiUseCase>{
13 | override suspend fun execute(params: Params): Flow>> {
14 | return repository.fetchOwnerRepositoryList(params)
15 | }
16 | data class Params(val userName:String)
17 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/iamkamrul/domain/usecase/UserProfileUsecase.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.domain.usecase
2 |
3 | import com.iamkamrul.domain.base.ApiUseCase
4 | import com.iamkamrul.domain.base.Result
5 | import com.iamkamrul.domain.repository.GithubRepository
6 | import com.iamkamrul.entity.UserProfileEntity
7 | import kotlinx.coroutines.flow.Flow
8 | import javax.inject.Inject
9 |
10 | class UserProfileUseCase @Inject constructor(
11 | private val repository: GithubRepository
12 | ):ApiUseCase{
13 | data class Params(val userName:String)
14 | override suspend fun execute(params: Params): Flow> {
15 | return repository.fetchOwnerProfile(params)
16 | }
17 | }
--------------------------------------------------------------------------------
/domain/src/test/java/com/iamkamrul/domain/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.domain
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 | }
--------------------------------------------------------------------------------
/features/profile/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/features/profile/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import dependencies.addCircleImageviewDependencies
2 | import dependencies.addPicassoDependencies
3 |
4 | plugins {
5 | plugins.`android-base-library-feature`
6 | }
7 |
8 | dependencies {
9 | addPicassoDependencies()
10 | addCircleImageviewDependencies()
11 | }
--------------------------------------------------------------------------------
/features/profile/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/features/profile/consumer-rules.pro
--------------------------------------------------------------------------------
/features/profile/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/features/profile/src/androidTest/java/com/iamkamrul/profile/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.profile
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.iamkamrul.profile.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/features/profile/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/features/profile/src/main/java/com/iamkamrul/profile/ProfileFragment.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.profile
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.viewModels
5 | import com.iamkamrul.common.base.BaseFragment
6 | import com.iamkamrul.profile.databinding.FragmentProfileBinding
7 | import com.squareup.picasso.Picasso
8 | import dagger.hilt.android.AndroidEntryPoint
9 |
10 | @AndroidEntryPoint
11 | class ProfileFragment : BaseFragment(){
12 | override fun viewBindingLayout(): FragmentProfileBinding =
13 | FragmentProfileBinding.inflate(layoutInflater)
14 |
15 | private val viewModel:ProfileViewModel by viewModels()
16 |
17 | override fun initializeView(savedInstanceState: Bundle?) {
18 | observeViewState()
19 | }
20 |
21 | private fun observeViewState(){
22 | viewModel.uiState.execute { state ->
23 | when(state){
24 | is UserProfileUiState.Loading->binding.root.setSimpleProgressBarStatusLayout(state.isLoading)
25 | is UserProfileUiState.Success->{
26 | Picasso.get().load(state.data.userAvatar).into(binding.profileIv)
27 | binding.fullNameTv.text = state.data.userFullName
28 | binding.userName.text = state.data.userName
29 | binding.aboutTv.text = state.data.about
30 | binding.repositoryCountTv.text = "${state.data.repoCount}"
31 | binding.followingCountTv.text = "${state.data.followingCount}"
32 | binding.followerCountTv.text = "${state.data.followerCount}"
33 | }
34 | is UserProfileUiState.Error->binding.root.showSimpleNetworkErrorLayout(message = state.message){
35 | viewModel.action(UserProfileUiAction.FetchUserProfile())
36 | }
37 | }
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/features/profile/src/main/java/com/iamkamrul/profile/ProfileViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.profile
2 |
3 | import com.iamkamrul.common.base.BaseViewModel
4 | import com.iamkamrul.domain.base.Result
5 | import com.iamkamrul.domain.usecase.UserProfileUseCase
6 | import com.iamkamrul.entity.UserProfileEntity
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import kotlinx.coroutines.flow.MutableStateFlow
9 | import kotlinx.coroutines.flow.StateFlow
10 | import javax.inject.Inject
11 |
12 | @HiltViewModel
13 | class ProfileViewModel @Inject constructor(
14 | private val userProfileUseCase: UserProfileUseCase
15 | ) : BaseViewModel(){
16 |
17 | val action: (UserProfileUiAction) -> Unit
18 |
19 | private val _uiState = MutableStateFlow>(UserProfileUiState.Loading(isLoading = true))
20 | val uiState: StateFlow> get() = _uiState
21 |
22 |
23 | init {
24 | action = {
25 | when(it){
26 | is UserProfileUiAction.FetchUserProfile -> fetchUserProfile(it)
27 | }
28 | }
29 | action(UserProfileUiAction.FetchUserProfile())
30 | }
31 |
32 | private fun fetchUserProfile(data:UserProfileUiAction.FetchUserProfile){
33 | execute {
34 | userProfileUseCase.execute(UserProfileUseCase.Params(userName = data.userName)).collect{result->
35 | when(result){
36 | is Result.Success-> _uiState.value = UserProfileUiState.Success(result.data)
37 | is Result.Error-> _uiState.value = UserProfileUiState.Error(result.message)
38 | is Result.Loading -> _uiState.value = UserProfileUiState.Loading(result.loadingState)
39 | }
40 | }
41 | }
42 | }
43 | }
44 |
45 | sealed class UserProfileUiState{
46 | data class Loading(val isLoading:Boolean):UserProfileUiState()
47 | data class Error(val message:String):UserProfileUiState()
48 | data class Success(val data:UserProfileEntity):UserProfileUiState()
49 | }
50 |
51 | sealed class UserProfileUiAction{
52 | data class FetchUserProfile(val userName:String = "kamrul3288"):UserProfileUiAction()
53 | }
--------------------------------------------------------------------------------
/features/profile/src/main/res/layout/fragment_profile.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
19 |
20 |
32 |
33 |
42 |
43 |
56 |
57 |
66 |
67 |
78 |
79 |
87 |
88 |
92 |
93 |
94 |
95 |
105 |
106 |
114 |
115 |
119 |
120 |
121 |
122 |
132 |
133 |
141 |
142 |
146 |
147 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/features/profile/src/test/java/com/iamkamrul/profile/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.profile
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 | }
--------------------------------------------------------------------------------
/features/repolist/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/features/repolist/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import dependencies.addCircleImageviewDependencies
2 | import dependencies.addPicassoDependencies
3 |
4 | plugins {
5 | plugins.`android-base-library-feature`
6 | }
7 |
8 | dependencies {
9 | addPicassoDependencies()
10 | addCircleImageviewDependencies()
11 | }
--------------------------------------------------------------------------------
/features/repolist/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/features/repolist/consumer-rules.pro
--------------------------------------------------------------------------------
/features/repolist/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/features/repolist/src/androidTest/java/com/iamkamrul/repolist/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.repolist
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.iamkamrul.repolist.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/features/repolist/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/features/repolist/src/main/java/com/iamkamrul/repolist/RepoListAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.repolist
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.DiffUtil
7 | import com.iamkamrul.common.adapter.DataBoundListAdapter
8 | import com.iamkamrul.entity.RepositoryListItemEntity
9 | import com.iamkamrul.repolist.databinding.ItemRepoListBinding
10 | import com.squareup.picasso.Picasso
11 |
12 | class RepoListAdapter(
13 | private val onClick:(View.OnClickListener)
14 | ) : DataBoundListAdapter(
15 | diffCallback = object : DiffUtil.ItemCallback(){
16 | override fun areItemsTheSame(oldItem: RepositoryListItemEntity, newItem: RepositoryListItemEntity): Boolean {
17 | return oldItem.repoFullName == newItem.repoFullName
18 | }
19 |
20 | override fun areContentsTheSame(oldItem: RepositoryListItemEntity, newItem: RepositoryListItemEntity): Boolean {
21 | return oldItem.repoFullName == newItem.repoFullName
22 | }
23 | }
24 | ){
25 | override fun createBinding(parent: ViewGroup): ItemRepoListBinding =
26 | ItemRepoListBinding.inflate(LayoutInflater.from(parent.context),parent,false)
27 |
28 | override fun bind(binding: ItemRepoListBinding, item: RepositoryListItemEntity, position: Int) {
29 | binding.root.setOnClickListener(onClick)
30 | Picasso.get().load(item.userAvatarUrl).into(binding.avatarIv)
31 | binding.nameTv.text = item.userName
32 | binding.repoShortNameTv.text = item.repoName
33 | binding.repoFullNameTv.text = item.repoFullName
34 | binding.descriptionTv.text = item.repoDescription
35 | binding.languageTv.text = item.language
36 | binding.staredTv.text = "${item.stargazers_count} Stars"
37 | binding.forkedTv.text = "${item.stargazers_count} Forked"
38 | }
39 | }
--------------------------------------------------------------------------------
/features/repolist/src/main/java/com/iamkamrul/repolist/RepoListFragment.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.repolist
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.viewModels
5 | import androidx.navigation.fragment.findNavController
6 | import com.iamkamrul.common.base.BaseFragment
7 | import com.iamkamrul.common.extension.setUpVerticalRecyclerView
8 | import com.iamkamrul.repolist.databinding.FragmentRepoListBinding
9 | import dagger.hilt.android.AndroidEntryPoint
10 |
11 | @AndroidEntryPoint
12 | class RepoListFragment : BaseFragment() {
13 | override fun viewBindingLayout(): FragmentRepoListBinding =
14 | FragmentRepoListBinding.inflate(layoutInflater)
15 |
16 | private val viewModel: RepoListViewModel by viewModels()
17 | private val adapter = RepoListAdapter{
18 | findNavController().navigate(RepoListFragmentDirections.actionRepoListFragmentToProfileFragment())
19 | }
20 |
21 |
22 | override fun initializeView(savedInstanceState: Bundle?) {
23 | requireActivity().setUpVerticalRecyclerView(binding.repoListRv,adapter)
24 | observeViewState()
25 | }
26 |
27 | private fun observeViewState(){
28 | viewModel.uiState.execute { state ->
29 | when(state){
30 | is RepoListUiState.Loading->binding.root.setSimpleProgressBarStatusLayout(state.isLoading)
31 | is RepoListUiState.Success-> adapter.submitList(state.data)
32 | is RepoListUiState.Error->binding.root.showSimpleNetworkErrorLayout(message = state.message){
33 | viewModel.action(RepoListUiAction.FetchRepoList())
34 | }
35 | }
36 | }
37 | }
38 |
39 |
40 | }
--------------------------------------------------------------------------------
/features/repolist/src/main/java/com/iamkamrul/repolist/RepoListViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.repolist
2 |
3 | import com.iamkamrul.common.base.BaseViewModel
4 | import com.iamkamrul.domain.base.Result
5 | import com.iamkamrul.domain.usecase.RepositoryListUseCase
6 | import com.iamkamrul.entity.RepositoryListItemEntity
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import kotlinx.coroutines.flow.MutableStateFlow
9 | import kotlinx.coroutines.flow.StateFlow
10 | import kotlinx.coroutines.flow.collect
11 | import javax.inject.Inject
12 |
13 | @HiltViewModel
14 | class RepoListViewModel @Inject constructor(
15 | private val repoListUseCase: RepositoryListUseCase
16 | ): BaseViewModel() {
17 | val action: (RepoListUiAction) -> Unit
18 |
19 | private val _uiState = MutableStateFlow>(RepoListUiState.Loading(isLoading = true))
20 | val uiState: StateFlow> get() = _uiState
21 |
22 |
23 | init {
24 | action = {
25 | when(it){
26 | is RepoListUiAction.FetchRepoList -> fetchRepoList(it)
27 | }
28 | }
29 | action(RepoListUiAction.FetchRepoList())
30 | }
31 |
32 | private fun fetchRepoList(data:RepoListUiAction.FetchRepoList){
33 | execute {
34 | repoListUseCase.execute(RepositoryListUseCase.Params(userName = data.userName)).collect{result->
35 | when(result){
36 | is Result.Success-> _uiState.value = RepoListUiState.Success(result.data)
37 | is Result.Error-> _uiState.value = RepoListUiState.Error(result.message)
38 | is Result.Loading -> _uiState.value = RepoListUiState.Loading(result.loadingState)
39 | }
40 | }
41 | }
42 | }
43 | }
44 |
45 | sealed class RepoListUiState{
46 | data class Loading(val isLoading:Boolean):RepoListUiState()
47 | data class Error(val message:String):RepoListUiState()
48 | data class Success(val data:List):RepoListUiState>()
49 | }
50 |
51 | sealed class RepoListUiAction{
52 | data class FetchRepoList(val userName:String = "kamrul3288"):RepoListUiAction()
53 | }
--------------------------------------------------------------------------------
/features/repolist/src/main/res/layout/fragment_repo_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
22 |
23 |
--------------------------------------------------------------------------------
/features/repolist/src/main/res/layout/item_repo_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
16 |
17 |
26 |
27 |
40 |
41 |
50 |
51 |
61 |
62 |
75 |
76 |
85 |
86 |
101 |
102 |
113 |
114 |
126 |
127 |
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/features/repolist/src/test/java/com/iamkamrul/repolist/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.repolist
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 | }
--------------------------------------------------------------------------------
/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=-Xmx2048m -Dfile.encoding=UTF-8
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 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 10 18:35:52 BDT 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/model/apiresponse/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/model/apiresponse/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("java-library")
3 | id("org.jetbrains.kotlin.jvm")
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_11
8 | targetCompatibility = JavaVersion.VERSION_11
9 | }
--------------------------------------------------------------------------------
/model/apiresponse/src/main/java/com/iamkamrul/apiresponse/RepositoryListItemApiResponse.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.apiresponse
2 |
3 | data class RepositoryListItemApiResponse(
4 | val archive_url: String?,
5 | val archived: Boolean?,
6 | val assignees_url: String?,
7 | val blobs_url: String?,
8 | val branches_url: String?,
9 | val clone_url: String?,
10 | val collaborators_url: String?,
11 | val comments_url: String?,
12 | val commits_url: String?,
13 | val compare_url: String?,
14 | val contents_url: String?,
15 | val contributors_url: String?,
16 | val created_at: String?,
17 | val default_branch: String?,
18 | val deployments_url: String?,
19 | val description: String?,
20 | val disabled: Boolean?,
21 | val downloads_url: String?,
22 | val events_url: String?,
23 | val fork: Boolean?,
24 | val forks_count: Int?,
25 | val forks_url: String?,
26 | val full_name: String?,
27 | val git_commits_url: String?,
28 | val git_refs_url: String?,
29 | val git_tags_url: String?,
30 | val git_url: String?,
31 | val has_downloads: Boolean?,
32 | val has_issues: Boolean?,
33 | val has_pages: Boolean?,
34 | val has_projects: Boolean?,
35 | val has_wiki: Boolean?,
36 | val homepage: String?,
37 | val hooks_url: String?,
38 | val html_url: String?,
39 | val id: Int?,
40 | val is_template: Boolean?,
41 | val issue_comment_url: String?,
42 | val issue_events_url: String?,
43 | val issues_url: String?,
44 | val keys_url: String?,
45 | val labels_url: String?,
46 | val language: String?,
47 | val languages_url: String?,
48 | val merges_url: String?,
49 | val milestones_url: String?,
50 | val mirror_url: String?,
51 | val name: String?,
52 | val node_id: String?,
53 | val notifications_url: String?,
54 | val open_issues_count: Int?,
55 | val owner: RepositoryListItemOwner?,
56 | val permissions: RepositoryListItemPermissions?,
57 | val `private`: Boolean?,
58 | val pulls_url: String?,
59 | val pushed_at: String?,
60 | val releases_url: String?,
61 | val size: Int?,
62 | val ssh_url: String?,
63 | val stargazers_count: Int?,
64 | val stargazers_url: String?,
65 | val statuses_url: String?,
66 | val subscribers_url: String?,
67 | val subscription_url: String?,
68 | val svn_url: String?,
69 | val tags_url: String?,
70 | val teams_url: String?,
71 | val template_repository: Any?,
72 | val topics: List?,
73 | val trees_url: String?,
74 | val updated_at: String?,
75 | val url: String?,
76 | val visibility: String?,
77 | val watchers_count: Int?
78 | )
79 |
80 | data class RepositoryListItemOwner(
81 | val avatar_url: String?,
82 | val events_url: String?,
83 | val followers_url: String?,
84 | val following_url: String?,
85 | val gists_url: String?,
86 | val gravatar_id: String?,
87 | val html_url: String?,
88 | val id: Int?,
89 | val login: String?,
90 | val node_id: String?,
91 | val organizations_url: String?,
92 | val received_events_url: String?,
93 | val repos_url: String?,
94 | val site_admin: Boolean?,
95 | val starred_url: String?,
96 | val subscriptions_url: String?,
97 | val type: String?,
98 | val url: String?
99 | )
100 |
101 | data class RepositoryListItemPermissions(
102 | val admin: Boolean?,
103 | val pull: Boolean?,
104 | val push: Boolean?
105 | )
--------------------------------------------------------------------------------
/model/apiresponse/src/main/java/com/iamkamrul/apiresponse/UserProfileApiResponse.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.apiresponse
2 |
3 | data class UserProfileApiResponse(
4 | val avatar_url: String?,
5 | val bio: String?,
6 | val blog: String?,
7 | val company: String?,
8 | val created_at: String?,
9 | val email: String?,
10 | val events_url: String?,
11 | val followers: Int?,
12 | val followers_url: String?,
13 | val following: Int?,
14 | val following_url: String?,
15 | val gists_url: String?,
16 | val gravatar_id: String?,
17 | val hireable: Boolean?,
18 | val html_url: String?,
19 | val id: Int?,
20 | val location: String?,
21 | val login: String?,
22 | val name: String?,
23 | val node_id: String?,
24 | val organizations_url: String?,
25 | val public_gists: Int?,
26 | val public_repos: Int?,
27 | val received_events_url: String?,
28 | val repos_url: String?,
29 | val site_admin: Boolean?,
30 | val starred_url: String?,
31 | val subscriptions_url: String?,
32 | val twitter_username: String?,
33 | val type: String?,
34 | val updated_at: String?,
35 | val url: String?
36 | )
--------------------------------------------------------------------------------
/model/entity/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/model/entity/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("java-library")
3 | id("org.jetbrains.kotlin.jvm")
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_11
8 | targetCompatibility = JavaVersion.VERSION_11
9 | }
--------------------------------------------------------------------------------
/model/entity/src/main/java/com/iamkamrul/entity/RepositoryListItemEntity.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.entity
2 |
3 | data class RepositoryListItemEntity(
4 | val userAvatarUrl:String,
5 | val userName:String,
6 | val repoName:String,
7 | val repoFullName:String,
8 | val repoDescription:String,
9 | val language:String,
10 | val forksCount:Int,
11 | val stargazers_count:Int,
12 |
13 | )
14 |
--------------------------------------------------------------------------------
/model/entity/src/main/java/com/iamkamrul/entity/UserProfileEntity.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.entity
2 |
3 | data class UserProfileEntity(
4 | val userAvatar:String,
5 | val userFullName:String,
6 | val userName:String,
7 | val about:String,
8 | val repoCount:Int,
9 | val followerCount:Int,
10 | val followingCount:Int,
11 | )
12 |
--------------------------------------------------------------------------------
/navigation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/navigation/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import dependencies.*
2 |
3 | plugins {
4 | plugins.`android-base-library`
5 | id ("androidx.navigation.safeargs.kotlin")
6 | }
7 |
8 | dependencies{
9 | addAndroidxNavigationDependencies()
10 | addEntityModule()
11 | addAndroidTestsDependencies()
12 | }
--------------------------------------------------------------------------------
/navigation/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamrul3288/MVVM-Clean-Architecture-Modularization/578b4e5d559a0fa56610804b7eaac3a2ab3f7ff8/navigation/consumer-rules.pro
--------------------------------------------------------------------------------
/navigation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/navigation/src/androidTest/java/com/iamkamrul/navigation/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.navigation
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.iamkamrul.navigation.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/navigation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/navigation/src/main/res/navigation/nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
14 |
15 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/navigation/src/test/java/com/iamkamrul/navigation/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.iamkamrul.navigation
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 | }
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | mavenCentral()
5 | mavenLocal()
6 | google()
7 | maven ("https://jitpack.io")
8 | maven ("https://oss.jfrog.org/libs-snapshot")
9 | }
10 | }
11 | rootProject.name = "GithubRepo"
12 | include (":app")
13 | include(":model:apiresponse")
14 | include(":model:entity")
15 | include(":domain")
16 | include(":data")
17 | include(":di")
18 | include(":common")
19 | include(":features:repolist")
20 | include(":navigation")
21 | include(":features:profile")
22 |
--------------------------------------------------------------------------------