├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── themes.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── values-night
│ │ │ │ └── themes.xml
│ │ │ ├── navigation
│ │ │ │ └── nav_graph.xml
│ │ │ ├── layout
│ │ │ │ ├── activity_main.xml
│ │ │ │ └── fragment_main.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── smarttoolfactory
│ │ │ │ └── projectboilerplate
│ │ │ │ ├── ui
│ │ │ │ └── theme
│ │ │ │ │ ├── Color.kt
│ │ │ │ │ ├── Shape.kt
│ │ │ │ │ ├── Type.kt
│ │ │ │ │ └── Theme.kt
│ │ │ │ ├── MyApplication.kt
│ │ │ │ ├── MainViewModel.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ └── MainFragment.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── smarttoolfactory
│ │ │ └── projectboilerplate
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── smarttoolfactory
│ │ └── projectboilerplate
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── features
└── feature
│ ├── .gitignore
│ ├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── smarttoolfactory
│ │ │ │ └── feature
│ │ │ │ ├── model
│ │ │ │ └── FeatureDependency.kt
│ │ │ │ ├── di
│ │ │ │ ├── FeatureComponent.kt
│ │ │ │ └── FeatureModule.kt
│ │ │ │ ├── viewmodel
│ │ │ │ └── FeatureViewModel.kt
│ │ │ │ └── FeatureFragment.kt
│ │ ├── AndroidManifest.xml
│ │ └── res
│ │ │ ├── navigation
│ │ │ └── nav_graph_feature.xml
│ │ │ └── layout
│ │ │ └── fragment_feature.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── smarttoolfactory
│ │ │ └── feature
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── smarttoolfactory
│ │ └── feature
│ │ └── ExampleInstrumentedTest.kt
│ └── build.gradle.kts
├── libraries
├── core
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ ├── main
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── smarttoolfactory
│ │ │ │ │ └── core
│ │ │ │ │ ├── error
│ │ │ │ │ └── NavigationException.kt
│ │ │ │ │ ├── di
│ │ │ │ │ ├── scope
│ │ │ │ │ │ └── FeatureScope.kt
│ │ │ │ │ ├── CoreModuleDependencies.kt
│ │ │ │ │ ├── CoreModule.kt
│ │ │ │ │ └── DataModule.kt
│ │ │ │ │ ├── CoreDependency.kt
│ │ │ │ │ ├── util
│ │ │ │ │ ├── LifecycleOwnerExtension.kt
│ │ │ │ │ ├── NavHostExtension.kt
│ │ │ │ │ ├── FlowViewStateExtension.kt
│ │ │ │ │ └── Event.kt
│ │ │ │ │ ├── viewmodel
│ │ │ │ │ └── NavControllerViewModel.kt
│ │ │ │ │ ├── viewstate
│ │ │ │ │ └── ViewState.kt
│ │ │ │ │ └── ui
│ │ │ │ │ ├── fragment
│ │ │ │ │ ├── BaseViewBindingFragment.kt
│ │ │ │ │ ├── navhost
│ │ │ │ │ │ ├── BaseDynamicNavHostFragment.kt
│ │ │ │ │ │ ├── BaseNavHostFragment.kt
│ │ │ │ │ │ └── NavHostContainerFragment.kt
│ │ │ │ │ └── DynamicNavigationFragment.kt
│ │ │ │ │ ├── adapter
│ │ │ │ │ └── BaseListAdapter.kt
│ │ │ │ │ └── widget
│ │ │ │ │ └── NestedScrollableHost.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── test
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── smarttoolfactory
│ │ │ │ └── core
│ │ │ │ └── ExampleUnitTest.kt
│ │ └── androidTest
│ │ │ └── java
│ │ │ └── com
│ │ │ └── smarttoolfactory
│ │ │ └── core
│ │ │ └── ExampleInstrumentedTest.kt
│ ├── proguard-rules.pro
│ └── build.gradle.kts
├── data
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ └── main
│ │ │ ├── java
│ │ │ └── com
│ │ │ │ └── smarttoolfactory
│ │ │ │ └── data
│ │ │ │ ├── api
│ │ │ │ └── SampleApi.kt
│ │ │ │ ├── model
│ │ │ │ ├── remote
│ │ │ │ │ └── SampleData.kt
│ │ │ │ ├── local
│ │ │ │ │ └── SampleDataEntity.kt
│ │ │ │ └── Mappables.kt
│ │ │ │ ├── db
│ │ │ │ ├── dao
│ │ │ │ │ ├── SampleDao.kt
│ │ │ │ │ └── BaseDao.kt
│ │ │ │ └── SampleDatabase.kt
│ │ │ │ ├── constant
│ │ │ │ └── Constants.kt
│ │ │ │ ├── source
│ │ │ │ ├── RemoteDataSource.kt
│ │ │ │ └── LocalDataSource.kt
│ │ │ │ ├── repository
│ │ │ │ └── Repository.kt
│ │ │ │ ├── mapper
│ │ │ │ └── Mappers.kt
│ │ │ │ └── di
│ │ │ │ ├── DatabaseModule.kt
│ │ │ │ └── NetworkModule.kt
│ │ │ └── AndroidManifest.xml
│ ├── proguard-rules.pro
│ ├── schemas
│ │ └── com.smarttoolfactory.data.db.SampleDatabase
│ │ │ └── 1.json
│ └── build.gradle.kts
├── domain
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ └── main
│ │ │ ├── java
│ │ │ └── com
│ │ │ │ └── smarttoolfactory
│ │ │ │ └── domain
│ │ │ │ ├── error
│ │ │ │ └── EmptyDataException.kt
│ │ │ │ ├── base
│ │ │ │ └── Disposable.kt
│ │ │ │ ├── dispatcher
│ │ │ │ └── UseCaseDispatchers.kt
│ │ │ │ └── usecase
│ │ │ │ └── UseCase.kt
│ │ │ └── AndroidManifest.xml
│ ├── proguard-rules.pro
│ └── build.gradle.kts
└── test-utils
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ ├── test
│ │ ├── resources
│ │ │ └── response.json
│ │ └── java
│ │ │ └── com
│ │ │ └── smarttoolfactory
│ │ │ └── test_utils
│ │ │ └── ExampleUnitTest.kt
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── smarttoolfactory
│ │ │ └── test_utils
│ │ │ ├── TestConstants.kt
│ │ │ ├── rule
│ │ │ ├── MockWebServerRule.kt
│ │ │ └── TestCoroutineRule.kt
│ │ │ ├── util
│ │ │ ├── LiveDataTestUtil.kt
│ │ │ └── ReadResourceUtil.kt
│ │ │ ├── extension
│ │ │ └── TestCoroutineExtension.kt
│ │ │ └── test_observer
│ │ │ ├── LiveDataTestObserver.kt
│ │ │ └── FlowTestObserver.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── smarttoolfactory
│ │ └── test_utils
│ │ └── ExampleInstrumentedTest.kt
│ ├── proguard-rules.pro
│ └── build.gradle.kts
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── settings.gradle.kts
├── .gitignore
├── scripts
└── git-hooks
│ └── pre-commit
├── gradle.properties
├── gradlew.bat
├── gradlew
├── config
└── detekt
│ └── detekt.yml
└── README.md
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/features/feature/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/libraries/core/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/libraries/core/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/libraries/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/libraries/data/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/libraries/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/libraries/domain/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/libraries/test-utils/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/libraries/test-utils/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/libraries/test-utils/src/test/resources/response.json:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/libraries/data/src/main/java/com/smarttoolfactory/data/api/SampleApi.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.data.api
2 |
3 | interface SampleApi
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmartToolFactory/Android-DaggerHilt-DynamicFetureModule-Boilerplate/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmartToolFactory/Android-DaggerHilt-DynamicFetureModule-Boilerplate/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmartToolFactory/Android-DaggerHilt-DynamicFetureModule-Boilerplate/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmartToolFactory/Android-DaggerHilt-DynamicFetureModule-Boilerplate/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmartToolFactory/Android-DaggerHilt-DynamicFetureModule-Boilerplate/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmartToolFactory/Android-DaggerHilt-DynamicFetureModule-Boilerplate/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | include(
2 | ":app",
3 | ":libraries:core",
4 | ":libraries:data",
5 | ":libraries:domain",
6 | ":libraries:test-utils",
7 | ":features:feature"
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmartToolFactory/Android-DaggerHilt-DynamicFetureModule-Boilerplate/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmartToolFactory/Android-DaggerHilt-DynamicFetureModule-Boilerplate/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmartToolFactory/Android-DaggerHilt-DynamicFetureModule-Boilerplate/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmartToolFactory/Android-DaggerHilt-DynamicFetureModule-Boilerplate/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/libraries/domain/src/main/java/com/smarttoolfactory/domain/error/EmptyDataException.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.domain.error
2 |
3 | class EmptyDataException(message: String) : Exception(message)
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmartToolFactory/Android-DaggerHilt-DynamicFetureModule-Boilerplate/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/libraries/core/src/main/java/com/smarttoolfactory/core/error/NavigationException.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.core.error
2 |
3 | class NavigationException(override val message: String?) : Exception(message)
4 |
--------------------------------------------------------------------------------
/libraries/core/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/libraries/data/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/libraries/domain/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/libraries/test-utils/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/features/feature/src/main/java/com/smarttoolfactory/feature/model/FeatureDependency.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.feature.model
2 |
3 | import android.content.Context
4 |
5 | class FeatureDependency(private val context: Context)
6 |
--------------------------------------------------------------------------------
/libraries/test-utils/src/main/java/com/smarttoolfactory/test_utils/TestConstants.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.test_utils
2 |
3 | const val RESPONSE_JSON_PATH = "response.json"
4 | const val SERVER_INTERNAL_ERROR_MESSAGE = "Unexpected error occurred"
5 |
--------------------------------------------------------------------------------
/libraries/data/src/main/java/com/smarttoolfactory/data/model/remote/SampleData.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.data.model.remote
2 |
3 | import com.smarttoolfactory.data.model.DataTransferObject
4 |
5 | data class SampleData(val id: Int) : DataTransferObject
6 |
--------------------------------------------------------------------------------
/libraries/core/src/main/java/com/smarttoolfactory/core/di/scope/FeatureScope.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.core.di.scope
2 |
3 | import javax.inject.Scope
4 |
5 | @Scope
6 | @kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
7 | annotation class FeatureScope
8 |
--------------------------------------------------------------------------------
/libraries/core/src/main/java/com/smarttoolfactory/core/CoreDependency.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.core
2 |
3 | /**
4 | * MOCK DEPENDENCY for instructing how to inject it to a feature module from **core module**, delete it for production
5 | */
6 | class CoreDependency
7 |
--------------------------------------------------------------------------------
/libraries/data/src/main/java/com/smarttoolfactory/data/db/dao/SampleDao.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.data.db.dao
2 |
3 | import androidx.room.Dao
4 | import com.smarttoolfactory.data.model.local.SampleDataEntity
5 |
6 | @Dao
7 | interface SampleDao : BaseDao
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Aug 02 11:05:47 TRT 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Project Boilerplate Code
3 | Dynamic Feature
4 | Feature
5 | Photos
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/*
5 | /.idea/caches
6 | /.idea/libraries
7 | /.idea/modules.xml
8 | /.idea/workspace.xml
9 | /.idea/navEditor.xml
10 | /.idea/assetWizardSettings.xml
11 | .DS_Store
12 | /build
13 | /captures
14 | .externalNativeBuild
15 | .cxx
16 | local.properties
17 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/com/smarttoolfactory/projectboilerplate/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.projectboilerplate.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple200 = Color(0xFFBB86FC)
6 | val Purple500 = Color(0xFF6200EE)
7 | val Purple700 = Color(0xFF3700B3)
8 | val Teal200 = Color(0xFF03DAC5)
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/libraries/data/src/main/java/com/smarttoolfactory/data/model/local/SampleDataEntity.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.data.model.local
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 | import com.smarttoolfactory.data.model.IEntity
6 |
7 | @Entity
8 | data class SampleDataEntity(@PrimaryKey val id: Int) : IEntity
9 |
--------------------------------------------------------------------------------
/libraries/domain/src/main/java/com/smarttoolfactory/domain/base/Disposable.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.domain.base
2 |
3 | /**
4 | * Interface for adding common behavior for any class that has items that need to be disposed
5 | * such as RxJava Observables or Coroutines jobs.
6 | */
7 | interface Disposable {
8 |
9 | fun dispose()
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/smarttoolfactory/projectboilerplate/MyApplication.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.projectboilerplate
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 |
6 | @HiltAndroidApp
7 | class MyApplication : Application() {
8 |
9 | override fun onCreate() {
10 | super.onCreate()
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/libraries/data/src/main/java/com/smarttoolfactory/data/constant/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.data.constant
2 |
3 | /*
4 | Web Service Constants
5 | */
6 |
7 | // Base Url
8 | const val BASE_URL = "http://www.example.com/"
9 |
10 | /*
11 | DBConstants
12 | */
13 | const val DATABASE_NAME = "sample.db"
14 | const val DATABASE_VERSION = 1
15 |
--------------------------------------------------------------------------------
/libraries/core/src/main/java/com/smarttoolfactory/core/util/LifecycleOwnerExtension.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.core.util
2 |
3 | import androidx.lifecycle.LifecycleOwner
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.Observer
6 |
7 | fun LifecycleOwner.observe(liveData: LiveData, predicate: (T) -> Unit) {
8 | liveData.observe(this, Observer { it?.let { predicate(it) } })
9 | }
10 |
--------------------------------------------------------------------------------
/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/java/com/smarttoolfactory/projectboilerplate/ui/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.projectboilerplate.ui.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val Shapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(4.dp),
10 | large = RoundedCornerShape(0.dp)
11 | )
12 |
--------------------------------------------------------------------------------
/libraries/core/src/main/java/com/smarttoolfactory/core/viewmodel/NavControllerViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.core.viewmodel
2 |
3 | import androidx.lifecycle.MutableLiveData
4 | import androidx.lifecycle.ViewModel
5 | import androidx.navigation.NavController
6 | import com.smarttoolfactory.core.util.Event
7 |
8 | class NavControllerViewModel : ViewModel() {
9 | val currentNavController = MutableLiveData>()
10 | }
11 |
--------------------------------------------------------------------------------
/libraries/core/src/test/java/com/smarttoolfactory/core/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.core
2 |
3 | import junit.framework.Assert.assertEquals
4 | import org.junit.Test
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/test/java/com/smarttoolfactory/projectboilerplate/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.projectboilerplate
2 |
3 | import org.junit.Assert.assertEquals
4 | import org.junit.Test
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/features/feature/src/test/java/com/smarttoolfactory/feature/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.feature
2 |
3 | import junit.framework.TestCase.assertEquals
4 | import org.junit.Test
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/libraries/test-utils/src/test/java/com/smarttoolfactory/test_utils/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.test_utils
2 |
3 | import org.junit.Assert.assertEquals
4 | import org.junit.Test
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/smarttoolfactory/projectboilerplate/MainViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.projectboilerplate
2 |
3 | import androidx.lifecycle.ViewModel
4 | import com.smarttoolfactory.domain.usecase.UseCase
5 | import dagger.hilt.android.lifecycle.HiltViewModel
6 | import javax.inject.Inject
7 | import kotlinx.coroutines.CoroutineScope
8 |
9 | @HiltViewModel
10 | class MainViewModel @Inject constructor(
11 | private val coroutineScope: CoroutineScope,
12 | private val useCase: UseCase
13 | ) : ViewModel()
14 |
--------------------------------------------------------------------------------
/features/feature/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/libraries/core/src/main/java/com/smarttoolfactory/core/viewstate/ViewState.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.core.viewstate
2 |
3 | class ViewState(
4 | val status: Status,
5 | val data: T? = null,
6 | val error: Throwable? = null
7 | ) {
8 |
9 | fun isSuccess() = status == Status.SUCCESS
10 |
11 | fun isLoading() = status == Status.LOADING
12 |
13 | fun getErrorMessage() = error?.message
14 |
15 | fun shouldShowErrorMessage() = error != null && status == Status.ERROR
16 | }
17 |
18 | enum class Status {
19 | LOADING,
20 | SUCCESS,
21 | ERROR
22 | }
23 |
--------------------------------------------------------------------------------
/libraries/data/src/main/java/com/smarttoolfactory/data/source/RemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.data.source
2 |
3 | import com.smarttoolfactory.data.api.SampleApi
4 | import com.smarttoolfactory.data.model.remote.SampleData
5 | import javax.inject.Inject
6 |
7 | interface RemoteDataSource {
8 | suspend fun getSampleDataList(): List
9 | }
10 |
11 | class RemoteDataSourceImpl @Inject constructor(private val api: SampleApi) : RemoteDataSource {
12 | override suspend fun getSampleDataList(): List {
13 | TODO("Not yet implemented")
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/scripts/git-hooks/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 |
4 | echo "Running static analysis..."
5 |
6 |
7 | # Inspect code using KtLint, and Detekt
8 |
9 | # Run KtLint only
10 | #./gradlew app:ktlintCheck --daemon
11 |
12 | # Format code using KtLint, then run Detekt and KtLint static analysis
13 | ./gradlew app:ktlintFormat app:detekt app:ktlintCheck --daemon
14 |
15 | status=$?
16 |
17 |
18 | if [ "$status" = 0 ] ; then
19 |
20 | echo "Static analysis found no problems."
21 |
22 | exit 0
23 |
24 | else
25 |
26 | echo 1>&2 "Static analysis found violations it could not fix."
27 |
28 | exit 1
29 |
30 | fi
31 |
--------------------------------------------------------------------------------
/libraries/data/src/main/java/com/smarttoolfactory/data/source/LocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.data.source
2 |
3 | import com.smarttoolfactory.data.db.dao.SampleDao
4 | import com.smarttoolfactory.data.model.local.SampleDataEntity
5 | import javax.inject.Inject
6 |
7 | interface LocalDataSource {
8 | suspend fun getSampleEntities(): List
9 | }
10 |
11 | class LocalDataSourceImpl @Inject constructor(private val sampleDao: SampleDao) : LocalDataSource {
12 |
13 | override suspend fun getSampleEntities(): List {
14 | TODO("Not yet implemented")
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/libraries/data/src/main/java/com/smarttoolfactory/data/db/SampleDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.data.db
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import com.smarttoolfactory.data.constant.DATABASE_VERSION
6 | import com.smarttoolfactory.data.db.dao.SampleDao
7 | import com.smarttoolfactory.data.model.local.SampleDataEntity
8 |
9 | @Database(
10 | entities = [
11 | SampleDataEntity::class,
12 | ],
13 | version = DATABASE_VERSION,
14 | exportSchema = true
15 | )
16 | abstract class SampleDatabase : RoomDatabase() {
17 | abstract fun sampleDao(): SampleDao
18 | }
19 |
--------------------------------------------------------------------------------
/libraries/data/src/main/java/com/smarttoolfactory/data/model/Mappables.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.data.model
2 |
3 | /**
4 | * Interface to create common behavior between entities to be able to use generics to create [Mapper]
5 | */
6 | interface IEntity : Mappable
7 |
8 | /**
9 | * Interface to create common behavior between DTOs to be able to use generics to create [Mapper]
10 | */
11 | interface DataTransferObject : Mappable
12 |
13 | /**
14 | * Marker interface to mark classes that implement, or interfaces that extend this interface
15 | * as convertible from one [Mappable] type to another
16 | */
17 | interface Mappable
18 |
--------------------------------------------------------------------------------
/libraries/core/src/main/java/com/smarttoolfactory/core/util/NavHostExtension.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.core.util
2 |
3 | import androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment
4 | import androidx.navigation.fragment.NavHostFragment
5 | import com.smarttoolfactory.core.ui.fragment.navhost.FieldProperty
6 | import com.smarttoolfactory.core.viewmodel.NavControllerViewModel
7 |
8 | var DynamicNavHostFragment.viewModel: NavControllerViewModel by FieldProperty {
9 | NavControllerViewModel()
10 | }
11 |
12 | var NavHostFragment.viewModel: NavControllerViewModel by FieldProperty {
13 | NavControllerViewModel()
14 | }
15 |
--------------------------------------------------------------------------------
/libraries/domain/src/main/java/com/smarttoolfactory/domain/dispatcher/UseCaseDispatchers.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.domain.dispatcher
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.Dispatchers
5 |
6 | /**
7 | * Class for providing dispatcher for different thread operations. This class is useful
8 | * in tests to control every thread in one place.
9 | */
10 |
11 | data class UseCaseDispatchers(
12 | val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
13 | val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default,
14 | val mainDispatcher: CoroutineDispatcher = Dispatchers.Main
15 | )
16 |
--------------------------------------------------------------------------------
/features/feature/src/main/res/navigation/nav_graph_feature.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
13 |
--------------------------------------------------------------------------------
/libraries/data/src/main/java/com/smarttoolfactory/data/repository/Repository.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.data.repository
2 |
3 | import com.smarttoolfactory.data.model.remote.SampleData
4 | import com.smarttoolfactory.data.source.LocalDataSource
5 | import com.smarttoolfactory.data.source.RemoteDataSource
6 | import javax.inject.Inject
7 |
8 | interface Repository {
9 |
10 | suspend fun geSampleDataFromRemote(): List
11 | }
12 |
13 | class RepositoryImpl @Inject constructor(
14 | private val remoteDataSource: RemoteDataSource,
15 | private val localDataSource: LocalDataSource
16 | ) : Repository {
17 |
18 | override suspend fun geSampleDataFromRemote(): List {
19 | TODO("Not yet implemented")
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/libraries/core/src/main/java/com/smarttoolfactory/core/di/CoreModuleDependencies.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.core.di
2 |
3 | import com.smarttoolfactory.core.CoreDependency
4 | import com.smarttoolfactory.domain.usecase.UseCase
5 | import dagger.hilt.EntryPoint
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.components.SingletonComponent
8 |
9 | /**
10 | * This component is required for adding dependencies to Dynamic Feature Modules by
11 | * adding [CoreModule] as dependent component
12 | */
13 | @EntryPoint
14 | @InstallIn(SingletonComponent::class)
15 | interface CoreModuleDependencies {
16 |
17 | /*
18 | Provision methods to provide dependencies to components that depend on this component
19 | */
20 | fun useCase(): UseCase
21 | fun coreDependency(): CoreDependency
22 | }
23 |
--------------------------------------------------------------------------------
/libraries/domain/src/main/java/com/smarttoolfactory/domain/usecase/UseCase.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.domain.usecase
2 |
3 | import com.smarttoolfactory.data.model.remote.SampleData
4 | import com.smarttoolfactory.data.repository.Repository
5 | import com.smarttoolfactory.domain.dispatcher.UseCaseDispatchers
6 | import javax.inject.Inject
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.flow
9 | import kotlinx.coroutines.flow.flowOn
10 |
11 | class UseCase @Inject constructor(
12 | private val repository: Repository,
13 | private val useCaseDispatchers: UseCaseDispatchers
14 | ) {
15 |
16 | fun getListFlow(): Flow> {
17 | return flow { emit(repository.geSampleDataFromRemote()) }
18 | .flowOn(useCaseDispatchers.defaultDispatcher)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.kts.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/libraries/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.kts.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/libraries/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.kts.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/libraries/core/src/androidTest/java/com/smarttoolfactory/core/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.core
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.assertEquals
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("com.smarttoolfactory.core.test", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/libraries/domain/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.kts.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/libraries/test-utils/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.kts.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/features/feature/src/androidTest/java/com/smarttoolfactory/feature/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.feature
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import junit.framework.TestCase.assertEquals
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("com.smarttoolfactory.feature", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/smarttoolfactory/projectboilerplate/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.projectboilerplate
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.assertEquals
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("com.smarttoolfactory.projectboilerplate", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/features/feature/src/main/java/com/smarttoolfactory/feature/di/FeatureComponent.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.feature.di
2 |
3 | import android.app.Application
4 | import androidx.fragment.app.Fragment
5 | import com.smarttoolfactory.core.di.CoreModuleDependencies
6 | import com.smarttoolfactory.feature.FeatureFragment
7 | import dagger.BindsInstance
8 | import dagger.Component
9 |
10 | @Component(
11 | dependencies = [CoreModuleDependencies::class],
12 | modules = [FeatureModule::class]
13 | )
14 | interface FeatureComponent {
15 |
16 | fun inject(fragment: FeatureFragment)
17 |
18 | @Component.Factory
19 | interface Factory {
20 | fun create(
21 | coreModuleDependencies: CoreModuleDependencies,
22 | @BindsInstance fragment: Fragment,
23 | @BindsInstance application: Application
24 | ): FeatureComponent
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/libraries/test-utils/src/androidTest/java/com/smarttoolfactory/test_utils/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | // package com.smarttoolfactory.test_utils
2 | //
3 | // import androidx.test.ext.junit.runners.AndroidJUnit4
4 | // import androidx.test.platform.app.InstrumentationRegistry
5 | // import org.junit.Test
6 | // import org.junit.runner.RunWith
7 | //
8 | // /**
9 | // * Instrumented test, which will execute on an Android device.
10 | // *
11 | // * See [testing documentation](http://d.android.com/tools/testing).
12 | // */
13 | // @RunWith(AndroidJUnit4::class)
14 | // class ExampleInstrumentedTest {
15 | // @Test
16 | // fun useAppContext() {
17 | // // Context of the app under test.
18 | // val appContext = InstrumentationRegistry.getInstrumentation().targetContext
19 | // assertEquals("com.smarttoolfactory.test_shared.test", appContext.packageName)
20 | // }
21 | // }
22 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/libraries/core/src/main/java/com/smarttoolfactory/core/util/FlowViewStateExtension.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.core.util
2 |
3 | import com.smarttoolfactory.core.viewstate.Status
4 | import com.smarttoolfactory.core.viewstate.ViewState
5 | import kotlinx.coroutines.CoroutineDispatcher
6 | import kotlinx.coroutines.Dispatchers
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.catch
9 | import kotlinx.coroutines.flow.emitAll
10 | import kotlinx.coroutines.flow.flowOf
11 | import kotlinx.coroutines.flow.flowOn
12 | import kotlinx.coroutines.flow.map
13 |
14 | fun Flow.convertToFlowViewState(
15 | dispatcher: CoroutineDispatcher = Dispatchers.Default
16 | ): Flow> {
17 | return this
18 | .map { list -> ViewState(status = Status.SUCCESS, data = list) }
19 | .catch { cause: Throwable -> emitAll(flowOf(ViewState(Status.ERROR, error = cause))) }
20 | .flowOn(dispatcher)
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/smarttoolfactory/projectboilerplate/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.projectboilerplate.ui.theme
2 |
3 | import androidx.compose.material.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | body1 = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp
15 | )
16 | /* Other default text styles to override
17 | button = TextStyle(
18 | fontFamily = FontFamily.Default,
19 | fontWeight = FontWeight.W500,
20 | fontSize = 14.sp
21 | ),
22 | caption = TextStyle(
23 | fontFamily = FontFamily.Default,
24 | fontWeight = FontWeight.Normal,
25 | fontSize = 12.sp
26 | )
27 | */
28 | )
29 |
--------------------------------------------------------------------------------
/libraries/data/src/main/java/com/smarttoolfactory/data/mapper/Mappers.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.data.mapper
2 |
3 | import com.smarttoolfactory.data.model.IEntity
4 | import com.smarttoolfactory.data.model.local.SampleDataEntity
5 | import com.smarttoolfactory.data.model.remote.SampleData
6 | import javax.inject.Inject
7 |
8 | /**
9 | * Mapper for transforming objects between REST and database or REST/db and domain
10 | * as [IEntity] which are Non-nullable to Non-nullable
11 | */
12 | interface Mapper {
13 | fun map(input: I): O
14 | }
15 |
16 | /**
17 | * Mapper for transforming objects between REST and database or REST/db and domain
18 | * as [List] of [IEntity] which are Non-nullable to Non-nullable
19 | */
20 | interface ListMapper : Mapper, List>
21 |
22 | class SampleDataMapper @Inject constructor() : Mapper {
23 | override fun map(input: SampleData): SampleDataEntity {
24 | return SampleDataEntity(input.id)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/libraries/data/src/main/java/com/smarttoolfactory/data/db/dao/BaseDao.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.data.db.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Delete
5 | import androidx.room.Insert
6 | import androidx.room.OnConflictStrategy
7 | import androidx.room.Transaction
8 | import androidx.room.Update
9 | import com.smarttoolfactory.data.model.IEntity
10 |
11 | @Dao
12 | interface BaseDao {
13 |
14 | /*
15 | Insert Single entity
16 | */
17 | @Insert(onConflict = OnConflictStrategy.REPLACE)
18 | suspend fun insert(entity: T): Long
19 |
20 | /*
21 | Insert Multiple entities
22 | */
23 | @Transaction
24 | @Insert(onConflict = OnConflictStrategy.REPLACE)
25 | suspend fun insert(entities: List): List
26 |
27 | /*
28 | Update
29 | */
30 | @Update
31 | suspend fun update(entity: T): Int
32 |
33 | /*
34 | Delete
35 | */
36 | @Delete
37 | suspend fun delete(entity: T): Int
38 | }
39 |
--------------------------------------------------------------------------------
/libraries/test-utils/src/main/java/com/smarttoolfactory/test_utils/rule/MockWebServerRule.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.test_utils.rule
2 |
3 | import okhttp3.mockwebserver.MockWebServer
4 | import org.junit.rules.TestRule
5 | import org.junit.runner.Description
6 | import org.junit.runners.model.Statement
7 |
8 | /**
9 | * Test rule for JUnit4 to invoke actions which are
10 | * start [MockWebServer],
11 | * run the test ,
12 | * and shut [MockWebServer] down after the test is run.
13 | */
14 | class MockWebServerRule : TestRule {
15 |
16 | val mockWebServer = MockWebServer()
17 |
18 | override fun apply(
19 | base: Statement,
20 | description: Description
21 | ): Statement {
22 |
23 | return object : Statement() {
24 |
25 | @Throws(Throwable::class)
26 | override fun evaluate() {
27 | mockWebServer.start()
28 | base.evaluate()
29 | mockWebServer.shutdown()
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/res/navigation/nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
15 |
20 |
21 |
25 |
26 |
--------------------------------------------------------------------------------
/libraries/data/src/main/java/com/smarttoolfactory/data/di/DatabaseModule.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.data.di
2 |
3 | import android.app.Application
4 | import androidx.room.Room
5 | import com.smarttoolfactory.data.constant.DATABASE_NAME
6 | import com.smarttoolfactory.data.db.SampleDatabase
7 | import com.smarttoolfactory.data.db.dao.SampleDao
8 | import dagger.Module
9 | import dagger.Provides
10 | import dagger.hilt.InstallIn
11 | import dagger.hilt.components.SingletonComponent
12 | import javax.inject.Singleton
13 |
14 | @InstallIn(SingletonComponent::class)
15 | @Module
16 | class DatabaseModule {
17 |
18 | @Singleton
19 | @Provides
20 | fun provideDatabase(application: Application): SampleDatabase {
21 | return Room.databaseBuilder(
22 | application,
23 | SampleDatabase::class.java,
24 | DATABASE_NAME
25 | ).build()
26 | }
27 |
28 | @Singleton
29 | @Provides
30 | fun provideSessionTokenDao(appDatabase: SampleDatabase): SampleDao =
31 | appDatabase.sampleDao()
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
21 |
22 |
--------------------------------------------------------------------------------
/libraries/core/src/main/java/com/smarttoolfactory/core/di/CoreModule.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.core.di
2 |
3 | import com.smarttoolfactory.core.CoreDependency
4 | import com.smarttoolfactory.domain.dispatcher.UseCaseDispatchers
5 | import dagger.Module
6 | import dagger.Provides
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 | import javax.inject.Singleton
10 | import kotlinx.coroutines.CoroutineScope
11 | import kotlinx.coroutines.Dispatchers
12 | import kotlinx.coroutines.SupervisorJob
13 |
14 | @InstallIn(SingletonComponent::class)
15 | @Module(includes = [DataModule::class])
16 | class CoreModule {
17 |
18 | @Singleton
19 | @Provides
20 | fun provideCoreDependency() = CoreDependency()
21 |
22 | @Singleton
23 | @Provides
24 | fun provideCoroutineScope() = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob())
25 |
26 | @Singleton
27 | @Provides
28 | fun provideUseCaseDispatchers(): UseCaseDispatchers {
29 | return UseCaseDispatchers(Dispatchers.IO, Dispatchers.Default, Dispatchers.Main)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/libraries/data/schemas/com.smarttoolfactory.data.db.SampleDatabase/1.json:
--------------------------------------------------------------------------------
1 | {
2 | "formatVersion": 1,
3 | "database": {
4 | "version": 1,
5 | "identityHash": "7d3b882d544656a1b1c9bf27028f57a4",
6 | "entities": [
7 | {
8 | "tableName": "SampleDataEntity",
9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, PRIMARY KEY(`id`))",
10 | "fields": [
11 | {
12 | "fieldPath": "id",
13 | "columnName": "id",
14 | "affinity": "INTEGER",
15 | "notNull": true
16 | }
17 | ],
18 | "primaryKey": {
19 | "columnNames": [
20 | "id"
21 | ],
22 | "autoGenerate": false
23 | },
24 | "indices": [],
25 | "foreignKeys": []
26 | }
27 | ],
28 | "views": [],
29 | "setupQueries": [
30 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
31 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7d3b882d544656a1b1c9bf27028f57a4')"
32 | ]
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
15 |
16 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/libraries/core/src/main/java/com/smarttoolfactory/core/ui/fragment/BaseViewBindingFragment.kt:
--------------------------------------------------------------------------------
1 |
2 | package com.smarttoolfactory.core.ui.fragment
3 |
4 | import androidx.fragment.app.Fragment
5 | import androidx.viewbinding.ViewBinding
6 |
7 | /**
8 | * BaseFragment to avoid writing data-binding code over again for each fragment.
9 | *
10 | * Generic approach forces Fragments to have specified number of ViewModels if added as generic parameter
11 | *
12 | * LifeCycle of Fragments
13 | *
14 | * * onAttach()
15 | * * onCreate()
16 | * * onCreateView() -> View is created or Fragment returned from back stack
17 | * * onViewCreated()
18 | * * onStart()
19 | * * onResume()
20 | * * onPause()
21 | * * onStop()
22 | * * onDestroyView() fragment sent to back stack / Back navigation -> onCreateView() is called
23 | * * onDestroy()
24 | * * onDetach()
25 | */
26 | abstract class BaseViewBindingFragment : Fragment() {
27 |
28 | /**
29 | * Generic nullable [ViewBinding] that is set to null in [BaseFragment.onDestroyView]
30 | */
31 | internal var binding: ViewBinding? = null
32 |
33 | override fun onDestroyView() {
34 | binding = null
35 | super.onDestroyView()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/smarttoolfactory/projectboilerplate/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.projectboilerplate.ui.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material.MaterialTheme
5 | import androidx.compose.material.darkColors
6 | import androidx.compose.material.lightColors
7 | import androidx.compose.runtime.Composable
8 |
9 | private val DarkColorPalette = darkColors(
10 | primary = Purple200,
11 | primaryVariant = Purple700,
12 | secondary = Teal200
13 | )
14 |
15 | private val LightColorPalette = lightColors(
16 | primary = Purple500,
17 | primaryVariant = Purple700,
18 | secondary = Teal200
19 |
20 | /* Other default colors to override
21 | background = Color.White,
22 | surface = Color.White,
23 | onPrimary = Color.White,
24 | onSecondary = Color.Black,
25 | onBackground = Color.Black,
26 | onSurface = Color.Black,
27 | */
28 | )
29 |
30 | @Composable
31 | fun SampleTheme(
32 | darkTheme: Boolean = isSystemInDarkTheme(),
33 | content: @Composable() () -> Unit
34 | ) {
35 | val colors = if (darkTheme) {
36 | DarkColorPalette
37 | } else {
38 | LightColorPalette
39 | }
40 |
41 | MaterialTheme(
42 | colors = colors,
43 | typography = Typography,
44 | shapes = Shapes,
45 | content = content
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/smarttoolfactory/projectboilerplate/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.projectboilerplate
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.compose.material.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.tooling.preview.Preview
8 | import com.smarttoolfactory.projectboilerplate.ui.theme.SampleTheme
9 | import dagger.hilt.android.AndroidEntryPoint
10 |
11 | @AndroidEntryPoint
12 | class MainActivity : AppCompatActivity() {
13 |
14 | override fun onCreate(savedInstanceState: Bundle?) {
15 | super.onCreate(savedInstanceState)
16 |
17 | // 🔥 Opens MainFragment which uses avController to navigate to dynamic feature module
18 | setContentView(R.layout.activity_main)
19 |
20 | // setContent {
21 | // SampleTheme {
22 | // // A surface container using the 'background' color from the theme
23 | // Surface(color = MaterialTheme.colors.background) {
24 | // Greeting("Android")
25 | // }
26 | // }
27 | // }
28 | }
29 | }
30 |
31 | @Composable
32 | fun Greeting(name: String) {
33 | Text(text = "Hello $name!")
34 | }
35 |
36 | @Preview(showBackground = true)
37 | @Composable
38 | fun DefaultPreview() {
39 | SampleTheme {
40 | Greeting("Android")
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/features/feature/src/main/java/com/smarttoolfactory/feature/viewmodel/FeatureViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.feature.viewmodel
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.ViewModelProvider
7 | import com.smarttoolfactory.core.viewstate.ViewState
8 | import com.smarttoolfactory.domain.usecase.UseCase
9 | import dagger.hilt.android.lifecycle.HiltViewModel
10 | import javax.inject.Inject
11 | import kotlinx.coroutines.CoroutineScope
12 |
13 | @HiltViewModel
14 | class FeatureViewModel @Inject constructor(
15 | private val coroutineScope: CoroutineScope,
16 | private val useCase: UseCase
17 | ) : ViewModel() {
18 |
19 | private val _dataState = MutableLiveData>>()
20 | val dataState: LiveData>>
21 | get() = _dataState
22 | }
23 |
24 | class FeatureViewModelFactory @Inject constructor(
25 | private val coroutineScope: CoroutineScope,
26 | private val useCase: UseCase,
27 | ) : ViewModelProvider.Factory {
28 |
29 | @Suppress("UNCHECKED_CAST")
30 | override fun create(modelClass: Class): T {
31 | if (modelClass != FeatureViewModel::class.java) {
32 | throw IllegalArgumentException("Unknown ViewModel class")
33 | }
34 | return FeatureViewModel(
35 | coroutineScope,
36 | useCase,
37 | ) as T
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/libraries/test-utils/src/main/java/com/smarttoolfactory/test_utils/util/LiveDataTestUtil.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.test_utils.util
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.Observer
5 | import java.util.concurrent.CountDownLatch
6 | import java.util.concurrent.TimeUnit
7 | import java.util.concurrent.TimeoutException
8 |
9 | /**
10 | * Gets the value of a [LiveData] or waits for it to have one, with a timeout.
11 | *
12 | * Use this extension from host-side (JVM) tests. It's recommended to use it alongside
13 | * `InstantTaskExecutorRule` or a similar mechanism to execute tasks synchronously.
14 | */
15 | fun LiveData.getOrAwaitValue(
16 | time: Long = 2,
17 | timeUnit: TimeUnit = TimeUnit.SECONDS,
18 | afterObserve: () -> Unit = {}
19 | ): T {
20 |
21 | var data: T? = null
22 | val latch = CountDownLatch(1)
23 |
24 | val observer = object : Observer {
25 | override fun onChanged(o: T?) {
26 | data = o
27 | latch.countDown()
28 | this@getOrAwaitValue.removeObserver(this)
29 | }
30 | }
31 |
32 | this.observeForever(observer)
33 |
34 | afterObserve.invoke()
35 |
36 | // Don't wait indefinitely if the LiveData is not set.
37 | if (!latch.await(time, timeUnit)) {
38 | this.removeObserver(observer)
39 | throw TimeoutException("LiveData value was never set.")
40 | }
41 |
42 | @Suppress("UNCHECKED_CAST")
43 | return data as T
44 | }
45 |
--------------------------------------------------------------------------------
/features/feature/src/main/java/com/smarttoolfactory/feature/di/FeatureModule.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.feature.di
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import androidx.fragment.app.Fragment
6 | import androidx.lifecycle.ViewModelProvider
7 | import com.smarttoolfactory.feature.model.FeatureDependency
8 | import com.smarttoolfactory.feature.viewmodel.FeatureViewModel
9 | import com.smarttoolfactory.feature.viewmodel.FeatureViewModelFactory
10 | import dagger.Binds
11 | import dagger.Module
12 | import dagger.Provides
13 | import dagger.hilt.InstallIn
14 | import dagger.hilt.android.components.FragmentComponent
15 | import kotlinx.coroutines.CoroutineScope
16 | import kotlinx.coroutines.Dispatchers
17 | import kotlinx.coroutines.SupervisorJob
18 |
19 | @InstallIn(FragmentComponent::class)
20 | @Module(includes = [FeatureBindModule::class])
21 | class FeatureModule {
22 |
23 | @Provides
24 | fun provideFeatureObject(context: Context) = FeatureDependency(context)
25 |
26 | @Provides
27 | fun provideFeatureViewModel(fragment: Fragment, factory: FeatureViewModelFactory) =
28 | ViewModelProvider(fragment, factory).get(FeatureViewModel::class.java)
29 |
30 | @Provides
31 | fun provideCoroutineScope() =
32 | CoroutineScope(Dispatchers.Main.immediate + SupervisorJob())
33 | }
34 |
35 | @InstallIn(FragmentComponent::class)
36 | @Module
37 | abstract class FeatureBindModule {
38 | @Binds
39 | abstract fun bindContext(application: Application): Context
40 | }
41 |
--------------------------------------------------------------------------------
/libraries/test-utils/src/main/java/com/smarttoolfactory/test_utils/rule/TestCoroutineRule.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.test_utils.rule
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.ExperimentalCoroutinesApi
5 | import kotlinx.coroutines.test.TestCoroutineDispatcher
6 | import kotlinx.coroutines.test.TestCoroutineScope
7 | import kotlinx.coroutines.test.resetMain
8 | import kotlinx.coroutines.test.runBlockingTest
9 | import kotlinx.coroutines.test.setMain
10 | import org.junit.rules.TestRule
11 | import org.junit.runner.Description
12 | import org.junit.runners.model.Statement
13 |
14 | @ExperimentalCoroutinesApi
15 | class TestCoroutineRule : TestRule {
16 |
17 | private val testCoroutineDispatcher = TestCoroutineDispatcher()
18 |
19 | val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher)
20 |
21 | override fun apply(base: Statement, description: Description?) = object : Statement() {
22 |
23 | @Throws(Throwable::class)
24 | override fun evaluate() {
25 |
26 | Dispatchers.setMain(testCoroutineDispatcher)
27 |
28 | base.evaluate()
29 |
30 | Dispatchers.resetMain()
31 | try {
32 | testCoroutineScope.cleanupTestCoroutines()
33 | } catch (exception: Exception) {
34 | exception.printStackTrace()
35 | }
36 | }
37 | }
38 |
39 | fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) =
40 | testCoroutineScope.runBlockingTest { block() }
41 | }
42 |
--------------------------------------------------------------------------------
/features/feature/src/main/res/layout/fragment_feature.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
21 |
22 |
23 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
22 |
23 |
34 |
35 |
--------------------------------------------------------------------------------
/libraries/core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import extension.addCoreModuleDependencies
2 | import extension.addInstrumentationTestDependencies
3 | import extension.addUnitTestDependencies
4 |
5 | plugins {
6 | id(Plugins.ANDROID_LIBRARY_PLUGIN)
7 | id(Plugins.KOTLIN_ANDROID_PLUGIN)
8 | id(Plugins.KOTLIN_ANDROID_EXTENSIONS_PLUGIN)
9 | id(Plugins.KOTLIN_KAPT_PLUGIN)
10 | }
11 |
12 | android {
13 |
14 | compileSdk = AndroidVersion.COMPILE_SDK_VERSION
15 | defaultConfig {
16 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 |
19 | buildTypes {
20 | getByName("release") {
21 | isMinifyEnabled = false
22 | proguardFiles(
23 | getDefaultProguardFile("proguard-android-optimize.txt"),
24 | "proguard-rules.pro"
25 | )
26 | }
27 | }
28 |
29 | buildFeatures {
30 | viewBinding = true
31 | }
32 |
33 | compileOptions {
34 | sourceCompatibility = JavaVersion.VERSION_1_8
35 | targetCompatibility = JavaVersion.VERSION_1_8
36 | }
37 |
38 | kotlinOptions {
39 | jvmTarget = "1.8"
40 | }
41 |
42 | testOptions {
43 | unitTests.isIncludeAndroidResources = true
44 | }
45 | }
46 |
47 | dependencies {
48 |
49 | implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
50 |
51 | implementation(project(Modules.AndroidLibrary.DOMAIN))
52 | implementation(project(Modules.AndroidLibrary.DATA))
53 |
54 | addCoreModuleDependencies()
55 |
56 | addUnitTestDependencies()
57 | testImplementation(project(Modules.AndroidLibrary.TEST_UTILS))
58 |
59 | addInstrumentationTestDependencies()
60 | androidTestImplementation(project(Modules.AndroidLibrary.TEST_UTILS))
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/libraries/test-utils/src/main/java/com/smarttoolfactory/test_utils/extension/TestCoroutineExtension.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.test_utils.extension
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.test.TestCoroutineDispatcher
5 | import kotlinx.coroutines.test.TestCoroutineScope
6 | import kotlinx.coroutines.test.resetMain
7 | import kotlinx.coroutines.test.runBlockingTest
8 | import kotlinx.coroutines.test.setMain
9 | import org.junit.jupiter.api.extension.AfterEachCallback
10 | import org.junit.jupiter.api.extension.BeforeEachCallback
11 | import org.junit.jupiter.api.extension.ExtensionContext
12 |
13 | /**
14 | * LifeCycle
15 | *
16 | * * BeforeAllCallback
17 | * * BeforeAll
18 | * * BeforeEachCallback
19 | * * BeforeEach
20 | * * BeforeTestExecutionCallback
21 | * * Test
22 | * * AfterTestExecutionCallback
23 | * * AfterEach
24 | * * AfterEachCallback
25 | * * AfterAll
26 | * * AfterAllCallback
27 | */
28 | class TestCoroutineExtension : BeforeEachCallback, AfterEachCallback {
29 |
30 | private val testCoroutineDispatcher = TestCoroutineDispatcher()
31 |
32 | val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher)
33 |
34 | override fun beforeEach(context: ExtensionContext?) {
35 | println("🚙 TestCoroutineExtension beforeEach()")
36 | Dispatchers.setMain(testCoroutineDispatcher)
37 | }
38 |
39 | override fun afterEach(context: ExtensionContext?) {
40 |
41 | println("🚗 TestCoroutineExtension afterEach()")
42 |
43 | Dispatchers.resetMain()
44 | try {
45 | testCoroutineScope.cleanupTestCoroutines()
46 | } catch (exception: Exception) {
47 | exception.printStackTrace()
48 | }
49 | }
50 |
51 | fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) =
52 | testCoroutineScope.runBlockingTest { block() }
53 | }
54 |
--------------------------------------------------------------------------------
/libraries/test-utils/src/main/java/com/smarttoolfactory/test_utils/util/ReadResourceUtil.kt:
--------------------------------------------------------------------------------
1 | package com.smarttoolfactory.test_utils.util
2 |
3 | import com.google.gson.Gson
4 | import com.google.gson.GsonBuilder
5 | import com.google.gson.internal.LinkedTreeMap
6 | import com.google.gson.reflect.TypeToken
7 |
8 | /**
9 | * Use this method to get json files as string from resources folder to use in tests.
10 | */
11 | fun getResourceAsText(path: String): String {
12 | return object {}.javaClass.classLoader!!.getResource(path)!!.readText()
13 | }
14 |
15 | inline fun Gson.fromJsonWithType(json: String): T? =
16 | fromJson(json, object : TypeToken() {}.type)
17 |
18 | /**
19 | *
20 | * Convert from json to item with type T
21 | *
22 | * * This function returns for some items as [LinkedTreeMap]
23 | */
24 | inline fun convertToObjectFromJson(json: String): T? {
25 | return Gson().fromJsonWithType(json)
26 | }
27 |
28 | /**
29 | *
30 | * Convert from json to [List] of items with type T
31 | *
32 | * * This function returns for some items as [LinkedTreeMap]
33 | */
34 | inline fun fromJsonToListOf(json: String): List {
35 | return GsonBuilder().create().fromJson(json, Array::class.java).asList()
36 | }
37 |
38 | fun Gson.mapFromLinkedTreeMap(map: Map?, type: Class): T? {
39 | if (map == null) return null
40 |
41 | val json = toJson(map)
42 | return fromJson(json, type)
43 | }
44 |
45 | inline fun convertFromJsonToListOf(json: String): List? {
46 |
47 | val gson = GsonBuilder().create()
48 |
49 | val itemList = fromJsonToListOf(json)
50 |
51 | if (itemList.first() !is LinkedTreeMap<*, *>)
52 | return itemList
53 |
54 | // Must use map here because the result is a list of LinkedTreeMaps
55 | val list: ArrayList