├── .gitignore
├── .idea
├── .gitignore
├── .name
├── compiler.xml
├── gradle.xml
├── jarRepositories.xml
└── misc.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── template
│ │ └── cleanarch
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── template
│ │ │ └── cleanarch
│ │ │ ├── CleanApp.kt
│ │ │ ├── di
│ │ │ ├── AppInjector.kt
│ │ │ ├── component
│ │ │ │ └── ApplicationComponent.kt
│ │ │ └── module
│ │ │ │ ├── ApplicationModule.kt
│ │ │ │ ├── DbModule.kt
│ │ │ │ └── ViewModelModule.kt
│ │ │ ├── presentation
│ │ │ └── view
│ │ │ │ ├── MainActivity.kt
│ │ │ │ └── MainViewModel.kt
│ │ │ └── utils
│ │ │ ├── LocaleManager.kt
│ │ │ ├── PreferenceUtil.kt
│ │ │ └── SharedPrefHelper.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── navigation
│ │ └── nav_graph_main.xml
│ │ ├── values-night
│ │ └── themes.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── test
│ ├── java
│ └── com
│ │ └── template
│ │ └── cleanarch
│ │ ├── LoginViewModelTest.kt
│ │ ├── base
│ │ ├── BaseTest.kt
│ │ └── MainCoroutineRule.kt
│ │ └── di
│ │ ├── MainDIComponent.kt
│ │ ├── MockWebServerDITest.kt
│ │ └── NetworkDITest.kt
│ └── resources
│ └── login_success.json
├── benchmark
├── benchmark-proguard-rules.pro
├── build.gradle
└── src
│ ├── androidTest
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── template
│ │ └── benchmark
│ │ └── ExampleBenchmark.kt
│ └── main
│ └── AndroidManifest.xml
├── build.gradle
├── commons.features.gradle
├── commons.gradle
├── core
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── template
│ │ └── core
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── template
│ │ │ └── core
│ │ │ ├── arc
│ │ │ └── SingleLiveEvent.kt
│ │ │ ├── common
│ │ │ ├── Constants.kt
│ │ │ └── SharedPrefConstants.kt
│ │ │ ├── config
│ │ │ └── Configuration.kt
│ │ │ ├── contract
│ │ │ └── SubscriptionContract.kt
│ │ │ ├── di
│ │ │ ├── Injectable.kt
│ │ │ └── module
│ │ │ │ └── application
│ │ │ │ ├── OkhttpModule.kt
│ │ │ │ └── RetrofitModule.kt
│ │ │ ├── interceptors
│ │ │ └── HeaderInterceptor.kt
│ │ │ ├── listeners
│ │ │ ├── BackButtonHandlerListener.kt
│ │ │ └── BackPressListner.kt
│ │ │ ├── ui
│ │ │ └── base
│ │ │ │ ├── BaseActivity.kt
│ │ │ │ └── BaseFragment.kt
│ │ │ ├── utils
│ │ │ ├── LocaleManager.kt
│ │ │ ├── NavigationCommand.kt
│ │ │ ├── PreferenceUtil.kt
│ │ │ └── SharedPrefHelper.kt
│ │ │ └── viewmodel
│ │ │ ├── SharedViewModel.kt
│ │ │ ├── ToolbarPropertyViewModel.kt
│ │ │ └── base
│ │ │ └── BaseViewModel.kt
│ └── res
│ │ ├── anim
│ │ ├── anim_enter.xml
│ │ ├── anim_exit.xml
│ │ ├── anim_frag_fade_in.xml
│ │ ├── anim_frag_fade_out.xml
│ │ ├── anim_pop_enter.xml
│ │ ├── anim_pop_exit.xml
│ │ ├── anim_slide_in_up.xml
│ │ ├── anim_slide_out_up.xml
│ │ ├── pop_enter.xml
│ │ ├── pop_exit.xml
│ │ ├── right_in.xml
│ │ ├── right_out.xml
│ │ ├── slide_down.xml
│ │ ├── slide_down_fade_out_anim.xml
│ │ ├── slide_down_half.xml
│ │ ├── slide_from_top_right.xml
│ │ ├── slide_in_from_left.xml
│ │ ├── slide_in_from_rigth.xml
│ │ ├── slide_left_fade_out_anim.xml
│ │ ├── slide_out_to_left.xml
│ │ ├── slide_out_to_right.xml
│ │ └── slide_up.xml
│ │ └── values
│ │ ├── do_not_translate.xml
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── template
│ └── core
│ └── ExampleUnitTest.kt
├── data
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── template
│ │ └── data
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── template
│ │ └── data
│ │ ├── constants
│ │ └── NetworkConstants.kt
│ │ ├── datasource
│ │ ├── local
│ │ │ ├── dao
│ │ │ │ ├── BaseDao.kt
│ │ │ │ └── BranchDao.kt
│ │ │ ├── database
│ │ │ │ └── AppDb.kt
│ │ │ └── entity
│ │ │ │ └── Branch.kt
│ │ └── remote
│ │ │ ├── api
│ │ │ └── IAuthApi.kt
│ │ │ └── dto
│ │ │ ├── AuthDto.kt
│ │ │ ├── CommonDto.kt
│ │ │ └── ErrorDto.kt
│ │ ├── di
│ │ ├── ApiModule.kt
│ │ └── RepositoryModule.kt
│ │ ├── mapper
│ │ └── dtotoentity
│ │ │ ├── AuthDtoToEntity.kt
│ │ │ └── CommonDtoToEntity.kt
│ │ └── repository
│ │ ├── AuthRepositoryImpl.kt
│ │ └── BaseRepositoryImpl.kt
│ └── test
│ └── java
│ └── com
│ └── template
│ └── data
│ └── ExampleUnitTest.kt
├── domain
├── .gitignore
├── build.gradle
└── src
│ └── main
│ └── java
│ └── com
│ └── template
│ └── domain
│ ├── common
│ └── ResultState.kt
│ ├── di
│ └── UseCaseModule.kt
│ ├── entity
│ ├── request
│ │ └── AuthRequest.kt
│ └── response
│ │ ├── auth
│ │ └── AuthEntity.kt
│ │ └── common
│ │ ├── CommonEntity.kt
│ │ └── ErrorEntity.kt
│ ├── repository
│ └── IAuthRepository.kt
│ └── usecases
│ ├── auth
│ ├── AuthUseCaseImpl.kt
│ └── IAuthUseCase.kt
│ └── base
│ └── BaseUseCase.kt
├── features
├── onboarding
│ ├── .gitignore
│ ├── build.gradle
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── template
│ │ │ └── feature_onboarding
│ │ │ └── ExampleInstrumentedTest.kt
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── template
│ │ │ │ └── feature_onboarding
│ │ │ │ ├── di
│ │ │ │ └── OnboardingViewModelModule.kt
│ │ │ │ └── view
│ │ │ │ └── fragment
│ │ │ │ ├── signin
│ │ │ │ ├── LoginFragment.kt
│ │ │ │ └── LoginViewModel.kt
│ │ │ │ └── splash
│ │ │ │ ├── SplashFragment.kt
│ │ │ │ └── SplashViewModel.kt
│ │ └── res
│ │ │ ├── drawable-hdpi
│ │ │ └── ic_launcher_background.xml
│ │ │ ├── drawable-mdpi
│ │ │ └── ic_launcher_background.xml
│ │ │ ├── drawable-xhdpi
│ │ │ └── ic_launcher_background.xml
│ │ │ ├── drawable-xxhdpi
│ │ │ └── ic_launcher_background.xml
│ │ │ ├── drawable-xxxhdpi
│ │ │ └── ic_launcher_background.xml
│ │ │ ├── drawable
│ │ │ └── ic_launcher_background.xml
│ │ │ ├── layout
│ │ │ ├── fragment_login.xml
│ │ │ └── fragment_splash.xml
│ │ │ ├── navigation
│ │ │ └── nav_graph_onboarding.xml
│ │ │ └── values
│ │ │ └── ids.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── template
│ │ └── feature_onboarding
│ │ └── ExampleUnitTest.kt
└── profile
│ ├── .gitignore
│ ├── build.gradle
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── template
│ │ └── feature_profile
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── template
│ │ │ └── feature_profile
│ │ │ ├── di
│ │ │ └── ProfileViewModelModule.kt
│ │ │ ├── edit
│ │ │ ├── EditProfileFragment.kt
│ │ │ └── EditProfileViewModel.kt
│ │ │ └── profile
│ │ │ ├── ProfileFragment.kt
│ │ │ └── ProfileViewModel.kt
│ └── res
│ │ ├── drawable-hdpi
│ │ └── ic_launcher_background.xml
│ │ ├── drawable-mdpi
│ │ └── ic_launcher_background.xml
│ │ ├── drawable-xhdpi
│ │ └── ic_launcher_background.xml
│ │ ├── drawable-xxhdpi
│ │ └── ic_launcher_background.xml
│ │ ├── drawable-xxxhdpi
│ │ └── ic_launcher_background.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── fragment_edit_profile.xml
│ │ └── fragment_profile.xml
│ │ ├── navigation
│ │ └── nav_graph_profile.xml
│ │ └── values
│ │ └── ids.xml
│ └── test
│ └── java
│ └── com
│ └── template
│ └── feature_profile
│ └── ExampleUnitTest.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshots
├── app_modules.png
├── arch.png
├── deeplink_onboarding.png
├── goback.png
├── ids.png
├── main_nav_graph.png
├── module_to_module.png
├── onboarding_login.png
├── onboarding_nav_graph.png
├── profile.png
├── profile_deeplink.png
├── profile_ids.png
└── profile_nav_graph.png
├── settings.gradle
└── thirdpartys
└── analyticslib
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
├── androidTest
└── java
│ └── com
│ └── template
│ └── analyticslib
│ └── ExampleInstrumentedTest.kt
├── main
├── AndroidManifest.xml
└── java
│ └── com
│ └── template
│ └── analyticslib
│ ├── AnalyicsHelper.kt
│ └── FirebaseAnalyticsHelper.kt
└── test
└── java
└── com
└── template
└── analyticslib
└── ExampleUnitTest.kt
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | CleanArchApp
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
29 |
30 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | id 'kotlin-kapt'
5 | id 'androidx.navigation.safeargs'
6 | }
7 | apply from: "$rootProject.projectDir/commons.gradle"
8 |
9 | android {
10 | compileSdkVersion rootProject.compileSdkVersion
11 | buildToolsVersion rootProject.buildToolsVersion
12 |
13 | defaultConfig {
14 | applicationId "com.template.cleanarch"
15 | minSdkVersion rootProject.minSdkVersion
16 | targetSdkVersion rootProject.targetSdkVersion
17 | versionCode 1
18 | versionName "1.0"
19 |
20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_1_8
31 | targetCompatibility JavaVersion.VERSION_1_8
32 | }
33 | kotlinOptions {
34 | jvmTarget = '1.8'
35 | }
36 |
37 | buildFeatures {
38 | dataBinding true
39 | }
40 |
41 | flavorDimensions "server"
42 |
43 | productFlavors {
44 |
45 | dev {
46 | dimension "server"
47 | }
48 |
49 | uat {
50 | dimension "server"
51 | }
52 |
53 | pilot {
54 | dimension "server"
55 | }
56 |
57 | prod {
58 | dimension "server"
59 | }
60 | }
61 |
62 | publishNonDefault true
63 |
64 | configurations {
65 | devDebugImplementation
66 | devReleaseImplementation
67 | uatDebugImplementation
68 | uatReleaseImplementation
69 | pilotDebugImplementation
70 | pilotReleaseImplementation
71 | prodDebugImplementation
72 | prodReleaseImplementation
73 | }
74 |
75 | packagingOptions {
76 | exclude 'META-INF/DEPENDENCIES'
77 | exclude 'META-INF/LICENSE'
78 | exclude 'META-INF/LICENSE.txt'
79 | exclude 'META-INF/license.txt'
80 | exclude 'META-INF/NOTICE'
81 | exclude 'META-INF/NOTICE.txt'
82 | exclude 'META-INF/notice.txt'
83 | exclude 'META-INF/AL2.0'
84 | exclude 'META-INF/LGPL2.1'
85 | exclude("META-INF/*.kotlin_module")
86 | }
87 |
88 | }
89 |
90 | dependencies {
91 |
92 | api project(':core')
93 | implementation project(':features:onboarding')
94 | implementation project(':features:profile')
95 |
96 | // Koin AndroidX Scope features
97 | implementation "org.koin:koin-androidx-scope:$koinVersion"
98 | implementation "org.koin:koin-androidx-viewmodel:$koinVersion"
99 | implementation "org.koin:koin-androidx-fragment:$koinVersion"
100 | implementation "org.koin:koin-androidx-ext:$koinVersion"
101 | implementation "org.koin:koin-androidx-scope:$koinVersion"
102 | implementation "org.koin:koin-androidx-viewmodel:$koinVersion"
103 | implementation "org.koin:koin-core:$koinVersion"
104 |
105 | //Calligraphy
106 | implementation "io.github.inflationx:calligraphy3:$calligrapyVersion"
107 | implementation "io.github.inflationx:viewpump:$viewpumpVersion"
108 |
109 | // Navigation Jetpack
110 | implementation "androidx.navigation:navigation-fragment-ktx:$navVersion"
111 | implementation "androidx.navigation:navigation-ui-ktx:$navVersion"
112 |
113 | //Retrofit
114 | implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
115 | implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
116 | implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion"
117 | implementation "com.squareup.okhttp3:logging-interceptor:$okhttpLoggingVersion"
118 | implementation "com.squareup.okio:okio:$okioVersion"
119 | implementation "com.squareup.okhttp3:okhttp-urlconnection:$urlConnectionVersion"
120 |
121 |
122 | //Coroutine
123 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
124 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion"
125 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutineVersion"
126 | implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$coroutineAdapterVersion"
127 |
128 | /* Android Architecture Component - Room Persistance Lib */
129 | implementation "androidx.room:room-runtime:$roomVersion"
130 | kapt "androidx.room:room-compiler:$roomVersion"
131 | implementation "androidx.room:room-common:$roomVersion"
132 |
133 | implementation "androidx.constraintlayout:constraintlayout:$constraintLayoutVersion"
134 |
135 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/template/cleanarch/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch
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.template.cleanarch", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/template/cleanarch/CleanApp.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import com.template.cleanarch.di.AppInjector
6 | import com.template.cleanarch.di.component.ApplicationComponent.loadAllModules
7 | import org.koin.android.ext.koin.androidContext
8 | import org.koin.core.context.startKoin
9 |
10 | /****
11 | * Application class
12 | * Author: Lajesh Dineshkumar
13 | * Created on: 1/31/21
14 | * Modified on: 1/31/21
15 | *****/
16 | class CleanApp : Application() {
17 |
18 | private var localeContext: Context? = null
19 |
20 | init {
21 | instance = this
22 | }
23 |
24 | companion object {
25 | private lateinit var instance: CleanApp
26 |
27 | private var isAppVisible: Boolean = false
28 |
29 | fun applicationContext(): Context {
30 | return instance.applicationContext
31 | }
32 |
33 | fun localeContext(): Context {
34 | return instance.localeContext ?: instance.applicationContext
35 | }
36 |
37 | fun getInstance(): CleanApp {
38 | return instance
39 | }
40 |
41 | fun setInstance(application: CleanApp) {
42 | instance = application
43 | }
44 |
45 | fun isApplicationVisible(): Boolean {
46 | return isAppVisible
47 | }
48 |
49 | fun setAppVisible(isVisible: Boolean) {
50 | isAppVisible = isVisible
51 | }
52 | }
53 |
54 | override fun onCreate() {
55 | super.onCreate()
56 |
57 | AppInjector.init(this)
58 |
59 | startKoin {
60 | androidContext(this@CleanApp)
61 | }
62 |
63 | loadAllModules()
64 | }
65 |
66 | fun setLocaleContext(context: Context) {
67 | this.localeContext = context
68 | }
69 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/template/cleanarch/di/AppInjector.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch.di
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.os.Bundle
6 | import com.template.cleanarch.CleanApp
7 |
8 | /**
9 | * Helper class to automatically inject fragments if they implement [Injectable].
10 | * Created by Lajesh Dineshkumar on 10/30/2019.
11 | * Company:
12 | * Email: lajeshds2007@gmail.com
13 | */
14 | object AppInjector {
15 |
16 | private var resumed = 0
17 | private var paused = 0
18 |
19 | fun init(cleanApp: CleanApp) {
20 | cleanApp.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
21 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
22 | cleanApp.setLocaleContext(activity)
23 | }
24 |
25 | override fun onActivityStarted(activity: Activity) {
26 | // Nothing goes here
27 | }
28 |
29 | override fun onActivityResumed(activity: Activity) {
30 | ++resumed
31 | CleanApp.setAppVisible(true)
32 | }
33 |
34 | override fun onActivityPaused(activity: Activity) {
35 | ++paused
36 | }
37 |
38 | override fun onActivityStopped(activity: Activity) {
39 | CleanApp.setAppVisible(resumed > paused)
40 | }
41 |
42 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
43 | // Nothing goes here
44 | }
45 |
46 | override fun onActivityDestroyed(activity: Activity) {
47 | // Nothing goes here
48 | }
49 | })
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/com/template/cleanarch/di/component/ApplicationComponent.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch.di.component
2 |
3 | import com.template.cleanarch.di.module.DbModule
4 | import com.template.cleanarch.di.module.ViewModelModule
5 | import com.template.core.di.module.application.RetrofitModule
6 | import com.template.cleanarch.di.module.ApplicationModule
7 | import com.template.core.di.module.application.OkhttpModule
8 | import com.template.data.di.ApiModule
9 | import com.template.data.di.RepositoryModule
10 | import com.template.domain.di.UseCaseModule
11 | import com.template.feature_onboarding.di.OnboardingViewModelModule
12 | import com.template.feature_profile.di.ProfileViewModelModule
13 |
14 | /****
15 | * Application component which loads all the Koin Modules
16 | * Author: Lajesh Dineshkumar
17 | * Company:
18 | * Created on: 2/2/21
19 | * Modified on: 2/2/21
20 | *****/
21 | object ApplicationComponent {
22 | fun loadAllModules(){
23 | RetrofitModule.load()
24 | OkhttpModule.load()
25 | RepositoryModule.load()
26 | UseCaseModule.load()
27 | ApiModule.load()
28 | ApplicationModule.load()
29 | ViewModelModule.load()
30 | OnboardingViewModelModule.load()
31 | ProfileViewModelModule.load()
32 | DbModule.load()
33 |
34 | }
35 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/template/cleanarch/di/module/ApplicationModule.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch.di.module
2 |
3 | import org.koin.core.context.loadKoinModules
4 | import org.koin.dsl.module
5 |
6 | /****
7 | * File Description
8 | * Author: Lajesh Dineshkumar
9 | * Company:
10 | * Created on: 2/2/21
11 | * Modified on: 2/2/21
12 | *****/
13 | object ApplicationModule {
14 | fun load() {
15 | loadKoinModules(module {
16 |
17 | })
18 | }
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/template/cleanarch/di/module/DbModule.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch.di.module
2 |
3 | import androidx.room.Room
4 | import androidx.room.RoomDatabase
5 | import androidx.sqlite.db.SupportSQLiteDatabase
6 | import com.template.cleanarch.CleanApp
7 | import com.template.data.datasource.local.database.AppDb
8 | import org.koin.core.context.loadKoinModules
9 | import org.koin.dsl.module
10 |
11 | /****
12 | * Database Module
13 | * Author: Lajesh Dineshkumar
14 | * Company:
15 | * Created on: 3/14/21
16 | * Modified on: 3/14/21
17 | *****/
18 | object DbModule {
19 | fun load() {
20 | loadKoinModules(dbModules )
21 | }
22 |
23 | val dbModules = module {
24 |
25 | single {
26 | Room.databaseBuilder(
27 | CleanApp.applicationContext(), AppDb::class.java, "cleanapp.db"
28 | ).allowMainThreadQueries()
29 | .addCallback(object : RoomDatabase.Callback() {
30 | override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
31 | // Clear data
32 | }
33 | })
34 | // Clear DB while upgrade or downgrade
35 | .fallbackToDestructiveMigration()
36 | .build()
37 | }
38 |
39 | single {
40 | get().branchDao()
41 | }
42 |
43 |
44 | }
45 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/template/cleanarch/di/module/ViewModelModule.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch.di.module
2 |
3 | import com.template.cleanarch.presentation.view.MainViewModel
4 | import com.template.core.viewmodel.SharedViewModel
5 | import com.template.core.viewmodel.ToolbarPropertyViewModel
6 | import org.koin.androidx.viewmodel.dsl.viewModel
7 | import org.koin.core.context.loadKoinModules
8 | import org.koin.dsl.module
9 |
10 | /****
11 | * DI module which provides all the viewmodel instances
12 | * Author: Lajesh Dineshkumar
13 | * Company:
14 | * Created on: 2/3/21
15 | * Modified on: 2/3/21
16 | *****/
17 | object ViewModelModule {
18 | fun load() {
19 | loadKoinModules(module {
20 | viewModel {
21 | MainViewModel()
22 | }
23 |
24 | viewModel {
25 | ToolbarPropertyViewModel()
26 | }
27 |
28 | viewModel {
29 | SharedViewModel()
30 | }
31 |
32 | })
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/template/cleanarch/presentation/view/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch.presentation.view
2 |
3 |
4 | import android.os.Bundle
5 | import androidx.navigation.Navigation
6 | import androidx.navigation.ui.NavigationUI
7 | import com.template.cleanarch.R
8 | import com.template.cleanarch.databinding.ActivityMainBinding
9 | import com.template.cleanarch.BR
10 | import com.template.core.ui.base.BaseActivity
11 |
12 |
13 | class MainActivity : BaseActivity(MainViewModel::class){
14 | override val layoutRes: Int
15 | get() = R.layout.activity_main
16 | override val bindingVariable: Int
17 | get() = BR.viewModel
18 |
19 | override fun getViewModel(): Class {
20 | return MainViewModel::class.java
21 | }
22 |
23 | override fun onCreate(savedInstanceState: Bundle?) {
24 | super.onCreate(savedInstanceState)
25 | NavigationUI.setupActionBarWithNavController(this,
26 | Navigation.findNavController(this, R.id.fragment_container_view));
27 | }
28 |
29 | override fun onSupportNavigateUp(): Boolean {
30 | return Navigation.findNavController(this, R.id.fragment_container_view).navigateUp()
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/template/cleanarch/presentation/view/MainViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch.presentation.view
2 |
3 | import com.template.core.viewmodel.base.BaseViewModel
4 |
5 | /****
6 | * File Description
7 | * Author: Lajesh Dineshkumar
8 | * Company:
9 | * Created on: 2/3/21
10 | * Modified on: 2/3/21
11 | *****/
12 | class MainViewModel : BaseViewModel() {
13 |
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/template/cleanarch/utils/PreferenceUtil.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch.utils
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 |
6 | /****
7 | * Shared Preference util class
8 | * ----------------------------
9 | * How to use ?
10 | * val prefs = PreferenceUtil.defaultPrefs(this)
11 | * set any type of value in prefs
12 | * prefs[PREFUSERID] = "name"
13 | * ----------------------------
14 | * Author: Lajesh Dineshkumar
15 | * Company:
16 | * Created on: 2020-03-02
17 | * Modified on: 2020-03-02
18 | *****/
19 | object PreferenceUtil {
20 |
21 | fun customPrefs(context: Context, name: String): SharedPreferences =
22 | context.getSharedPreferences(name, Context.MODE_PRIVATE)
23 |
24 | private inline fun SharedPreferences.edit(operation: (SharedPreferences.Editor) -> Unit) {
25 | val editor = this.edit()
26 | operation(editor)
27 | editor.apply()
28 | }
29 |
30 | /**
31 | * puts a key value pair in shared prefs if doesn't exists, otherwise updates value on given [key]
32 | */
33 | operator fun SharedPreferences.set(key: String, value: Any?) {
34 | when (value) {
35 | is String? -> edit { it.putString(key, value) }
36 | is Int -> edit { it.putInt(key, value) }
37 | is Boolean -> edit { it.putBoolean(key, value) }
38 | is Float -> edit { it.putFloat(key, value) }
39 | is Long -> edit { it.putLong(key, value) }
40 | else -> throw UnsupportedOperationException("Not yet implemented")
41 | }
42 | }
43 |
44 | /**
45 | * finds value on given key.
46 | * [T] is the type of value
47 | * @param defaultValue optional default value - will take null for strings, false for bool and -1 for numeric values if [defaultValue] is not specified
48 | */
49 | inline operator fun SharedPreferences.get(key: String, defaultValue: T? = null): T? {
50 | return when (T::class) {
51 | String::class -> getString(key, defaultValue as? String) as T?
52 | Int::class -> getInt(key, defaultValue as? Int ?: -1) as T?
53 | Boolean::class -> getBoolean(key, defaultValue as? Boolean ?: false) as T?
54 | Float::class -> getFloat(key, defaultValue as? Float ?: -1f) as T?
55 | Long::class -> getLong(key, defaultValue as? Long ?: -1) as T?
56 | else -> throw UnsupportedOperationException("Not yet implemented")
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/com/template/cleanarch/utils/SharedPrefHelper.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch.utils
2 |
3 | import android.content.SharedPreferences
4 | import com.template.cleanarch.CleanApp
5 | import com.template.cleanarch.utils.PreferenceUtil.get
6 | import com.template.cleanarch.utils.PreferenceUtil.set
7 | import com.template.core.common.SharedPrefConstants
8 | import com.template.core.common.SharedPrefConstants.DID_FAVORITE_OFFER
9 | import com.template.core.common.SharedPrefConstants.WALLET_ONBOARDING_COMPLETED
10 |
11 | /****
12 | * Keep all shared preference related methods here
13 | * Author: Lajesh Dineshkumar
14 | * Company:
15 | * Created on: 2020-03-02
16 | * Modified on: 2020-03-02
17 | *****/
18 | class SharedPrefHelper {
19 |
20 | private var sharedPreferences: SharedPreferences =
21 | PreferenceUtil.customPrefs(CleanApp.applicationContext(), SharedPrefConstants.APP_PREFS)
22 |
23 | fun getToken(): String? {
24 | return sharedPreferences[SharedPrefConstants.TOKEN]
25 | }
26 |
27 | fun setToken(token: String?) {
28 | sharedPreferences[SharedPrefConstants.TOKEN] = token
29 | }
30 |
31 | fun saveFcmToken(token: String?) {
32 | sharedPreferences[SharedPrefConstants.PREF_FCM_TOKEN] = token
33 | }
34 |
35 | fun getFCMToken(): String? {
36 | return sharedPreferences[SharedPrefConstants.PREF_FCM_TOKEN]
37 | }
38 |
39 | fun isUserLogged(): Boolean {
40 | return getUserID() != 0
41 | }
42 |
43 | fun setWalletOnBoardingCompleted(completed: Boolean) {
44 | sharedPreferences[WALLET_ONBOARDING_COMPLETED] = completed
45 | }
46 |
47 | fun doFavoriteOffer() {
48 | sharedPreferences[DID_FAVORITE_OFFER] = true
49 | }
50 |
51 | fun didFavoriteOffer(): Boolean = sharedPreferences[DID_FAVORITE_OFFER] ?: false
52 |
53 | fun isWalletOnBoardingCompleted(): Boolean {
54 | return sharedPreferences[WALLET_ONBOARDING_COMPLETED] ?: false
55 | }
56 |
57 | private fun getUserID(): Int {
58 | return (sharedPreferences[SharedPrefConstants.USER_ID, "0"] ?: "0").toInt()
59 | }
60 |
61 | fun getCurrentLocale(): LocaleManager.LocaleInfo? {
62 | return LocaleManager.getCurrentLocaleInfo(CleanApp.localeContext())
63 | }
64 | }
--------------------------------------------------------------------------------
/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 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
19 |
20 |
27 |
28 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/navigation/nav_graph_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | CleanArchApp
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/test/java/com/template/cleanarch/LoginViewModelTest.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch
2 |
3 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule
4 | import com.template.cleanarch.base.BaseTest
5 | import com.template.domain.usecases.auth.IAuthUseCase
6 | import com.template.feature_onboarding.view.fragment.signin.LoginViewModel
7 | import junit.framework.Assert.assertTrue
8 | import kotlinx.coroutines.ExperimentalCoroutinesApi
9 | import kotlinx.coroutines.test.runBlockingTest
10 | import okhttp3.mockwebserver.MockWebServer
11 | import org.junit.Assert
12 | import org.junit.Rule
13 | import org.junit.Test
14 | import org.junit.runner.RunWith
15 | import org.junit.runners.JUnit4
16 | import org.koin.test.inject
17 | import java.net.HttpURLConnection
18 |
19 | /****
20 | * Unit test for LogiViewModel
21 | * Author: Lajesh Dineshkumar
22 | * Company:
23 | * Created on: 3/1/21
24 | * Modified on: 3/1/21
25 | *****/
26 | @RunWith(JUnit4::class)
27 | @ExperimentalCoroutinesApi
28 | class LoginViewModelTest : BaseTest() {
29 | private lateinit var loginViewModel: LoginViewModel
30 |
31 | //Inject api service created with koin
32 | val authUseCase: IAuthUseCase by inject()
33 |
34 | //Inject Mockwebserver created with koin
35 | val mockWebServer: MockWebServer by inject()
36 |
37 | @get:Rule
38 | var instantExecutorRule = InstantTaskExecutorRule()
39 |
40 | override fun setUp() {
41 | super.setUp()
42 | loginViewModel = LoginViewModel(authUseCase, mainCoroutineRule.testDispatcher)
43 | }
44 |
45 | @Test
46 | @ExperimentalCoroutinesApi
47 | fun test_login_success_case() {
48 | mainCoroutineRule.testDispatcher.runBlockingTest {
49 | mockNetworkResponseWithFileContent("login_success.json", HttpURLConnection.HTTP_OK)
50 | loginViewModel.login()
51 | loginViewModel.addLikeCount()
52 | Assert.assertEquals(1, loginViewModel.getLikeCount())
53 | Assert.assertEquals(false, loginViewModel.data.value)
54 | testCoroutineScope.advanceTimeBy(2000)
55 |
56 | loginViewModel.errorEvent.observeForever {
57 | assertTrue(it.errorCode == 503)
58 | }
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/template/cleanarch/base/BaseTest.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch.base
2 |
3 | import com.template.cleanarch.di.configureTestAppComponent
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlinx.coroutines.ExperimentalCoroutinesApi
6 | import kotlinx.coroutines.test.TestCoroutineDispatcher
7 | import kotlinx.coroutines.test.TestCoroutineScope
8 | import kotlinx.coroutines.test.resetMain
9 | import kotlinx.coroutines.test.setMain
10 | import okhttp3.mockwebserver.MockResponse
11 | import okhttp3.mockwebserver.MockWebServer
12 | import org.junit.After
13 | import org.junit.Before
14 | import org.junit.Rule
15 | import org.koin.core.context.startKoin
16 | import org.koin.core.context.stopKoin
17 | import org.koin.test.KoinTest
18 | import java.io.File
19 |
20 | /****
21 | * Base class for all the unit tests
22 | * Author: Lajesh Dineshkumar
23 | * Company:
24 | * Created on: 2/22/21
25 | * Modified on: 2/22/21
26 | *****/
27 | @ExperimentalCoroutinesApi
28 | abstract class BaseTest : KoinTest {
29 | /**
30 | * For MockWebServer instance
31 | */
32 | private lateinit var mMockServerInstance: MockWebServer
33 |
34 | /**
35 | * Default, let server be shut down
36 | */
37 | private var mShouldStart = false
38 |
39 | val dispatcher = TestCoroutineDispatcher()
40 |
41 | val testCoroutineScope = TestCoroutineScope(dispatcher)
42 |
43 | @get:Rule
44 | var mainCoroutineRule = MainCoroutineRule()
45 |
46 | @Before
47 | open fun setUp(){
48 | startMockServer(true)
49 | startKoin{ modules(configureTestAppComponent(getMockWebServerUrl()))}
50 | Dispatchers.setMain(dispatcher)
51 | }
52 |
53 | /**
54 | * Helps to read input file returns the respective data in mocked call
55 | */
56 | fun mockNetworkResponseWithFileContent(fileName: String, responseCode: Int) = mMockServerInstance.enqueue(
57 | MockResponse()
58 | .setResponseCode(responseCode)
59 | .setBody(getJson(fileName)))
60 |
61 | /**
62 | * Reads input file and converts to json
63 | */
64 | fun getJson(path : String) : String {
65 | val uri = javaClass.classLoader!!.getResource(path)
66 | val file = File(uri.path)
67 | return String(file.readBytes())
68 | }
69 |
70 | /**
71 | * Start Mockwebserver
72 | */
73 | private fun startMockServer(shouldStart:Boolean){
74 | if (shouldStart){
75 | mShouldStart = shouldStart
76 | mMockServerInstance = MockWebServer()
77 | mMockServerInstance.start()
78 | }
79 | }
80 |
81 | /**
82 | * Set Mockwebserver url
83 | */
84 | fun getMockWebServerUrl() = mMockServerInstance.url("/").toString()
85 |
86 | /**
87 | * Stop Mockwebserver
88 | */
89 | private fun stopMockServer() {
90 | if (mShouldStart){
91 | mMockServerInstance.shutdown()
92 | }
93 | }
94 |
95 | @After
96 | open fun tearDown(){
97 | //Stop Mock server
98 | stopMockServer()
99 | //Stop Koin as well
100 | stopKoin()
101 |
102 | Dispatchers.resetMain()
103 | }
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/app/src/test/java/com/template/cleanarch/base/MainCoroutineRule.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch.base
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.ExperimentalCoroutinesApi
5 | import kotlinx.coroutines.test.TestCoroutineDispatcher
6 | import kotlinx.coroutines.test.resetMain
7 | import kotlinx.coroutines.test.setMain
8 | import org.junit.rules.TestWatcher
9 | import org.junit.runner.Description
10 |
11 | /****
12 | * File Description
13 | * Author: Lajesh Dineshkumar
14 |
15 | * Created on: 3/1/21
16 | * Modified on: 3/1/21
17 | *****/
18 | @ExperimentalCoroutinesApi
19 | class MainCoroutineRule(
20 | val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
21 | ) : TestWatcher() {
22 |
23 | override fun starting(description: Description?) {
24 | super.starting(description)
25 | Dispatchers.setMain(testDispatcher)
26 | }
27 |
28 | override fun finished(description: Description?) {
29 | super.finished(description)
30 | Dispatchers.resetMain()
31 | testDispatcher.cleanupTestCoroutines()
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/template/cleanarch/di/MainDIComponent.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch.di
2 |
3 | import com.template.data.di.ApiModule
4 | import com.template.data.di.RepositoryModule
5 | import com.template.domain.di.UseCaseModule
6 |
7 | /****
8 | * Main Koin DI component which helps to configure
9 | * Mockwebserver, Usecase and repository
10 | * Author: Lajesh Dineshkumar
11 | * Company:
12 | * Created on: 2/22/21
13 | * Modified on: 2/22/21
14 | *****/
15 |
16 | fun configureTestAppComponent(baseApi: String) = listOf(
17 | mockwebserverDITest,
18 | configureNetworkModuleForTest(baseApi),
19 | ApiModule.apiModules,
20 | UseCaseModule.authUSeCaseModules,
21 | RepositoryModule.repositoryModules
22 | )
--------------------------------------------------------------------------------
/app/src/test/java/com/template/cleanarch/di/MockWebServerDITest.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch.di
2 |
3 | import okhttp3.mockwebserver.MockWebServer
4 | import org.koin.dsl.module
5 |
6 | /****
7 | * Creates Mockwebserver instance for testing
8 | * Author: Lajesh Dineshkumar
9 | * Company:
10 | * Created on: 2/22/21
11 | * Modified on: 2/22/21
12 | *****/
13 |
14 | val mockwebserverDITest = module {
15 | factory {
16 | MockWebServer()
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/template/cleanarch/di/NetworkDITest.kt:
--------------------------------------------------------------------------------
1 | package com.template.cleanarch.di
2 |
3 | import com.google.gson.GsonBuilder
4 | import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
5 | import com.template.data.datasource.remote.api.IAuthApi
6 | import org.koin.core.qualifier.named
7 | import org.koin.dsl.module
8 | import retrofit2.Retrofit
9 | import retrofit2.converter.gson.GsonConverterFactory
10 |
11 | /****
12 | * Network module test configuration with mockserver url
13 | * Author: Lajesh Dineshkumar
14 | * Company:
15 | * Created on: 2/22/21
16 | * Modified on: 2/22/21
17 | *****/
18 | fun configureNetworkModuleForTest(baseApi: String) = module {
19 | single {
20 | Retrofit.Builder()
21 | .baseUrl(baseApi)
22 | .addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
23 | .addCallAdapterFactory(CoroutineCallAdapterFactory())
24 | .build()
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/app/src/test/resources/login_success.json:
--------------------------------------------------------------------------------
1 | {
2 | "status": 1,
3 | "data" : {
4 | "name": "Lajesh"
5 | }
6 | }
--------------------------------------------------------------------------------
/benchmark/benchmark-proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | -dontobfuscate
24 |
25 | -ignorewarnings
26 |
27 | -keepattributes *Annotation*
28 |
29 | -dontnote junit.framework.**
30 | -dontnote junit.runner.**
31 |
32 | -dontwarn androidx.test.**
33 | -dontwarn org.junit.**
34 | -dontwarn org.hamcrest.**
35 | -dontwarn com.squareup.javawriter.JavaWriter
36 |
37 | -keepclasseswithmembers @org.junit.runner.RunWith public class *
--------------------------------------------------------------------------------
/benchmark/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'androidx.benchmark'
4 | id 'kotlin-android'
5 | }
6 |
7 | android {
8 | compileSdkVersion rootProject.compileSdkVersion
9 | buildToolsVersion rootProject.buildToolsVersion
10 |
11 | compileOptions {
12 | sourceCompatibility = 1.8
13 | targetCompatibility = 1.8
14 | }
15 |
16 | kotlinOptions {
17 | jvmTarget = "1.8"
18 | }
19 |
20 | defaultConfig {
21 | minSdkVersion rootProject.minSdkVersion
22 | targetSdkVersion rootProject.targetSdkVersion
23 | versionCode 1
24 | versionName "1.0"
25 |
26 | testInstrumentationRunner 'androidx.benchmark.junit4.AndroidBenchmarkRunner'
27 | }
28 |
29 | testBuildType = "release"
30 | buildTypes {
31 | debug {
32 | // Since debuggable can"t be modified by gradle for library modules,
33 | // it must be done in a manifest - see src/androidTest/AndroidManifest.xml
34 | minifyEnabled true
35 | proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "benchmark-proguard-rules.pro"
36 | }
37 | release {
38 | isDefault = true
39 | }
40 | }
41 | }
42 |
43 | dependencies {
44 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
45 | androidTestImplementation "androidx.test:runner:$testRunnerVersion"
46 | androidTestImplementation "androidx.test.ext:junit:$junitExtVersion"
47 | androidTestImplementation "junit:junit:$junitVersion"
48 | androidTestImplementation "androidx.benchmark:benchmark-junit4:$benchmarkVersion"
49 |
50 | // Add your dependencies here. Note that you cannot benchmark code
51 | // in an app module this way - you will need to move any code you
52 | // want to benchmark to a library module:
53 | // https://developer.android.com/studio/projects/android-library#Convert
54 |
55 | }
--------------------------------------------------------------------------------
/benchmark/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
16 |
--------------------------------------------------------------------------------
/benchmark/src/androidTest/java/com/template/benchmark/ExampleBenchmark.kt:
--------------------------------------------------------------------------------
1 | package com.template.benchmark
2 |
3 | import android.util.Log
4 | import androidx.benchmark.junit4.BenchmarkRule
5 | import androidx.benchmark.junit4.measureRepeated
6 | import androidx.test.ext.junit.runners.AndroidJUnit4
7 | import org.junit.Rule
8 | import org.junit.Test
9 | import org.junit.runner.RunWith
10 |
11 | /**
12 | * Benchmark, which will execute on an Android device.
13 | *
14 | * The body of [BenchmarkRule.measureRepeated] is measured in a loop, and Studio will
15 | * output the result. Modify your code to see how it affects performance.
16 | */
17 | @RunWith(AndroidJUnit4::class)
18 | class ExampleBenchmark {
19 |
20 | @get:Rule
21 | val benchmarkRule = BenchmarkRule()
22 |
23 | @Test
24 | fun log() {
25 | benchmarkRule.measureRepeated {
26 | Log.d("LogBenchmark", "the cost of writing this log method will be measured")
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/benchmark/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext.kotlin_version = "1.4.21"
4 | ext{
5 | minSdkVersion = 25
6 | targetSdkVersion = 30
7 | compileSdkVersion = 30
8 | buildToolsVersion = '30.0.2'
9 | coreKtxVersion = '1.3.2'
10 | appCompatVersion = '1.2.0'
11 | materialVersion = '1.2.1'
12 | constraintLayoutVersion = '2.0.4'
13 | junitVersion = '4.13.1'
14 | junitExtVersion = '1.1.2'
15 | testXRulesVersion = '1.3.0'
16 | coreTestingVersion = '1.1.1'
17 | retrofitMockVersion = '2.3.0'
18 | mockVersion = '1.9.3'
19 | espressoVersion = '3.3.0'
20 | espressoCoreVersion = '3.3.0'
21 | mockWebserverVersion = '4.1.0'
22 | benchmarkVersion = '1.0.0'
23 | testRunnerVersion = '1.3.0'
24 | lifecycleVersion = '2.2.0'
25 | koinVersion = '2.1.6'
26 | retrofitVersion = '2.9.0'
27 | okioVersion = '2.8.0'
28 | okhttpLoggingVersion = '4.9.0'
29 | coroutineVersion = '1.3.8'
30 | roomVersion = '2.2.6'
31 | timberVersion = '4.7.1'
32 | urlConnectionVersion = '3.11.0'
33 | timberVersion = '4.7.1'
34 | calligrapyVersion = '3.1.1'
35 | viewpumpVersion = '2.0.3'
36 | navVersion = "2.3.3"
37 | lifecycleVersion = '2.2.0'
38 | analyticsVersion = '26.4.0'
39 | coroutineAdapterVersion = '0.9.2'
40 |
41 | }
42 | repositories {
43 | google()
44 | jcenter()
45 | maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
46 | }
47 | dependencies {
48 | classpath "com.android.tools.build:gradle:4.1.2"
49 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
50 | classpath 'androidx.benchmark:benchmark-gradle-plugin:1.0.0'
51 | classpath "org.koin:koin-gradle-plugin:$koinVersion"
52 | classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navVersion"
53 |
54 | }
55 | }
56 |
57 | allprojects {
58 | repositories {
59 | google()
60 | jcenter()
61 | }
62 | }
63 |
64 | task clean(type: Delete) {
65 | delete rootProject.buildDir
66 | }
--------------------------------------------------------------------------------
/commons.features.gradle:
--------------------------------------------------------------------------------
1 | dependencies {
2 | // Keep all dependencies here
3 | implementation project(':core')
4 | implementation project(':domain')
5 | implementation project(':data')
6 |
7 | // Koin AndroidX Scope features
8 | implementation "org.koin:koin-androidx-scope:$koinVersion"
9 | implementation "org.koin:koin-androidx-viewmodel:$koinVersion"
10 | implementation "org.koin:koin-androidx-fragment:$koinVersion"
11 | implementation "org.koin:koin-androidx-ext:$koinVersion"
12 | implementation "org.koin:koin-androidx-scope:$koinVersion"
13 | implementation "org.koin:koin-androidx-viewmodel:$koinVersion"
14 | implementation "org.koin:koin-core:$koinVersion"
15 |
16 | // Lifecycle
17 | implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
18 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
19 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
20 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
21 |
22 | // Navigation Jetpack
23 | implementation "androidx.navigation:navigation-fragment-ktx:$navVersion"
24 | implementation "androidx.navigation:navigation-ui-ktx:$navVersion"
25 |
26 | implementation "androidx.constraintlayout:constraintlayout:$constraintLayoutVersion"
27 | }
--------------------------------------------------------------------------------
/commons.gradle:
--------------------------------------------------------------------------------
1 | dependencies {
2 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
3 | implementation "androidx.core:core-ktx:$coreKtxVersion"
4 | implementation "androidx.appcompat:appcompat:$appCompatVersion"
5 | implementation "com.google.android.material:material:$materialVersion"
6 | testImplementation "junit:junit:$junitVersion"
7 | androidTestImplementation "androidx.test.espresso:espresso-core:$espressoCoreVersion"
8 |
9 | androidTestImplementation "androidx.test.ext:junit:$junitExtVersion"
10 | androidTestImplementation "androidx.test:rules:$testXRulesVersion"
11 | androidTestImplementation "android.arch.core:core-testing:$coreTestingVersion"
12 | androidTestImplementation "com.squareup.okhttp3:mockwebserver:$mockWebserverVersion"
13 | androidTestImplementation "org.koin:koin-test:$koinVersion"
14 | androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
15 |
16 | testImplementation "androidx.test.ext:junit:$junitExtVersion"
17 | testImplementation "android.arch.core:core-testing:$coreTestingVersion"
18 | testImplementation "com.squareup.okhttp3:mockwebserver:$mockWebserverVersion"
19 | testImplementation "org.koin:koin-test:$koinVersion"
20 | testImplementation "io.mockk:mockk:$mockVersion"
21 | }
--------------------------------------------------------------------------------
/core/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | id 'kotlin-kapt'
5 | id 'androidx.navigation.safeargs'
6 | }
7 | apply from: "$rootProject.projectDir/commons.gradle"
8 |
9 | android {
10 | compileSdkVersion 30
11 | buildToolsVersion "30.0.2"
12 |
13 | defaultConfig {
14 | minSdkVersion 25
15 | targetSdkVersion 30
16 | versionCode 1
17 | versionName "1.0"
18 |
19 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
20 | consumerProguardFiles "consumer-rules.pro"
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_1_8
31 | targetCompatibility JavaVersion.VERSION_1_8
32 | }
33 | kotlinOptions {
34 | jvmTarget = '1.8'
35 | }
36 |
37 | buildFeatures {
38 | dataBinding true
39 | }
40 |
41 | flavorDimensions "server"
42 |
43 | productFlavors {
44 |
45 | dev {
46 | dimension "server"
47 | buildConfigField "Boolean", "IS_PINNING_ENABLED", 'false'
48 | }
49 |
50 | uat {
51 | dimension "server"
52 | buildConfigField "Boolean", "IS_PINNING_ENABLED", 'false'
53 | }
54 |
55 | pilot {
56 | dimension "server"
57 | buildConfigField "Boolean", "IS_PINNING_ENABLED", 'false'
58 | }
59 |
60 | prod {
61 | dimension "server"
62 | buildConfigField "Boolean", "IS_PINNING_ENABLED", 'false'
63 | }
64 | }
65 |
66 | publishNonDefault true
67 |
68 | configurations {
69 | devDebugImplementation
70 | devReleaseImplementation
71 | uatDebugImplementation
72 | uatReleaseImplementation
73 | pilotDebugImplementation
74 | pilotReleaseImplementation
75 | prodDebugImplementation
76 | prodReleaseImplementation
77 | }
78 |
79 | }
80 |
81 | dependencies {
82 |
83 | api project(':domain')
84 | api project(':data')
85 | implementation project(':thirdpartys:analyticslib')
86 |
87 | // Koin AndroidX Scope features
88 | implementation "org.koin:koin-androidx-scope:$koinVersion"
89 | implementation "org.koin:koin-androidx-viewmodel:$koinVersion"
90 | implementation "org.koin:koin-androidx-fragment:$koinVersion"
91 | implementation "org.koin:koin-androidx-ext:$koinVersion"
92 | implementation "org.koin:koin-androidx-scope:$koinVersion"
93 | implementation "org.koin:koin-androidx-viewmodel:$koinVersion"
94 | implementation "org.koin:koin-core:$koinVersion"
95 |
96 | //Retrofit
97 | implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
98 | implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
99 | implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion"
100 | implementation "com.squareup.okhttp3:logging-interceptor:$okhttpLoggingVersion"
101 | implementation "com.squareup.okio:okio:$okioVersion"
102 | implementation "com.squareup.okhttp3:okhttp-urlconnection:$urlConnectionVersion"
103 |
104 |
105 | //Coroutine
106 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
107 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion"
108 | implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$coroutineAdapterVersion"
109 |
110 | //Calligraphy
111 | implementation "io.github.inflationx:calligraphy3:$calligrapyVersion"
112 | implementation "io.github.inflationx:viewpump:$viewpumpVersion"
113 |
114 | // Timber for Logging
115 | implementation "com.jakewharton.timber:timber:$timberVersion"
116 |
117 | // Lifecycle
118 | implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
119 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
120 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
121 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
122 |
123 |
124 | // Navigation Jetpack
125 | implementation "androidx.navigation:navigation-fragment-ktx:$navVersion"
126 | implementation "androidx.navigation:navigation-ui-ktx:$navVersion"
127 | }
128 | repositories {
129 | maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
130 | mavenCentral()
131 | }
--------------------------------------------------------------------------------
/core/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/core/consumer-rules.pro
--------------------------------------------------------------------------------
/core/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
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/template/core/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.template.core
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.template.common.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/core/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/arc/SingleLiveEvent.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.arc
2 |
3 | import android.util.Log
4 | import androidx.annotation.MainThread
5 | import androidx.lifecycle.LifecycleOwner
6 | import androidx.lifecycle.MutableLiveData
7 | import androidx.lifecycle.Observer
8 | import timber.log.Timber
9 | import java.util.concurrent.atomic.AtomicBoolean
10 |
11 | /****
12 | * A lifecycle-aware observable that sends only new updates after subscription, used for events like
13 | * navigation and SnackBar messages.
14 | * This avoids a common problem with events: on configuration change (like rotation) an update
15 | * can be emitted if the observer is active. This LiveData only calls the observable if there's an
16 | * explicit call to setValue() or call().
17 | * Note that only one observer is going to be notified of changes.
18 | * Author: Lajesh Dineshkumar
19 | * Company:
20 | * Created on: 2020-03-03
21 | * Modified on: 2020-03-03
22 | *****/
23 | open class SingleLiveEvent : MutableLiveData() {
24 |
25 | private val mPending = AtomicBoolean(false)
26 |
27 | @MainThread
28 | override fun observe(owner: LifecycleOwner, observer: Observer) {
29 |
30 | if (hasActiveObservers()) {
31 | Timber.log(Log.ASSERT, "Multiple observers registered but only one will be notified of changes.")
32 | }
33 |
34 | // Observe the internal MutableLiveData
35 | super.observe(
36 | owner,
37 | Observer { t ->
38 | if (mPending.compareAndSet(true, false)) {
39 | observer.onChanged(t)
40 | }
41 | }
42 | )
43 | }
44 |
45 | @MainThread
46 | override fun setValue(t: T?) {
47 | mPending.set(true)
48 | super.setValue(t)
49 | }
50 |
51 | /**
52 | * Used for cases where T is Void, to make calls cleaner.
53 | */
54 | @MainThread
55 | fun call() {
56 | value = null
57 | }
58 |
59 | companion object {
60 |
61 | private val TAG = "SingleLiveEvent"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/common/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.common
2 |
3 | /****
4 | * Keep all the common constants here
5 | * Author: Lajesh Dineshkumar
6 | * Company:
7 | * Created on: 2/2/21
8 | * Modified on: 2/2/21
9 | *****/
10 | object Constants {
11 | const val LANG_CODE_ENGLISH = "en"
12 | const val LANG_CODE_ARABIC = "ar"
13 | const val LANGUAGE_SWITCH = "language_switch"
14 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/common/SharedPrefConstants.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.common
2 |
3 | /****
4 | * Keep all shared preference constants here
5 | * Author: Lajesh Dineshkumar
6 | * Company:
7 | * Created on: 2020-03-02
8 | * Modified on: 2020-03-02
9 | *****/
10 | object SharedPrefConstants {
11 | const val APP_PREFS = "app_preferences"
12 | const val TOKEN = "session_token"
13 | const val PREFERENCE_LANGUAGE = "pref_language"
14 | const val PREF_FCM_TOKEN = "prefFcmToken"
15 | const val SELECTED_LANGUAGE = "key_language"
16 | const val USER_ID: String = "user_session"
17 | const val PREF_USER_HASH = "pref_user_hash"
18 | const val PREF_USER_NAME = "pref_user_name"
19 | const val IS_USER_LOGGED = "IS_USER_LOGGED"
20 | const val KEY_CONTAINER_SESSION = "kcs"
21 | const val KEY_SESSION_START_TIME = "ksst"
22 | const val WALLET_ONBOARDING_COMPLETED = "wallet_onboarding_completed"
23 | const val DID_FAVORITE_OFFER = "did_favorite_offer"
24 | }
25 |
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/config/Configuration.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.config
2 |
3 | import com.template.core.BuildConfig
4 |
5 | /****
6 | * Keep all the common configurations here
7 | * Author: Lajesh Dineshkumar
8 | * Company:
9 | * Created on: 2020-03-29
10 | * Modified on: 2020-03-29
11 | *****/
12 | object Configuration {
13 | // Deployment Types
14 | private const val DEV = "dev"
15 | private const val SIT = "sit"
16 | private const val UAT = "uat"
17 | private const val PROD = "prod"
18 | private const val PILOT = "pilot"
19 |
20 | // Host URLs
21 | private const val DEV_URL = "http://demo4597733.mockable.io/"
22 | private const val SIT_URL = "http://demo4597733.mockable.io/sit/"
23 | private const val UAT_URL = "http://demo4597733.mockable.io/uat/"
24 | private const val PROD_URL = "http://demo4597733.mockable.io/prod/"
25 | private const val PILOT_URL = "http://demo4597733.mockable.io/pilot/"
26 |
27 | const val CONNECT_TIMEOUT: Long = 60
28 | const val READ_TIMEOUT: Long = 60
29 | const val CALL_TIMEOUT: Long = 60
30 |
31 |
32 | val baseURL: String
33 | get() {
34 |
35 | return when (BuildConfig.FLAVOR) {
36 |
37 | DEV -> DEV_URL
38 |
39 | SIT -> SIT_URL
40 |
41 | UAT -> UAT_URL
42 |
43 | PROD -> PROD_URL
44 |
45 | PILOT -> PILOT_URL
46 |
47 | else -> DEV_URL
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/contract/SubscriptionContract.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.contract
2 |
3 | /****
4 | * Subscription contract. Wherever you want to subcribe for the network response
5 | * or navigation event, implement this interface
6 | * Author: Lajesh Dineshkumar
7 | * Company:
8 | * Created on: 2020-03-03
9 | * Modified on: 2020-03-03
10 | *****/
11 | interface SubscriptionContract {
12 | fun subscribeNetworkResponse() {
13 | // Implementation goes in the owner
14 | }
15 | fun subscribeNavigationEvent() {
16 | // Implementation goes in the owner
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/di/Injectable.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.di
2 |
3 | /**
4 | * Makes as activity/fragmenty injectable
5 | * Created by Lajesh Dineshkumar on 10/30/2019.
6 | * Company:
7 | * Email: lajeshds2007@gmail.com
8 | */
9 | interface Injectable
10 |
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/di/module/application/OkhttpModule.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.di.module.application
2 |
3 | import com.template.core.config.Configuration
4 | import com.template.core.interceptors.HeaderInterceptor
5 | import okhttp3.OkHttpClient
6 | import okhttp3.*
7 | import okhttp3.logging.HttpLoggingInterceptor
8 | import org.koin.core.context.loadKoinModules
9 | import org.koin.dsl.module
10 | import java.net.CookieManager
11 | import java.util.concurrent.TimeUnit
12 |
13 | import com.template.core.BuildConfig
14 |
15 | /****
16 | * OKhttp Module
17 | * Author: Lajesh Dineshkumar
18 | * Company:
19 | * Created on: 2/2/21
20 | * Modified on: 2/2/21
21 | *****/
22 | object OkhttpModule {
23 | fun load() {
24 | loadKoinModules(module {
25 | single {
26 | val cookieHandler = CookieManager()
27 | val okHttpBuilder = OkHttpClient()
28 | .newBuilder()
29 | .addInterceptor(get())
30 | .addInterceptor(get())
31 | .cookieJar(JavaNetCookieJar(cookieHandler))
32 | .connectTimeout(Configuration.CONNECT_TIMEOUT, TimeUnit.SECONDS)
33 | .callTimeout(Configuration.CALL_TIMEOUT, TimeUnit.SECONDS)
34 | .readTimeout(Configuration.READ_TIMEOUT, TimeUnit.SECONDS)
35 |
36 | okHttpBuilder.build()
37 | }
38 |
39 | single {
40 | HeaderInterceptor()
41 | }
42 |
43 | single {
44 | val httpLoggingInterceptor = HttpLoggingInterceptor()
45 | if (BuildConfig.DEBUG)
46 | httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
47 | else
48 | httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.NONE
49 |
50 | httpLoggingInterceptor
51 | }
52 | })
53 | }
54 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/di/module/application/RetrofitModule.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.di.module.application
2 |
3 | import com.google.gson.Gson
4 | import com.google.gson.GsonBuilder
5 | import com.template.core.config.Configuration
6 | import org.koin.core.context.loadKoinModules
7 | import org.koin.dsl.module
8 | import retrofit2.Retrofit
9 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
10 | import retrofit2.converter.gson.GsonConverterFactory
11 |
12 | /****
13 | * Retrofit Module
14 | * Author: Lajesh Dineshkumar
15 | * Company:
16 | * Created on: 2/2/21
17 | * Modified on: 2/2/21
18 | *****/
19 | object RetrofitModule {
20 | fun load() {
21 | loadKoinModules(retrofitModules)
22 | }
23 |
24 | private val retrofitModules = module {
25 | single {
26 | Retrofit.Builder()
27 | .client(get())
28 | .addConverterFactory(getGsonConverterFactory())
29 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()).baseUrl(get())
30 | .build() as Retrofit
31 | }
32 |
33 | single {
34 | Configuration.baseURL
35 | }
36 | }
37 |
38 | private fun getGsonConverterFactory(): GsonConverterFactory {
39 | return GsonConverterFactory.create(getGson())
40 | }
41 |
42 | private fun getGson(): Gson {
43 | val gsonBuilder = GsonBuilder()
44 | gsonBuilder.setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
45 | return gsonBuilder.create()
46 | }
47 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/interceptors/HeaderInterceptor.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.interceptors
2 |
3 | import android.os.Handler
4 | import android.os.Looper
5 | import android.util.Base64
6 | import okhttp3.Interceptor
7 | import okhttp3.Request
8 | import okhttp3.Response
9 | import java.io.IOException
10 | import java.net.SocketTimeoutException
11 |
12 | /****
13 | * File Description
14 | * Author: Lajesh Dineshkumar
15 | * Company:
16 | * Created on: 2/2/21
17 | * Modified on: 2/2/21
18 | *****/
19 | class HeaderInterceptor : okhttp3.Interceptor {
20 |
21 | override fun intercept(chain: Interceptor.Chain): Response {
22 | var response: Response?
23 | var requestBuilder: Request.Builder = chain.request().newBuilder()
24 | requestBuilder = addCommonHeaders(requestBuilder)
25 | val request = requestBuilder.build()
26 | try {
27 | response = chain.proceed(request)
28 | response.takeIf { isHtmlResponse(it) }?.apply {
29 | // throw custom exception
30 | }
31 | } catch (e: Exception) {
32 | throw e
33 | }
34 | return response
35 | }
36 |
37 | /**
38 | * Add the common set of headers
39 | */
40 | private fun addCommonHeaders(requestBuilder: Request.Builder): Request.Builder {
41 | // Add all the common headers here
42 |
43 | return requestBuilder
44 | }
45 |
46 | private fun isHtmlResponse(response: Response): Boolean {
47 | return response.headers.names().contains("Content-Type")
48 | && response.headers["Content-Type"]?.contains("text/html") == true
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/listeners/BackButtonHandlerListener.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.listeners
2 |
3 | /**
4 | * Back button handler interface. Add/remove listener functionality
5 | * Created by Lajesh Dineshkumar on 10/31/2019.
6 | * Company:
7 | * Email: lajeshds2007@gmail.com
8 | */
9 | interface BackButtonHandlerListener {
10 | fun addBackpressListener(listner: BackPressListener)
11 | fun removeBackpressListener(listner: BackPressListener)
12 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/listeners/BackPressListner.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.listeners
2 |
3 | /**
4 | * Back press listener for handling back navigation in activity/fragments
5 | * Author: Lajesh Dineshkumar
6 | * Company:
7 | * Created on: 7/7/20
8 | * Modified on: 7/7/20
9 | */
10 | interface BackPressListener {
11 | fun onBackPress(): Boolean
12 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/ui/base/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.ui.base
2 |
3 | import android.os.Bundle
4 | import android.view.WindowManager
5 | import androidx.annotation.LayoutRes
6 | import androidx.appcompat.app.AppCompatActivity
7 | import androidx.databinding.DataBindingUtil
8 | import androidx.databinding.ViewDataBinding
9 | import androidx.lifecycle.ViewModel
10 | import com.template.core.listeners.BackButtonHandlerListener
11 | import com.template.core.listeners.BackPressListener
12 | import com.template.core.utils.LocaleManager
13 | import com.template.core.viewmodel.ToolbarPropertyViewModel
14 | import com.template.core.viewmodel.base.BaseViewModel
15 | import org.koin.androidx.viewmodel.ext.android.viewModel
16 | import java.lang.ref.WeakReference
17 | import java.util.ArrayList
18 | import kotlin.reflect.KClass
19 |
20 | /****
21 | * All the activity should be extended from this parent class.
22 | * All the common functionalities across activities should be kept here
23 | * Author: Lajesh Dineshkumar
24 | * Company:
25 | * Created on: 2020-03-03
26 | * Modified on: 2020-03-03
27 | *****/
28 | abstract class BaseActivity(clazz: KClass) :
29 | AppCompatActivity(), BackButtonHandlerListener {
30 |
31 | val viewModel: V by viewModel(clazz)
32 |
33 | private val toolbarModel: ToolbarPropertyViewModel by viewModel()
34 |
35 | lateinit var dataBinding: D
36 |
37 | @get:LayoutRes
38 | protected abstract val layoutRes: Int
39 |
40 | abstract val bindingVariable: Int
41 |
42 | protected abstract fun getViewModel(): Class
43 |
44 | private val backClickListenersList = ArrayList>()
45 |
46 | override fun onCreate(savedInstanceState: Bundle?) {
47 | super.onCreate(savedInstanceState)
48 |
49 | initializeDataBinding()
50 | (viewModel as? BaseViewModel)?.toolbarPropertyViewModel = toolbarModel
51 |
52 | }
53 |
54 | private fun initializeDataBinding(){
55 | dataBinding = DataBindingUtil.setContentView(this, layoutRes)
56 | dataBinding.lifecycleOwner = this
57 |
58 | (viewModel as? BaseViewModel)?.toolbarPropertyViewModel = toolbarModel
59 |
60 | dataBinding.setVariable(bindingVariable, viewModel)
61 | dataBinding.executePendingBindings()
62 | }
63 |
64 | fun logout(showConfirm: Boolean? = false) {
65 | (viewModel as BaseViewModel).logoutClickEvent.value = showConfirm ?: false
66 | }
67 |
68 | /**
69 | * Showing progress bar over screen
70 | */
71 | fun showLoading(it: Boolean?) {
72 | it?.let { disableTouch ->
73 | (viewModel as BaseViewModel).showLoading(it)
74 | if (disableTouch) {
75 | window.setFlags(
76 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
77 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
78 | )
79 | } else {
80 | window.clearFlags(
81 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
82 | )
83 | }
84 | }
85 | }
86 |
87 | fun setUpLocale(lngCode: String, isSelected: Boolean = false) {
88 | val currentLanguage = LocaleManager.getCurrentLanguage(this)
89 | if (!currentLanguage.equals(lngCode, true)) {
90 | LocaleManager.setNewLocale(this, lngCode)
91 | if (currentLanguage != null || isSelected) {
92 | LocaleManager.notifyLanguageChange(this)
93 | }
94 | }
95 | }
96 |
97 | /**
98 | * Methods which handles the hardware back button / navigation back view
99 | */
100 | override fun onBackPressed() {
101 | if (!fragmentsBackKeyIntercept()) {
102 | super.onBackPressed()
103 | }
104 | }
105 |
106 | /**
107 | * Add the back navigation listener here.
108 | * Call this method from onAttach of your fragment
109 | * @param listner - back navigation listener
110 | */
111 | override fun addBackpressListener(listner: BackPressListener) {
112 | backClickListenersList.add(WeakReference(listner))
113 | }
114 |
115 | /**
116 | * remove the back navigation listener here.
117 | * Call this method from onDetach of your fragment
118 | * @param listner - back navigation listener
119 | */
120 | override fun removeBackpressListener(listner: BackPressListener) {
121 | val iterator = backClickListenersList.iterator()
122 | while (iterator.hasNext()) {
123 | val weakRef = iterator.next()
124 | if (weakRef.get() === listner) {
125 | iterator.remove()
126 | }
127 | }
128 | }
129 |
130 | /**
131 | * This method checks if any frgament is overriding the back button behavior or not
132 | * @return true/false
133 | */
134 | private fun fragmentsBackKeyIntercept(): Boolean {
135 | var isIntercept = false
136 | for (weakRef in backClickListenersList) {
137 | val backpressListner = weakRef.get()
138 | if (backpressListner != null) {
139 | val isFragmIntercept: Boolean = backpressListner.onBackPress()
140 | if (!isIntercept)
141 | isIntercept = isFragmIntercept
142 | }
143 | }
144 | return isIntercept
145 | }
146 |
147 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/utils/NavigationCommand.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.utils
2 |
3 | import android.net.Uri
4 | import androidx.navigation.NavDirections
5 |
6 | /****
7 | * The app will be using command pattern to handle navigation within the app
8 | * Author: Lajesh Dineshkumar
9 | * Company:
10 | * Created on: 2/3/21
11 | * Modified on: 2/3/21
12 | *****/
13 | sealed class NavigationCommand {
14 | data class To(val directions: NavDirections): NavigationCommand()
15 | object Back: NavigationCommand()
16 | data class BackTo(val destinationId: Int): NavigationCommand()
17 | data class ToByDeepLink(val deepLink: Uri): NavigationCommand()
18 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/utils/PreferenceUtil.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.utils
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 |
6 | /****
7 | * Shared Preference util class
8 | * ----------------------------
9 | * How to use ?
10 | * val prefs = PreferenceUtil.defaultPrefs(this)
11 | * set any type of value in prefs
12 | * prefs[PREFUSERID] = "name"
13 | * ----------------------------
14 | * Author: Lajesh Dineshkumar
15 | * Company:
16 | * Created on: 2020-03-02
17 | * Modified on: 2020-03-02
18 | *****/
19 | object PreferenceUtil {
20 |
21 | fun customPrefs(context: Context, name: String): SharedPreferences =
22 | context.getSharedPreferences(name, Context.MODE_PRIVATE)
23 |
24 | private inline fun SharedPreferences.edit(operation: (SharedPreferences.Editor) -> Unit) {
25 | val editor = this.edit()
26 | operation(editor)
27 | editor.apply()
28 | }
29 |
30 | /**
31 | * puts a key value pair in shared prefs if doesn't exists, otherwise updates value on given [key]
32 | */
33 | operator fun SharedPreferences.set(key: String, value: Any?) {
34 | when (value) {
35 | is String? -> edit { it.putString(key, value) }
36 | is Int -> edit { it.putInt(key, value) }
37 | is Boolean -> edit { it.putBoolean(key, value) }
38 | is Float -> edit { it.putFloat(key, value) }
39 | is Long -> edit { it.putLong(key, value) }
40 | else -> throw UnsupportedOperationException("Not yet implemented")
41 | }
42 | }
43 |
44 | /**
45 | * finds value on given key.
46 | * [T] is the type of value
47 | * @param defaultValue optional default value - will take null for strings, false for bool and -1 for numeric values if [defaultValue] is not specified
48 | */
49 | inline operator fun SharedPreferences.get(key: String, defaultValue: T? = null): T? {
50 | return when (T::class) {
51 | String::class -> getString(key, defaultValue as? String) as T?
52 | Int::class -> getInt(key, defaultValue as? Int ?: -1) as T?
53 | Boolean::class -> getBoolean(key, defaultValue as? Boolean ?: false) as T?
54 | Float::class -> getFloat(key, defaultValue as? Float ?: -1f) as T?
55 | Long::class -> getLong(key, defaultValue as? Long ?: -1) as T?
56 | else -> throw UnsupportedOperationException("Not yet implemented")
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/utils/SharedPrefHelper.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.utils
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import com.template.core.utils.PreferenceUtil.get
6 | import com.template.core.utils.PreferenceUtil.set
7 | import com.template.core.common.SharedPrefConstants
8 | import com.template.core.common.SharedPrefConstants.DID_FAVORITE_OFFER
9 | import com.template.core.common.SharedPrefConstants.WALLET_ONBOARDING_COMPLETED
10 |
11 | /****
12 | * Keep all shared preference related methods here
13 | * Author: Lajesh Dineshkumar
14 | * Company:
15 | * Created on: 2020-03-02
16 | * Modified on: 2020-03-02
17 | *****/
18 | class SharedPrefHelper constructor(context: Context) {
19 |
20 | private var sharedPreferences: SharedPreferences =
21 | PreferenceUtil.customPrefs(context, SharedPrefConstants.APP_PREFS)
22 |
23 | fun getToken(): String? {
24 | return sharedPreferences[SharedPrefConstants.TOKEN]
25 | }
26 |
27 | fun setToken(token: String?) {
28 | sharedPreferences[SharedPrefConstants.TOKEN] = token
29 | }
30 |
31 | fun saveFcmToken(token: String?) {
32 | sharedPreferences[SharedPrefConstants.PREF_FCM_TOKEN] = token
33 | }
34 |
35 | fun getFCMToken(): String? {
36 | return sharedPreferences[SharedPrefConstants.PREF_FCM_TOKEN]
37 | }
38 |
39 | fun isUserLogged(): Boolean {
40 | return getUserID() != 0
41 | }
42 |
43 | fun setWalletOnBoardingCompleted(completed: Boolean) {
44 | sharedPreferences[WALLET_ONBOARDING_COMPLETED] = completed
45 | }
46 |
47 | fun doFavoriteOffer() {
48 | sharedPreferences[DID_FAVORITE_OFFER] = true
49 | }
50 |
51 | fun didFavoriteOffer(): Boolean = sharedPreferences[DID_FAVORITE_OFFER] ?: false
52 |
53 | fun isWalletOnBoardingCompleted(): Boolean {
54 | return sharedPreferences[WALLET_ONBOARDING_COMPLETED] ?: false
55 | }
56 |
57 | private fun getUserID(): Int {
58 | return (sharedPreferences[SharedPrefConstants.USER_ID, "0"] ?: "0").toInt()
59 | }
60 |
61 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/viewmodel/SharedViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.viewmodel
2 |
3 | import androidx.lifecycle.ViewModel
4 |
5 | /****
6 | * Shared view model for data sharing between fragments
7 | * Author: Lajesh Dineshkumar
8 | * Company:
9 | * Created on: 2/2/21
10 | * Modified on: 2/2/21
11 | *****/
12 | open class SharedViewModel : ViewModel() {
13 |
14 |
15 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/viewmodel/ToolbarPropertyViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.viewmodel
2 |
3 | import androidx.lifecycle.MutableLiveData
4 | import androidx.lifecycle.ViewModel
5 | import com.template.core.arc.SingleLiveEvent
6 |
7 | /****
8 | * Tool bar Property ViewModel
9 | * Author: Lajesh Dineshkumar
10 | * Company:
11 | * Created on: 5/17/20
12 | * Modified on: 5/17/20
13 | *****/
14 | class ToolbarPropertyViewModel : ViewModel() {
15 | var showBack = MutableLiveData(true)
16 | var showClose = MutableLiveData(false)
17 | var toolbarTitle = MutableLiveData()
18 | val closeButtonAction: SingleLiveEvent = SingleLiveEvent()
19 | val backButtonAction: SingleLiveEvent = SingleLiveEvent()
20 | }
21 |
--------------------------------------------------------------------------------
/core/src/main/java/com/template/core/viewmodel/base/BaseViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.template.core.viewmodel.base
2 |
3 | import androidx.databinding.Observable
4 | import androidx.databinding.PropertyChangeRegistry
5 | import androidx.lifecycle.MutableLiveData
6 | import androidx.lifecycle.ViewModel
7 | import androidx.navigation.NavDirections
8 | import com.template.core.arc.SingleLiveEvent
9 | import com.template.core.utils.NavigationCommand
10 | import com.template.core.viewmodel.SharedViewModel
11 | import com.template.core.viewmodel.ToolbarPropertyViewModel
12 | import com.template.domain.entity.response.common.ErrorEntity
13 |
14 | /****
15 | * Base view model. All the common implementation of viewmodel goes here
16 | * Author: Lajesh Dineshkumar
17 | * Company:
18 | * Created on: 2/2/21
19 | * Modified on: 2/2/21
20 | *****/
21 | abstract class BaseViewModel : ViewModel(), Observable {
22 |
23 | /**
24 | * Live data to handle loading
25 | */
26 | val loadingEvent = SingleLiveEvent()
27 |
28 | /**
29 | * Live data to handle error
30 | */
31 | val errorEvent = SingleLiveEvent()
32 |
33 | /**
34 | * Set the Logout Click to true for showing confirmation before logout
35 | */
36 | var logoutClickEvent = SingleLiveEvent()
37 |
38 | val navigationCommands = SingleLiveEvent()
39 |
40 | val hideKeyboard = MutableLiveData()
41 |
42 |
43 | lateinit var sharedViewModel: SharedViewModel
44 |
45 |
46 | private val callbacks = PropertyChangeRegistry()
47 |
48 |
49 | var toolbarPropertyViewModel: ToolbarPropertyViewModel = ToolbarPropertyViewModel()
50 |
51 |
52 | val isSharedViewModelInitialized: Boolean
53 | get() = this::sharedViewModel.isInitialized
54 |
55 |
56 | override fun addOnPropertyChangedCallback(
57 | callback: Observable.OnPropertyChangedCallback
58 | ) {
59 | callbacks.add(callback)
60 | }
61 |
62 | override fun removeOnPropertyChangedCallback(
63 | callback: Observable.OnPropertyChangedCallback
64 | ) {
65 | callbacks.remove(callback)
66 | }
67 |
68 | /**
69 | * Notifies observers that all properties of this instance have changed.
70 | */
71 | internal fun notifyChange() {
72 | callbacks.notifyCallbacks(this, 0, null)
73 | }
74 |
75 | /**
76 | * Notifies observers that a specific property has changed. The getter for the
77 | * property that changes should be marked with the @Bindable annotation to
78 | * generate a field in the BR class to be used as the fieldId parameter.
79 | *
80 | * @param fieldId The generated BR id for the Bindable field.
81 | */
82 | internal fun notifyPropertyChanged(fieldId: Int) {
83 | callbacks.notifyCallbacks(this, fieldId, null)
84 | }
85 |
86 | fun showLoading(show: Boolean) {
87 | loadingEvent.value = show
88 | }
89 |
90 | fun postLoading(show: Boolean) {
91 | loadingEvent.postValue(show)
92 | }
93 |
94 | fun getLoading(): SingleLiveEvent {
95 | return loadingEvent
96 | }
97 |
98 | /**
99 | * Method call to handle error
100 | */
101 | fun setError(error: ErrorEntity.Error?) {
102 | errorEvent.value = error
103 | }
104 |
105 | fun navigate(directions: NavDirections) {
106 | navigationCommands.postValue(NavigationCommand.To(directions))
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/anim_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/anim_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/anim_frag_fade_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/anim_frag_fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/anim_pop_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/anim_pop_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/anim_slide_in_up.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/anim_slide_out_up.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/pop_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/pop_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/right_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/right_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/slide_down.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/slide_down_fade_out_anim.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
14 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/slide_down_half.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/slide_from_top_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/slide_in_from_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
20 |
21 |
25 |
26 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/slide_in_from_rigth.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
19 |
23 |
24 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/slide_left_fade_out_anim.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
14 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/slide_out_to_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
20 |
21 |
25 |
26 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/slide_out_to_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
20 |
21 |
25 |
26 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/slide_up.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/core/src/main/res/values/do_not_translate.xml:
--------------------------------------------------------------------------------
1 |
2 | CleanArchApp
3 | English
4 | عربى
5 |
--------------------------------------------------------------------------------
/core/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Error
3 | OK
4 |
5 | Login
6 | Profile
7 | Splash
8 | Edit Profile
9 |
--------------------------------------------------------------------------------
/core/src/test/java/com/template/core/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.template.core
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:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | id 'kotlin-kapt'
5 | }
6 |
7 | apply from: "$rootProject.projectDir/commons.gradle"
8 |
9 | android {
10 | compileSdkVersion rootProject.compileSdkVersion
11 | buildToolsVersion rootProject.buildToolsVersion
12 |
13 | defaultConfig {
14 | minSdkVersion rootProject.minSdkVersion
15 | targetSdkVersion rootProject.targetSdkVersion
16 | versionCode 1
17 | versionName "1.0"
18 |
19 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
20 | consumerProguardFiles "consumer-rules.pro"
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_1_8
31 | targetCompatibility JavaVersion.VERSION_1_8
32 | }
33 | kotlinOptions {
34 | jvmTarget = '1.8'
35 | }
36 | }
37 |
38 | dependencies {
39 |
40 | implementation project(':domain')
41 |
42 | //Coroutine
43 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
44 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion"
45 |
46 | /* Android Architecture Component - Room Persistance Lib */
47 | implementation "androidx.room:room-runtime:$roomVersion"
48 | kapt "androidx.room:room-compiler:$roomVersion"
49 | implementation "androidx.room:room-common:$roomVersion"
50 | implementation "androidx.room:room-ktx:$roomVersion"
51 |
52 | //Retrofit
53 | implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
54 | implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
55 |
56 | implementation "org.koin:koin-core:$koinVersion"
57 | implementation "org.koin:koin-android:$koinVersion"
58 |
59 | // Timber for Logging
60 | implementation "com.jakewharton.timber:timber:$timberVersion"
61 | }
--------------------------------------------------------------------------------
/data/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/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/template/data/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.template.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.template.data.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/data/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/data/src/main/java/com/template/data/constants/NetworkConstants.kt:
--------------------------------------------------------------------------------
1 | package com.template.data.constants
2 |
3 | import androidx.annotation.IntDef
4 | import androidx.annotation.StringDef
5 |
6 | /**
7 | * Keep all the network related constants here
8 | * Created by Lajesh Dineshkumar on 2020-03-09.
9 | * Company:
10 | * Email: lajeshds2007@gmail.com
11 | */
12 | object NetworkConstants {
13 | @IntDef(
14 | NETWORK_ERROR_CODES.SERVICE_UNAVAILABLE, NETWORK_ERROR_CODES.MALFORMED_JSON,
15 | NETWORK_ERROR_CODES.NO_INTERNET, NETWORK_ERROR_CODES.UNEXPECTED_ERROR,
16 | NETWORK_ERROR_CODES.HTML_RESPONSE_ERROR
17 | )
18 | @Retention(AnnotationRetention.SOURCE)
19 | annotation class NETWORK_ERROR_CODES {
20 | companion object {
21 | const val SERVICE_UNAVAILABLE = 500
22 | const val MALFORMED_JSON = 501
23 | const val NO_INTERNET = 502
24 | const val UNEXPECTED_ERROR = 503
25 | const val HTML_RESPONSE_ERROR = 504
26 | }
27 | }
28 |
29 | @StringDef(
30 | NETWORK_ERROR_MESSAGES.SERVICE_UNAVAILABLE, NETWORK_ERROR_MESSAGES.MALFORMED_JSON,
31 | NETWORK_ERROR_MESSAGES.NO_INTERNET, NETWORK_ERROR_MESSAGES.UNEXPECTED_ERROR
32 | )
33 | @Retention(AnnotationRetention.SOURCE)
34 | annotation class NETWORK_ERROR_MESSAGES {
35 | companion object {
36 | const val SERVICE_UNAVAILABLE = "System temporarily unavailable, please try again later"
37 | const val MALFORMED_JSON = "Oops! We hit an error. Try again later."
38 | const val NO_INTERNET = "Oh! You are not connected to a wifi or cellular data network. Please connect and try again"
39 | const val UNEXPECTED_ERROR = "Something went wrong"
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/data/src/main/java/com/template/data/datasource/local/dao/BaseDao.kt:
--------------------------------------------------------------------------------
1 | package com.template.data.datasource.local.dao
2 |
3 | import androidx.room.Insert
4 | import androidx.room.Query
5 | import androidx.room.Update
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | /****
9 | * All the DAO should be extended from this base class.
10 | * Author: Lajesh Dineshkumar
11 | * Company:
12 | * Created on: 3/14/21
13 | * Modified on: 3/14/21
14 | *****/
15 | interface BaseDao {
16 |
17 | /**
18 | * Insert an object in the database.
19 | *
20 | * @param obj the object to be inserted.
21 | */
22 | @Insert
23 | suspend fun insert(obj: T): Long
24 |
25 | /**
26 | * Update an object from the database.
27 | *
28 | * @param obj the object to be updated
29 | */
30 | @Update
31 | suspend fun update(obj: T): Void
32 |
33 | /**
34 | * Delete an object from the database
35 | *
36 | * @param obj the object to be deleted
37 | */
38 | suspend fun delete(): Int
39 |
40 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/template/data/datasource/local/dao/BranchDao.kt:
--------------------------------------------------------------------------------
1 | package com.template.data.datasource.local.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import androidx.room.Query
7 | import com.template.data.datasource.local.entity.Branch
8 |
9 | /****
10 | * DAO for bank branches
11 | * Author: Lajesh Dineshkumar
12 | * Company:
13 | * Created on: 3/14/21
14 | * Modified on: 3/14/21
15 | *****/
16 | @Dao
17 | interface BranchDao : BaseDao {
18 |
19 | @Query("SELECT * FROM branches")
20 | suspend fun getBranches(): List
21 |
22 | @Insert(onConflict = OnConflictStrategy.REPLACE)
23 | override suspend fun insert(obj: Branch): Long
24 |
25 | @Query("DELETE FROM branches")
26 | override suspend fun delete(): Int
27 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/template/data/datasource/local/database/AppDb.kt:
--------------------------------------------------------------------------------
1 | package com.template.data.datasource.local.database
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import com.template.data.datasource.local.dao.BranchDao
6 | import com.template.data.datasource.local.entity.Branch
7 |
8 | /****
9 | * Application Database
10 | * Author: Lajesh Dineshkumar
11 | * Company:
12 | * Created on: 3/14/21
13 | * Modified on: 3/14/21
14 | *****/
15 | @Database(
16 | entities = [Branch::class],
17 | version = 1,
18 | exportSchema = false
19 | )
20 | abstract class AppDb : RoomDatabase() {
21 | abstract fun branchDao() : BranchDao
22 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/template/data/datasource/local/entity/Branch.kt:
--------------------------------------------------------------------------------
1 | package com.template.data.datasource.local.entity
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 |
6 | /****
7 | * Branch model
8 | * Author: Lajesh Dineshkumar
9 | * Company:
10 | * Created on: 3/14/21
11 | * Modified on: 3/14/21
12 | *****/
13 | @Entity(tableName = "branches")
14 | data class Branch(
15 | @PrimaryKey(autoGenerate = true)
16 | val id: Int = 0,
17 |
18 | val branchName: String? = "",
19 |
20 | val branchAddress: String? = ""
21 | )
22 |
--------------------------------------------------------------------------------
/data/src/main/java/com/template/data/datasource/remote/api/IAuthApi.kt:
--------------------------------------------------------------------------------
1 | package com.template.data.datasource.remote.api
2 |
3 | import com.template.data.datasource.remote.dto.AuthDto
4 | import com.template.data.datasource.remote.dto.CommonDto
5 | import com.template.domain.entity.request.AuthRequest
6 | import kotlinx.coroutines.flow.Flow
7 | import retrofit2.Response
8 | import retrofit2.http.Body
9 | import retrofit2.http.POST
10 |
11 | /****
12 | * API endpoint of Authentication related service calls
13 | * Author: Lajesh Dineshkumar
14 | * Company:
15 | * Created on: 2/1/21
16 | * Modified on: 2/1/21
17 | *****/
18 | interface IAuthApi {
19 |
20 | @POST("login")
21 | suspend fun signIn(@Body signinRequest: AuthRequest.SigninRequest) : AuthDto.LoginResponse
22 |
23 | @POST("signup")
24 | suspend fun signUp(@Body signupRequest: AuthRequest.SignupRequest) : CommonDto.CommonResponse
25 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/template/data/datasource/remote/dto/AuthDto.kt:
--------------------------------------------------------------------------------
1 | package com.template.data.datasource.remote.dto
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | /****
6 | * Keep all the AUTH related DTOs here
7 | * Author: Lajesh Dineshkumar
8 | * Company: Farabi Technologies
9 | * Created on: 4/25/21
10 | * Modified on: 4/25/21
11 | *****/
12 | sealed class AuthDto {
13 |
14 | data class LoginResponse(
15 | @SerializedName("userDetails") val userDetails: UserDetails? = null
16 | ) : AuthDto()
17 |
18 | data class UserDetails(
19 | @SerializedName("firstName") val firstName: String? = "",
20 | @SerializedName("lastName") val lastName: String? = "",
21 | @SerializedName("phoneNumber") val phoneNumber: String?= "",
22 | @SerializedName("countryCode") val countryCode: String? = ""
23 | ) : AuthDto()
24 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/template/data/datasource/remote/dto/CommonDto.kt:
--------------------------------------------------------------------------------
1 | package com.template.data.datasource.remote.dto
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | /****
6 | * File Description
7 | * Author: Lajesh Dineshkumar
8 | * Company:
9 | * Created on: 2/1/21
10 | * Modified on: 2/1/21
11 | *****/
12 | sealed class CommonDto {
13 |
14 | data class CommonResponse(val response: Any?, val data: T?) : CommonDto()
15 |
16 | data class Error(
17 | @SerializedName("code")val errorCode: String,
18 | @SerializedName("message")val errorMessage: String
19 | ) : CommonDto()
20 |
21 | data class ServerDate(
22 | @SerializedName("serverDateTime")val dateTime: String? = ""
23 | ) : CommonDto()
24 |
25 | data class Location(
26 | @SerializedName("latitude") val latitude: Double,
27 | @SerializedName("longitude") val longitude: Double
28 | )
29 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/template/data/datasource/remote/dto/ErrorDto.kt:
--------------------------------------------------------------------------------
1 | package com.template.data.datasource.remote.dto
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | /**
6 | * ERROR DTO Class
7 | * Created by Lajesh Dineshkumar on 2020-03-09.
8 | * Company:
9 | * Email: lajeshds2007@gmail.com
10 | */
11 | sealed class ErrorDto {
12 |
13 | data class ErrorResponse(@SerializedName("error") val error: Error? = null)
14 |
15 | data class Error(
16 | @SerializedName("type") val type: String? = "",
17 | @SerializedName("code") val code: Any?,
18 | @SerializedName("error_user_msg") var errorUserMsg: String? = "",
19 | @SerializedName("field_errors") val fieldErrors: List? = emptyList()
20 | )
21 |
22 | data class FieldErrors(
23 | @SerializedName("code") val code: String? = "",
24 | @SerializedName("message") val message: String? = ""
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/data/src/main/java/com/template/data/di/ApiModule.kt:
--------------------------------------------------------------------------------
1 | package com.template.data.di
2 |
3 | import com.template.data.datasource.remote.api.IAuthApi
4 | import org.koin.core.context.loadKoinModules
5 | import org.koin.dsl.module
6 | import retrofit2.Retrofit
7 |
8 | /****
9 | * API Module ehich provides the instance of API Endpoints
10 | * Author: Lajesh Dineshkumar
11 | * Company:
12 | * Created on: 2/2/21
13 | * Modified on: 2/2/21
14 | *****/
15 | object ApiModule {
16 | fun load(){
17 | loadKoinModules(apiModules)
18 | }
19 |
20 | val apiModules = module {
21 | single {
22 | get().create(IAuthApi::class.java)
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/template/data/di/RepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package com.template.data.di
2 |
3 | import com.template.data.repository.AuthRepositoryImpl
4 | import com.template.domain.repository.IAuthRepository
5 | import org.koin.core.context.loadKoinModules
6 | import org.koin.dsl.module
7 |
8 | /****
9 | * DI module which provides the factory repository instances
10 | * Author: Lajesh Dineshkumar
11 | * Company:
12 | * Created on: 2/1/21
13 | * Modified on: 2/1/21
14 | *****/
15 | object RepositoryModule {
16 | fun load() {
17 | loadKoinModules(repositoryModules)
18 | }
19 |
20 | val repositoryModules = module {
21 | factory{ AuthRepositoryImpl(authApi = get(), branchDao = get()) }
22 | }
23 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/template/data/mapper/dtotoentity/AuthDtoToEntity.kt:
--------------------------------------------------------------------------------
1 | package com.template.data.mapper.dtotoentity
2 |
3 | import com.template.data.datasource.remote.dto.AuthDto
4 | import com.template.domain.entity.response.auth.AuthEntity
5 |
6 | /****
7 | * File Description
8 | * Author: Lajesh Dineshkumar
9 | * Company: Farabi Technologies
10 | * Created on: 4/25/21
11 | * Modified on: 4/25/21
12 | *****/
13 | fun AuthDto.UserDetails.map() = AuthEntity.UserDetails(
14 | firstName = firstName,
15 | lastName = lastName
16 | )
17 |
18 | fun AuthDto.LoginResponse.map() = AuthEntity.LoginResponse(
19 | userDetails = userDetails?.map()
20 | )
--------------------------------------------------------------------------------
/data/src/main/java/com/template/data/mapper/dtotoentity/CommonDtoToEntity.kt:
--------------------------------------------------------------------------------
1 | package com.template.data.mapper.dtotoentity
2 |
3 | import com.template.data.datasource.remote.dto.AuthDto
4 | import com.template.data.datasource.remote.dto.CommonDto
5 | import com.template.domain.entity.common.CommonEntity
6 | import com.template.domain.entity.response.auth.AuthEntity
7 |
8 | /**
9 | * Keep all the DTO to Entity Mapping here
10 | * Created by Lajesh Dineshkumar on 2020-03-09.
11 | * Company:
12 | * Email: lajeshds2007@gmail.com
13 | */
14 |
15 | fun CommonDto.CommonResponse.map() = CommonEntity.CommonResponse(
16 | response = response,
17 | data = data
18 | )
19 |
20 | fun CommonDto.ServerDate.map() = CommonEntity.ServerDate(
21 | dateTime = dateTime
22 | )
23 |
24 | fun CommonDto.Location.map() = CommonEntity.Location(latitude, longitude)
25 |
--------------------------------------------------------------------------------
/data/src/main/java/com/template/data/repository/AuthRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.template.data.repository
2 |
3 | import com.template.data.datasource.local.dao.BranchDao
4 | import com.template.data.datasource.remote.api.IAuthApi
5 | import com.template.data.mapper.dtotoentity.map
6 | import com.template.domain.common.ResultState
7 | import com.template.domain.entity.common.CommonEntity
8 | import com.template.domain.entity.request.AuthRequest
9 | import com.template.domain.entity.response.auth.AuthEntity
10 | import com.template.domain.repository.IAuthRepository
11 | import kotlinx.coroutines.Dispatchers
12 | import kotlinx.coroutines.flow.Flow
13 | import kotlinx.coroutines.flow.flow
14 | import kotlinx.coroutines.flow.flowOn
15 |
16 | /****
17 | * AuthRepository Implementation
18 | * Author: Lajesh Dineshkumar
19 | * Company:
20 | * Created on: 2/1/21
21 | * Modified on: 2/1/21
22 | *****/
23 | class AuthRepositoryImpl(private val authApi: IAuthApi, private val branchDao: BranchDao) : BaseRepositoryImpl(), IAuthRepository {
24 | override fun signIn(
25 | signinRequest: AuthRequest.SigninRequest
26 | ): Flow> = flow {
27 | emit(apiCall { authApi.signIn(signinRequest).map() })
28 | }.flowOn(Dispatchers.IO)
29 |
30 | override fun signUp(
31 | signupRequest: AuthRequest.SignupRequest
32 | ): Flow>> = flow {
33 | emit(apiCall { authApi.signUp(signupRequest).map()})
34 | }.flowOn(Dispatchers.IO)
35 | }
--------------------------------------------------------------------------------
/data/src/test/java/com/template/data/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.template.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 | }
--------------------------------------------------------------------------------
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java-library'
3 | id 'kotlin'
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_1_8
8 | targetCompatibility = JavaVersion.VERSION_1_8
9 | }
10 |
11 | dependencies {
12 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
13 |
14 | //Coroutine
15 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
16 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion"
17 |
18 | // Koin
19 | implementation "org.koin:koin-core:$koinVersion"
20 | implementation "org.koin:koin-android:$koinVersion"
21 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/template/domain/common/ResultState.kt:
--------------------------------------------------------------------------------
1 | package com.template.domain.common
2 |
3 | import com.template.domain.entity.response.common.ErrorEntity
4 |
5 | /**
6 | * A wrapper for database and network states.
7 | * Created by Lajesh Dineshkumar on 2020-03-09.
8 | * Company:
9 | * Email: lajeshds2007@gmail.com
10 | */
11 | sealed class ResultState {
12 |
13 | /**
14 | * A state that shows the [data] is the last known update.
15 | */
16 | data class Success(val data: T) : ResultState()
17 |
18 | /**
19 | * A state to show an error
20 | */
21 | data class Error(val error: ErrorEntity.Error?) : ResultState()
22 | }
23 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/template/domain/di/UseCaseModule.kt:
--------------------------------------------------------------------------------
1 | package com.template.domain.di
2 |
3 | import com.template.domain.usecases.auth.AuthUseCaseImpl
4 | import com.template.domain.usecases.auth.IAuthUseCase
5 | import org.koin.core.context.loadKoinModules
6 | import org.koin.dsl.module
7 |
8 | /****
9 | * Module which provides the factory instance of Usecase
10 | * Author: Lajesh Dineshkumar
11 | * Company:
12 | * Created on: 2/1/21
13 | * Modified on: 2/1/21
14 | *****/
15 | object UseCaseModule {
16 | fun load(){
17 | loadKoinModules(authUSeCaseModules)
18 | }
19 |
20 | val authUSeCaseModules = module {
21 | factory { AuthUseCaseImpl(authRepository = get()) }
22 | }
23 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/template/domain/entity/request/AuthRequest.kt:
--------------------------------------------------------------------------------
1 | package com.template.domain.entity.request
2 |
3 | /****
4 | * File Description
5 | * Author: Lajesh Dineshkumar
6 | * Company:
7 | * Created on: 2/1/21
8 | * Modified on: 2/1/21
9 | *****/
10 | sealed class AuthRequest {
11 |
12 | abstract val userId: String
13 | abstract val password: String
14 |
15 | data class SignupRequest(
16 | override val userId: String,
17 | override val password: String,
18 | val tnc: Boolean
19 | ) : AuthRequest()
20 |
21 | data class SigninRequest(
22 | override val userId: String,
23 | override val password: String,
24 | val loginType: Int,
25 | val enableRememberMe: Boolean,
26 | val encryptionToken: String? = "",
27 | val notificationToken: String? = ""
28 | ) : AuthRequest()
29 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/template/domain/entity/response/auth/AuthEntity.kt:
--------------------------------------------------------------------------------
1 | package com.template.domain.entity.response.auth
2 |
3 | /****
4 | * Keep all the authentication use case related entities over here.
5 | * Entity model contains only the fields needed for UI
6 | * Author: Lajesh Dineshkumar
7 | * Company: Farabi Technologies
8 | * Created on: 4/25/21
9 | * Modified on: 4/25/21
10 | *****/
11 | sealed class AuthEntity {
12 |
13 | data class LoginResponse(
14 | val userDetails: UserDetails? = null
15 | ) : AuthEntity()
16 |
17 | data class UserDetails(
18 | val firstName: String? = "",
19 | val lastName: String? = ""
20 | ) : AuthEntity()
21 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/template/domain/entity/response/common/CommonEntity.kt:
--------------------------------------------------------------------------------
1 | package com.template.domain.entity.common
2 |
3 | /****
4 | * Keep all the common entity class here
5 | * Author: Lajesh Dineshkumar
6 | * Company:
7 | * Created on: 1/31/21
8 | * Modified on: 1/31/21
9 | *****/
10 | sealed class CommonEntity {
11 |
12 | data class CommonResponse(
13 | val response: Any?,
14 | val data: T?
15 | ) : CommonEntity()
16 |
17 | data class ServerDate(
18 | val dateTime: String? = ""
19 | ) : CommonEntity()
20 |
21 | data class Location(
22 | val latitude: Double,
23 | val longitude: Double
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/template/domain/entity/response/common/ErrorEntity.kt:
--------------------------------------------------------------------------------
1 | package com.template.domain.entity.response.common
2 |
3 | /****
4 | * Keep all the error related model class here
5 | * Author: Lajesh Dineshkumar
6 | * Company:
7 | * Created on: 1/31/21
8 | * Modified on: 1/31/21
9 | *****/
10 | sealed class ErrorEntity {
11 | data class Error(val errorCode: Any?, val errorMessage: String? = "") : ErrorEntity()
12 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/template/domain/repository/IAuthRepository.kt:
--------------------------------------------------------------------------------
1 | package com.template.domain.repository
2 |
3 | import com.template.domain.common.ResultState
4 | import com.template.domain.entity.common.CommonEntity
5 | import com.template.domain.entity.request.AuthRequest
6 | import com.template.domain.entity.response.auth.AuthEntity
7 | import kotlinx.coroutines.flow.Flow
8 |
9 | /****
10 | * The abstraction of Auth repositories goes here
11 | * Author: Lajesh Dineshkumar
12 | * Company:
13 | * Created on: 2/1/21
14 | * Modified on: 2/1/21
15 | *****/
16 | interface IAuthRepository {
17 |
18 | fun signIn(signinRequest: AuthRequest.SigninRequest) : Flow>
19 |
20 | fun signUp(signupRequest: AuthRequest.SignupRequest) : Flow>>
21 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/template/domain/usecases/auth/AuthUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package com.template.domain.usecases.auth
2 |
3 | import com.template.domain.common.ResultState
4 | import com.template.domain.entity.common.CommonEntity
5 | import com.template.domain.entity.request.AuthRequest
6 | import com.template.domain.entity.response.auth.AuthEntity
7 | import com.template.domain.repository.IAuthRepository
8 | import kotlinx.coroutines.flow.Flow
9 |
10 | /****
11 | * Implementation of auth usecase goes here
12 | * Author: Lajesh Dineshkumar
13 | * Company:
14 | * Created on: 1/31/21
15 | * Modified on: 1/31/21
16 | *****/
17 | class AuthUseCaseImpl(private val authRepository: IAuthRepository) : IAuthUseCase {
18 | override fun signIn(
19 | signinRequest: AuthRequest.SigninRequest
20 | ): Flow> = authRepository.signIn(
21 | signinRequest
22 | )
23 |
24 | override fun signUp(
25 | signupRequest: AuthRequest.SignupRequest
26 | ): Flow>> = authRepository.signUp(
27 | signupRequest
28 | )
29 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/template/domain/usecases/auth/IAuthUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.template.domain.usecases.auth
2 |
3 | import com.template.domain.common.ResultState
4 | import com.template.domain.entity.common.CommonEntity
5 | import com.template.domain.entity.request.AuthRequest
6 | import com.template.domain.entity.response.auth.AuthEntity
7 | import com.template.domain.usecases.base.BaseUseCase
8 | import kotlinx.coroutines.flow.Flow
9 |
10 | /****
11 | * Keep all the authentication related business use cases here
12 | * Author: Lajesh Dineshkumar
13 | * Company:
14 | * Created on: 1/31/21
15 | * Modified on: 1/31/21
16 | *****/
17 | interface IAuthUseCase : BaseUseCase {
18 |
19 | fun signIn(signinRequest: AuthRequest.SigninRequest) : Flow>
20 |
21 | fun signUp(signupRequest: AuthRequest.SignupRequest) : Flow>>
22 |
23 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/template/domain/usecases/base/BaseUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.template.domain.usecases.base
2 |
3 | /****
4 | * All the use cases should be extended from this interface
5 | * Author: Lajesh Dineshkumar
6 | * Company:
7 | * Created on: 1/31/21
8 | * Modified on: 1/31/21
9 | *****/
10 | interface BaseUseCase {
11 | }
--------------------------------------------------------------------------------
/features/onboarding/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/features/onboarding/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | id 'kotlin-kapt'
5 | id 'androidx.navigation.safeargs.kotlin'
6 | }
7 | apply from: "$rootProject.projectDir/commons.gradle"
8 | apply from: "$rootProject.projectDir/commons.features.gradle"
9 |
10 | android {
11 | compileSdkVersion 30
12 | buildToolsVersion "30.0.2"
13 |
14 | defaultConfig {
15 | minSdkVersion 25
16 | targetSdkVersion 30
17 | versionCode 1
18 | versionName "1.0"
19 |
20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
21 | consumerProguardFiles "consumer-rules.pro"
22 | }
23 |
24 | buildTypes {
25 | release {
26 | minifyEnabled false
27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
28 | }
29 | }
30 |
31 | compileOptions {
32 | sourceCompatibility JavaVersion.VERSION_1_8
33 | targetCompatibility JavaVersion.VERSION_1_8
34 | }
35 |
36 | kotlinOptions {
37 | jvmTarget = '1.8'
38 | }
39 |
40 |
41 | flavorDimensions "server"
42 |
43 | productFlavors {
44 |
45 | dev {
46 | dimension "server"
47 | }
48 |
49 | uat {
50 | dimension "server"
51 | }
52 |
53 | pilot {
54 | dimension "server"
55 | }
56 |
57 | prod {
58 | dimension "server"
59 | }
60 | }
61 |
62 | publishNonDefault true
63 |
64 | buildFeatures {
65 | dataBinding true
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/features/onboarding/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/features/onboarding/consumer-rules.pro
--------------------------------------------------------------------------------
/features/onboarding/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/onboarding/src/androidTest/java/com/template/feature_onboarding/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.template.feature_onboarding
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.template.feature_onboarding.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/features/onboarding/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/features/onboarding/src/main/java/com/template/feature_onboarding/di/OnboardingViewModelModule.kt:
--------------------------------------------------------------------------------
1 | package com.template.feature_onboarding.di
2 |
3 | import com.template.feature_onboarding.view.fragment.signin.LoginViewModel
4 | import com.template.feature_onboarding.view.fragment.splash.SplashViewModel
5 | import kotlinx.coroutines.Dispatchers
6 | import org.koin.androidx.viewmodel.dsl.viewModel
7 | import org.koin.core.context.loadKoinModules
8 | import org.koin.dsl.module
9 |
10 | /****
11 | * File Description
12 | * Author: Lajesh Dineshkumar
13 | * Company:
14 | * Created on: 2/8/21
15 | * Modified on: 2/8/21
16 | *****/
17 | object OnboardingViewModelModule {
18 | fun load() {
19 | loadKoinModules(module {
20 |
21 | viewModel {
22 | LoginViewModel(get())
23 | }
24 |
25 |
26 | viewModel {
27 | SplashViewModel()
28 | }
29 |
30 | })
31 | }
32 | }
--------------------------------------------------------------------------------
/features/onboarding/src/main/java/com/template/feature_onboarding/view/fragment/signin/LoginFragment.kt:
--------------------------------------------------------------------------------
1 | package com.template.feature_onboarding.view.fragment.signin
2 |
3 | import com.template.core.ui.base.BaseFragment
4 | import com.template.feature_onboarding.R
5 | import com.template.feature_onboarding.BR
6 | import com.template.feature_onboarding.databinding.FragmentLoginBinding
7 |
8 | /****
9 | * File Description
10 | * Author: Lajesh Dineshkumar
11 | * Company:
12 | * Created on: 2/3/21
13 | * Modified on: 2/3/21
14 | *****/
15 | class LoginFragment : BaseFragment(LoginViewModel::class) {
16 | override val layoutRes: Int
17 | get() = R.layout.fragment_login
18 | override val bindingVariable: Int
19 | get() = BR.viewModel
20 | }
--------------------------------------------------------------------------------
/features/onboarding/src/main/java/com/template/feature_onboarding/view/fragment/signin/LoginViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.template.feature_onboarding.view.fragment.signin
2 |
3 | import android.net.Uri
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.viewModelScope
6 | import com.template.core.utils.NavigationCommand
7 | import com.template.core.viewmodel.base.BaseViewModel
8 | import com.template.domain.common.ResultState
9 | import com.template.domain.entity.request.AuthRequest
10 | import com.template.domain.entity.response.auth.AuthEntity
11 | import com.template.domain.usecases.auth.IAuthUseCase
12 | import kotlinx.coroutines.CoroutineDispatcher
13 | import kotlinx.coroutines.Dispatchers
14 | import kotlinx.coroutines.flow.collect
15 | import kotlinx.coroutines.launch
16 |
17 | /****
18 | * File Description
19 | * Author: Lajesh Dineshkumar
20 | * Company:
21 | * Created on: 2/3/21
22 | * Modified on: 2/3/21
23 | *****/
24 | class LoginViewModel constructor(
25 | private val authUseCase: IAuthUseCase
26 | ) : BaseViewModel() {
27 |
28 | val data = MutableLiveData(false)
29 | val userDetails = MutableLiveData()
30 |
31 | private var likeCount = 0
32 |
33 | fun getLikeCount() = likeCount
34 |
35 | fun addLikeCount() {
36 | viewModelScope.launch {
37 | likeCount += 1
38 | likeCount
39 | }
40 | }
41 |
42 | fun signup() {
43 | showLoading(true)
44 | viewModelScope.launch {
45 | authUseCase.signUp(AuthRequest.SignupRequest("", "dasdasd", true))
46 | .collect { state ->
47 | when (state) {
48 | is ResultState.Success -> {
49 | login()
50 | }
51 |
52 | is ResultState.Error -> {
53 | setError(error = state.error)
54 | showLoading(false)
55 | }
56 | }
57 |
58 | }
59 |
60 | }
61 | }
62 |
63 | fun login() {
64 | showLoading(true)
65 | viewModelScope.launch {
66 | authUseCase.signIn(AuthRequest.SigninRequest("test@test.com", "dasdasd", 0, false))
67 | .collect { state ->
68 | when (state) {
69 | is ResultState.Success -> {
70 | userDetails.value = state.data.userDetails
71 | data.value = true
72 | showLoading(false)
73 | navigationCommands.value =
74 | NavigationCommand.To(LoginFragmentDirections.actionLoginToProfile())
75 | }
76 |
77 | is ResultState.Error -> {
78 | setError(error = state.error)
79 | showLoading(false)
80 | }
81 | }
82 |
83 | }
84 |
85 | }
86 | }
87 |
88 | fun ediProfile() {
89 | navigationCommands.value = NavigationCommand.ToByDeepLink(Uri.parse("dpl://editprofile"))
90 | }
91 | }
--------------------------------------------------------------------------------
/features/onboarding/src/main/java/com/template/feature_onboarding/view/fragment/splash/SplashFragment.kt:
--------------------------------------------------------------------------------
1 | package com.template.feature_onboarding.view.fragment.splash
2 |
3 | import com.template.core.ui.base.BaseFragment
4 | import com.template.feature_onboarding.R
5 | import com.template.feature_onboarding.BR
6 | import com.template.feature_onboarding.databinding.FragmentSplashBinding
7 |
8 | /****
9 | * SplashFragment
10 | * Author: Lajesh Dineshkumar
11 | * Company:
12 | * Created on: 2/3/21
13 | * Modified on: 2/3/21
14 | *****/
15 | class SplashFragment : BaseFragment(SplashViewModel::class){
16 | override val layoutRes: Int
17 | get() = R.layout.fragment_splash
18 | override val bindingVariable: Int
19 | get() = BR.viewModel
20 | }
--------------------------------------------------------------------------------
/features/onboarding/src/main/java/com/template/feature_onboarding/view/fragment/splash/SplashViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.template.feature_onboarding.view.fragment.splash
2 |
3 | import com.template.core.utils.NavigationCommand
4 | import com.template.core.viewmodel.base.BaseViewModel
5 |
6 | /****
7 | * SplashViewModel
8 | * Author: Lajesh Dineshkumar
9 | * Company:
10 | * Created on: 2/3/21
11 | * Modified on: 2/3/21
12 | *****/
13 | class SplashViewModel : BaseViewModel() {
14 |
15 | init {
16 | android.os.Handler().postDelayed({
17 | navigationCommands.value = NavigationCommand.To(SplashFragmentDirections.actionSplashToLogin())
18 | }, 5000)
19 | }
20 | }
--------------------------------------------------------------------------------
/features/onboarding/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 |
--------------------------------------------------------------------------------
/features/onboarding/src/main/res/layout/fragment_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
17 |
18 |
27 |
28 |
29 |
39 |
40 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/features/onboarding/src/main/res/layout/fragment_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
17 |
18 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/features/onboarding/src/main/res/navigation/nav_graph_onboarding.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
14 |
15 |
24 |
25 |
26 |
27 |
32 |
33 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/features/onboarding/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/features/onboarding/src/test/java/com/template/feature_onboarding/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.template.feature_onboarding
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:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | id 'kotlin-kapt'
5 | id 'androidx.navigation.safeargs.kotlin'
6 | }
7 |
8 | apply from: "$rootProject.projectDir/commons.gradle"
9 | apply from: "$rootProject.projectDir/commons.features.gradle"
10 |
11 | android {
12 | compileSdkVersion 30
13 | buildToolsVersion "30.0.2"
14 |
15 | defaultConfig {
16 | minSdkVersion 25
17 | targetSdkVersion 30
18 | versionCode 1
19 | versionName "1.0"
20 |
21 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
22 | consumerProguardFiles "consumer-rules.pro"
23 | }
24 |
25 | buildTypes {
26 | release {
27 | minifyEnabled false
28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
29 | }
30 | }
31 | compileOptions {
32 | sourceCompatibility JavaVersion.VERSION_1_8
33 | targetCompatibility JavaVersion.VERSION_1_8
34 | }
35 |
36 | kotlinOptions {
37 | jvmTarget = '1.8'
38 | }
39 |
40 |
41 | flavorDimensions "server"
42 |
43 | productFlavors {
44 |
45 | dev {
46 | dimension "server"
47 | }
48 |
49 | uat {
50 | dimension "server"
51 | }
52 |
53 | pilot {
54 | dimension "server"
55 | }
56 |
57 | prod {
58 | dimension "server"
59 | }
60 | }
61 |
62 | publishNonDefault true
63 |
64 | buildFeatures {
65 | dataBinding true
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/features/profile/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/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/template/feature_profile/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.template.feature_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.template.feature_profile.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/features/profile/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/features/profile/src/main/java/com/template/feature_profile/di/ProfileViewModelModule.kt:
--------------------------------------------------------------------------------
1 | package com.template.feature_profile.di
2 |
3 | import com.template.feature_profile.edit.EditProfileViewModel
4 | import com.template.feature_profile.profile.ProfileViewModel
5 | import org.koin.core.context.loadKoinModules
6 | import org.koin.dsl.module
7 | import org.koin.androidx.viewmodel.dsl.viewModel
8 |
9 | /****
10 | * File Description
11 | * Author: Lajesh Dineshkumar
12 | * Company:
13 | * Created on: 2/8/21
14 | * Modified on: 2/8/21
15 | *****/
16 | object ProfileViewModelModule {
17 | fun load() {
18 | loadKoinModules(module {
19 |
20 | viewModel {
21 | ProfileViewModel()
22 | }
23 |
24 | viewModel {
25 | EditProfileViewModel()
26 | }
27 |
28 | })
29 | }
30 | }
--------------------------------------------------------------------------------
/features/profile/src/main/java/com/template/feature_profile/edit/EditProfileFragment.kt:
--------------------------------------------------------------------------------
1 | package com.template.feature_profile.edit
2 |
3 | import com.template.core.ui.base.BaseFragment
4 | import com.template.feature_profile.BR
5 | import com.template.feature_profile.R
6 | import com.template.feature_profile.databinding.FragmentEditProfileBinding
7 |
8 | /****
9 | * File Description
10 | * Author: Lajesh Dineshkumar
11 | * Company:
12 | * Created on: 2/4/21
13 | * Modified on: 2/4/21
14 | *****/
15 | class EditProfileFragment : BaseFragment(EditProfileViewModel::class) {
16 | override val layoutRes: Int
17 | get() = R.layout.fragment_edit_profile
18 | override val bindingVariable: Int
19 | get() = BR.viewModel
20 | }
--------------------------------------------------------------------------------
/features/profile/src/main/java/com/template/feature_profile/edit/EditProfileViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.template.feature_profile.edit
2 |
3 | import com.template.core.utils.NavigationCommand
4 | import com.template.core.viewmodel.base.BaseViewModel
5 | import com.template.feature_profile.R
6 |
7 | /****
8 | * Edit profile viewmodel
9 | * Author: Lajesh Dineshkumar
10 | * Company:
11 | * Created on: 2/4/21
12 | * Modified on: 2/4/21
13 | *****/
14 | class EditProfileViewModel : BaseViewModel() {
15 |
16 | fun backToLogin(){
17 | navigationCommands.value = NavigationCommand.BackTo(R.id.loginFragment)
18 | }
19 | }
--------------------------------------------------------------------------------
/features/profile/src/main/java/com/template/feature_profile/profile/ProfileFragment.kt:
--------------------------------------------------------------------------------
1 | package com.template.feature_profile.profile
2 |
3 | import com.template.core.ui.base.BaseFragment
4 | import com.template.feature_profile.BR
5 | import com.template.feature_profile.R
6 | import com.template.feature_profile.databinding.FragmentProfileBinding
7 |
8 | /****
9 | * File Description
10 | * Author: Lajesh Dineshkumar
11 | * Company:
12 | * Created on: 2/4/21
13 | * Modified on: 2/4/21
14 | *****/
15 | class ProfileFragment : BaseFragment(ProfileViewModel::class) {
16 | override val layoutRes: Int
17 | get() = R.layout.fragment_profile
18 | override val bindingVariable: Int
19 | get() = BR.viewModel
20 | }
--------------------------------------------------------------------------------
/features/profile/src/main/java/com/template/feature_profile/profile/ProfileViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.template.feature_profile.profile
2 |
3 | import com.template.core.utils.NavigationCommand
4 | import com.template.core.viewmodel.base.BaseViewModel
5 |
6 | /****
7 | * File Description
8 | * Author: Lajesh Dineshkumar
9 | * Company:
10 | * Created on: 2/4/21
11 | * Modified on: 2/4/21
12 | *****/
13 | class ProfileViewModel : BaseViewModel() {
14 |
15 | fun details(){
16 | navigationCommands.value = NavigationCommand.To(ProfileFragmentDirections.actionProfileToEditProfile())
17 | }
18 | }
--------------------------------------------------------------------------------
/features/profile/src/main/res/drawable-hdpi/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 |
--------------------------------------------------------------------------------
/features/profile/src/main/res/drawable-mdpi/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 |
--------------------------------------------------------------------------------
/features/profile/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 |
--------------------------------------------------------------------------------
/features/profile/src/main/res/layout/fragment_edit_profile.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
18 |
19 |
28 |
29 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/features/profile/src/main/res/layout/fragment_profile.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
18 |
19 |
28 |
29 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/features/profile/src/main/res/navigation/nav_graph_profile.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
14 |
15 |
22 |
23 |
24 |
25 |
30 |
31 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/features/profile/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/features/profile/src/test/java/com/template/feature_profile/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.template.feature_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 | }
--------------------------------------------------------------------------------
/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 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Jan 31 14:14:04 IST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/screenshots/app_modules.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/screenshots/app_modules.png
--------------------------------------------------------------------------------
/screenshots/arch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/screenshots/arch.png
--------------------------------------------------------------------------------
/screenshots/deeplink_onboarding.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/screenshots/deeplink_onboarding.png
--------------------------------------------------------------------------------
/screenshots/goback.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/screenshots/goback.png
--------------------------------------------------------------------------------
/screenshots/ids.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/screenshots/ids.png
--------------------------------------------------------------------------------
/screenshots/main_nav_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/screenshots/main_nav_graph.png
--------------------------------------------------------------------------------
/screenshots/module_to_module.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/screenshots/module_to_module.png
--------------------------------------------------------------------------------
/screenshots/onboarding_login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/screenshots/onboarding_login.png
--------------------------------------------------------------------------------
/screenshots/onboarding_nav_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/screenshots/onboarding_nav_graph.png
--------------------------------------------------------------------------------
/screenshots/profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/screenshots/profile.png
--------------------------------------------------------------------------------
/screenshots/profile_deeplink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/screenshots/profile_deeplink.png
--------------------------------------------------------------------------------
/screenshots/profile_ids.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/screenshots/profile_ids.png
--------------------------------------------------------------------------------
/screenshots/profile_nav_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/screenshots/profile_nav_graph.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':thirdpartys:analyticslib'
2 | include ':features:onboarding'
3 | include ':features:profile'
4 | include ':core'
5 | include ':benchmark'
6 | include ':data'
7 | include ':domain'
8 | include ':app'
9 | rootProject.name = "CleanArchApp"
--------------------------------------------------------------------------------
/thirdpartys/analyticslib/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/thirdpartys/analyticslib/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | compileSdkVersion 30
8 | buildToolsVersion "30.0.2"
9 |
10 | defaultConfig {
11 | minSdkVersion 25
12 | targetSdkVersion 30
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | consumerProguardFiles "consumer-rules.pro"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_1_8
28 | targetCompatibility JavaVersion.VERSION_1_8
29 | }
30 | kotlinOptions {
31 | jvmTarget = '1.8'
32 | }
33 | }
34 |
35 | dependencies {
36 |
37 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
38 | implementation 'androidx.core:core-ktx:1.3.2'
39 | implementation 'androidx.appcompat:appcompat:1.2.0'
40 | implementation 'com.google.android.material:material:1.2.1'
41 | testImplementation 'junit:junit:4.+'
42 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
43 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
44 |
45 | // Import the BoM for the Firebase platform
46 | implementation platform("com.google.firebase:firebase-bom:$analyticsVersion")
47 |
48 | // Declare the dependency for the Analytics library
49 | // When using the BoM, you don't specify versions in Firebase library dependencies
50 | implementation 'com.google.firebase:firebase-analytics-ktx'
51 | }
--------------------------------------------------------------------------------
/thirdpartys/analyticslib/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lajesh/clean-architecture-kotlin/af1fe7b55f7585e1406d93a02b7d336a0b81ad45/thirdpartys/analyticslib/consumer-rules.pro
--------------------------------------------------------------------------------
/thirdpartys/analyticslib/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
--------------------------------------------------------------------------------
/thirdpartys/analyticslib/src/androidTest/java/com/template/analyticslib/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.template.analyticslib
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.template.analyticslib.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/thirdpartys/analyticslib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/thirdpartys/analyticslib/src/main/java/com/template/analyticslib/AnalyicsHelper.kt:
--------------------------------------------------------------------------------
1 | package com.template.analyticslib
2 |
3 | import android.app.Activity
4 |
5 | /****
6 | * Abstraction of Analytics
7 | * Author: Lajesh Dineshkumar
8 | * Company:
9 | * Created on: 2/8/21
10 | * Modified on: 2/8/21
11 | *****/
12 | interface AnalyicsHelper {
13 | fun sendScreenView(screenName: String, activity: Activity)
14 | fun logUiEvent(itemId: String, action: String)
15 | fun setUserSignedIn(isSignedIn: Boolean)
16 | fun setUserRegistered(isRegistered: Boolean)
17 | }
--------------------------------------------------------------------------------
/thirdpartys/analyticslib/src/main/java/com/template/analyticslib/FirebaseAnalyticsHelper.kt:
--------------------------------------------------------------------------------
1 | package com.template.analyticslib
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.os.Bundle
6 | import com.google.firebase.analytics.FirebaseAnalytics
7 |
8 | /****
9 | * Implementation of Firebase Analytics
10 | * Author: Lajesh Dineshkumar
11 | * Company:
12 | * Created on: 2/8/21
13 | * Modified on: 2/8/21
14 | *****/
15 | class FirebaseAnalyticsHelper constructor(application: Application) : AnalyicsHelper {
16 | companion object {
17 | private const val UPROP_USER_SIGNED_IN = "user_signed_in"
18 | private const val UPROP_USER_REGISTERED = "user_registered"
19 | private const val CONTENT_TYPE_SCREEN_VIEW = "screen"
20 | private const val KEY_UI_ACTION = "ui_action"
21 | private const val CONTENT_TYPE_UI_EVENT = "ui event"
22 | }
23 |
24 | private var firebaseAnalytics: FirebaseAnalytics =
25 | FirebaseAnalytics.getInstance(application.applicationContext)
26 |
27 | override fun sendScreenView(screenName: String, activity: Activity) {
28 | val params = Bundle().apply {
29 | putString(FirebaseAnalytics.Param.ITEM_ID, screenName)
30 | putString(
31 | FirebaseAnalytics.Param.CONTENT_TYPE,
32 | CONTENT_TYPE_SCREEN_VIEW
33 | )
34 | }
35 | firebaseAnalytics.run {
36 | setCurrentScreen(activity, screenName, null)
37 | logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, params)
38 | }
39 | }
40 |
41 | override fun logUiEvent(itemId: String, action: String) {
42 | val params = Bundle().apply {
43 | putString(FirebaseAnalytics.Param.ITEM_ID, itemId)
44 | putString(
45 | FirebaseAnalytics.Param.CONTENT_TYPE,
46 | CONTENT_TYPE_UI_EVENT
47 | )
48 | putString(KEY_UI_ACTION, action)
49 | }
50 | firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, params)
51 | }
52 |
53 | override fun setUserSignedIn(isSignedIn: Boolean) {
54 | firebaseAnalytics.setUserProperty(UPROP_USER_SIGNED_IN, isSignedIn.toString())
55 | }
56 |
57 | override fun setUserRegistered(isRegistered: Boolean) {
58 | firebaseAnalytics.setUserProperty(UPROP_USER_REGISTERED, isRegistered.toString())
59 | }
60 | }
--------------------------------------------------------------------------------
/thirdpartys/analyticslib/src/test/java/com/template/analyticslib/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.template.analyticslib
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 | }
--------------------------------------------------------------------------------